Add ilength and some documentation

This commit is contained in:
Michaël Lemaire 2021-08-17 18:06:37 +02:00
parent f2016e38e2
commit db54d4f444
8 changed files with 203 additions and 13 deletions

View file

@ -1,3 +1,82 @@
# typescript/iterators
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/iterators?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=iterators)
## About
Lazy iterators to work on dynamic data sets without materializing them.
They allow to work on infinite streams of values, with limited memory
consumption.
Functions in this library that do not return an Iterable are "materializing",
meaning that they may consume iterators up to the end, and will not work well on
infinite iterators (a limit is often provided to break out).
These iterators are guaranteed to be repeatable, meaning that calling
Symbol.iterator on them will start over.
## Import
In deno:
```typescript
import { iarray, isum } from "https://js.thunderk.net/iterators/mod.ts";
```
In browser:
```html
<script type="module">
import { iarray, isum } from "https://js.thunderk.net/iterators/mod.js";
</script>
```
## Use
Examples (edge cases can be found in each function's documentation):
```typescript
// Create iterables
const i1 = iarray([1, 2, 3]); // 1 2 3
const i2 = isingle(4); // 4
const i3 = irecur(0, (x) => x - 1); // 0 -1 -2 -3 -4 ...
const i4 = irange(5); // 0 1 2 3 4
const i5 = irange(5, 1, 2); // 1 3 5 7 9
const i6 = istep(4, iarray([1, 10, 1, -1])); // 4 5 15 16 15
const i7 = irepeat(2); // 2 2 2 2 2 ...
// Consume iterables
iforeach(i1, console.log);
ifirst(i6); // 4
ifirstmap(i1, (x) => x > 1 ? -x : null); // -2
imaterialize(i1); // [1, 2, 3]
ilength(i1); // 3
iat(i5, 3); // 7
ireduce(i1, (a, b) => a + b, 0); // 6
isum(i1); // 6
icat(iarray(["a", "b", "c"])); // abc
imin(i1); // 1
imax(i1); // 3
// Transform iterables
iloop(i1); // 1 2 3 1 2 3 ...
iskip(i3, 2); // -2 -3 -4 -5 -6 ...
imap(i1, (x) => x * 2); // 2 4 6
ifilter(i3, (x) => x % 4 == 0); // 0 -4 -8 -12 ...
ipartition(i3, (x) => x % 4 == 0); // [0 -4 -8 ..., -1 -2 -3 -5 ...]
iunique(iarray([1, 4, 2, 4, 3, 1, 5])); // 1 4 2 3 5
// Combine iterables
ichain(i1, i2); // 1 2 3 4
ichainit(iarray([i1, i2])); // 1 2 3 4
icombine(iarray([0, 1]), iarray(["a", "b"])); // [0, "a"] [0, "b"] [1, "a"] [2, "b"]
izip(iarray([0, 1]), iarray(["a", "b"])); // [0, "a"] [1, "b"]
ialternate(iarray([0, 1]), iarray(["a", "b"])); // 0 "a" 1 "b"
// Type filter (result is a typed iterable)
ifiltertype(iarray([1, "a", 2, "b"]), (x): x is number => typeof x == "number"); // 1 2
class A {}
class B {}
ifilterclass(iarray([new A(), new B(), new A()]), A); // A A
```

6
deps.testing.ts Normal file
View file

@ -0,0 +1,6 @@
export {
describe,
expect,
it,
mockfn,
} from "https://js.thunderk.net/devtools@1.3.0/testing.ts";

13
doc/about.md Normal file
View file

@ -0,0 +1,13 @@
## About
Lazy iterators to work on dynamic data sets without materializing them.
They allow to work on infinite streams of values, with limited memory
consumption.
Functions in this library that do not return an Iterable are "materializing",
meaning that they may consume iterators up to the end, and will not work well on
infinite iterators (a limit is often provided to break out).
These iterators are guaranteed to be repeatable, meaning that calling
Symbol.iterator on them will start over.

15
doc/import.md Normal file
View file

@ -0,0 +1,15 @@
## Import
In deno:
```typescript
import { iarray, isum } from "https://js.thunderk.net/iterators/mod.ts";
```
In browser:
```html
<script type="module">
import { iarray, isum } from "https://js.thunderk.net/iterators/mod.js";
</script>
```

3
doc/index Normal file
View file

@ -0,0 +1,3 @@
about
import
use

48
doc/use.md Normal file
View file

@ -0,0 +1,48 @@
## Use
Examples (edge cases can be found in each function's documentation):
```typescript
// Create iterables
const i1 = iarray([1, 2, 3]); // 1 2 3
const i2 = isingle(4); // 4
const i3 = irecur(0, (x) => x - 1); // 0 -1 -2 -3 -4 ...
const i4 = irange(5); // 0 1 2 3 4
const i5 = irange(5, 1, 2); // 1 3 5 7 9
const i6 = istep(4, iarray([1, 10, 1, -1])); // 4 5 15 16 15
const i7 = irepeat(2); // 2 2 2 2 2 ...
// Consume iterables
iforeach(i1, console.log);
ifirst(i6); // 4
ifirstmap(i1, (x) => x > 1 ? -x : null); // -2
imaterialize(i1); // [1, 2, 3]
ilength(i1); // 3
iat(i5, 3); // 7
ireduce(i1, (a, b) => a + b, 0); // 6
isum(i1); // 6
icat(iarray(["a", "b", "c"])); // abc
imin(i1); // 1
imax(i1); // 3
// Transform iterables
iloop(i1); // 1 2 3 1 2 3 ...
iskip(i3, 2); // -2 -3 -4 -5 -6 ...
imap(i1, (x) => x * 2); // 2 4 6
ifilter(i3, (x) => x % 4 == 0); // 0 -4 -8 -12 ...
ipartition(i3, (x) => x % 4 == 0); // [0 -4 -8 ..., -1 -2 -3 -5 ...]
iunique(iarray([1, 4, 2, 4, 3, 1, 5])); // 1 4 2 3 5
// Combine iterables
ichain(i1, i2); // 1 2 3 4
ichainit(iarray([i1, i2])); // 1 2 3 4
icombine(iarray([0, 1]), iarray(["a", "b"])); // [0, "a"] [0, "b"] [1, "a"] [2, "b"]
izip(iarray([0, 1]), iarray(["a", "b"])); // [0, "a"] [1, "b"]
ialternate(iarray([0, 1]), iarray(["a", "b"])); // 0 "a" 1 "b"
// Type filter (result is a typed iterable)
ifiltertype(iarray([1, "a", 2, "b"]), (x): x is number => typeof x == "number"); // 1 2
class A {}
class B {}
ifilterclass(iarray([new A(), new B(), new A()]), A); // A A
```

View file

@ -1,10 +1,4 @@
import {
describe,
expect,
it,
mockfn,
patch,
} from "https://code.thunderk.net/typescript/devtools/raw/1.3.0/testing.ts";
import { describe, expect, it, mockfn } from "./deps.testing.ts";
import {
ialternate,
iarray,
@ -20,6 +14,7 @@ import {
ifirst,
ifirstmap,
iforeach,
ilength,
iloop,
imap,
imaterialize,
@ -133,13 +128,23 @@ describe("Iterators", () => {
});
it("materializes an array from an iterator", () => {
expect(imaterialize(IEMPTY)).toEqual([]);
expect(imaterialize(iarray([1, 2, 3]))).toEqual([1, 2, 3]);
expect(() => imaterialize(irepeat(null))).toThrow(
"Length limit on iterator materialize",
);
expect(() => imaterialize(iarray([1, 2, 3, 4, 5]), 2)).toThrow(
"Length limit on iterator materialize",
);
});
it("counts items in an iterator", () => {
expect(ilength(IEMPTY)).toEqual(0);
expect(ilength(iarray([1, 2, 3]))).toEqual(3);
expect(ilength(irange())).toEqual(Infinity);
expect(ilength(iarray([1, 2, 3, 4, 5]), 3)).toEqual(Infinity);
});
it("creates an iterator in a range of integers", () => {
checkit(irange(4), [0, 1, 2, 3]);
checkit(irange(4, 1), [1, 2, 3, 4]);
@ -225,6 +230,8 @@ describe("Iterators", () => {
it("maps an iterator", () => {
checkit(imap(IEMPTY, (i) => i * 2), []);
checkit(imap(irange(3), (i) => i * 2), [0, 2, 4]);
const r: Iterable<string> = imap(irange(3), (i) => i.toString());
checkit(r, ["0", "1", "2"]);
});
it("reduces an iterator", () => {
@ -239,7 +246,7 @@ describe("Iterators", () => {
});
it("filters an iterator with a type guard", () => {
let result = ifiltertype(
let result: Iterable<number> = ifiltertype(
<(number | string)[]> [1, "a", 2, "b"],
(x): x is number => typeof x == "number",
);
@ -250,7 +257,7 @@ describe("Iterators", () => {
let o1 = new A();
let o2 = new A();
let o3 = new B();
let result = ifilterclass([1, "a", o1, 2, o2, o3, "b"], A);
let result: Iterable<A> = ifilterclass([1, "a", o1, 2, o2, o3, "b"], A);
checkit(result, [o1, o2]);
});

25
mod.ts
View file

@ -133,6 +133,25 @@ export function imaterialize<T>(iterable: Iterable<T>, limit = 1000000): T[] {
return result;
}
/**
* Count items in an iterator
*
* To avoid counting infinite iterators (and bursting memory), the item count is limited by
* the *limit* parameter, and Infinity will be returned after that many.
*/
export function ilength<T>(iterable: Iterable<T>, limit = 1000000): number {
let result = 0;
for (let _ of iterable) {
if (result >= limit) {
return Infinity;
}
result += 1;
}
return result;
}
/**
* Iterate over natural integers
*
@ -451,12 +470,12 @@ export function iunique<T>(
): Iterable<T> {
return {
[Symbol.iterator]: function* () {
let done: T[] = [];
let done: Set<T> = new Set();
let n = limit;
for (let value of iterable) {
if (done.indexOf(value) < 0) {
if (!done.has(value)) {
if (n-- > 0) {
done.push(value);
done.add(value);
yield value;
} else {
throw new Error("Unique count limit on iterator");