diff --git a/README.md b/README.md
index 23d9d40..b81898e 100644
--- a/README.md
+++ b/README.md
@@ -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
+
+```
+
+## 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
+```
diff --git a/deps.testing.ts b/deps.testing.ts
new file mode 100644
index 0000000..7abf437
--- /dev/null
+++ b/deps.testing.ts
@@ -0,0 +1,6 @@
+export {
+ describe,
+ expect,
+ it,
+ mockfn,
+} from "https://js.thunderk.net/devtools@1.3.0/testing.ts";
diff --git a/doc/about.md b/doc/about.md
new file mode 100644
index 0000000..15813e4
--- /dev/null
+++ b/doc/about.md
@@ -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.
diff --git a/doc/import.md b/doc/import.md
new file mode 100644
index 0000000..0c9c9b5
--- /dev/null
+++ b/doc/import.md
@@ -0,0 +1,15 @@
+## Import
+
+In deno:
+
+```typescript
+import { iarray, isum } from "https://js.thunderk.net/iterators/mod.ts";
+```
+
+In browser:
+
+```html
+
+```
diff --git a/doc/index b/doc/index
new file mode 100644
index 0000000..b1dffcc
--- /dev/null
+++ b/doc/index
@@ -0,0 +1,3 @@
+about
+import
+use
diff --git a/doc/use.md b/doc/use.md
new file mode 100644
index 0000000..757a9b6
--- /dev/null
+++ b/doc/use.md
@@ -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
+```
diff --git a/mod.test.ts b/mod.test.ts
index b359d07..dcd0903 100644
--- a/mod.test.ts
+++ b/mod.test.ts
@@ -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 = 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 = 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 = ifilterclass([1, "a", o1, 2, o2, o3, "b"], A);
checkit(result, [o1, o2]);
});
diff --git a/mod.ts b/mod.ts
index d7f75b4..cfc5494 100644
--- a/mod.ts
+++ b/mod.ts
@@ -133,6 +133,25 @@ export function imaterialize(iterable: Iterable, 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(iterable: Iterable, 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(
): Iterable {
return {
[Symbol.iterator]: function* () {
- let done: T[] = [];
+ let done: Set = 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");