352 lines
9.3 KiB
TypeScript
352 lines
9.3 KiB
TypeScript
import { describe, expect, it, mockfn } from "./deps.testing.ts";
|
|
import {
|
|
ialternate,
|
|
iarray,
|
|
iat,
|
|
icat,
|
|
ichain,
|
|
ichainit,
|
|
icombine,
|
|
IEMPTY,
|
|
ifilter,
|
|
ifilterclass,
|
|
ifiltertype,
|
|
ifirst,
|
|
ifirstmap,
|
|
iforeach,
|
|
ilength,
|
|
iloop,
|
|
imap,
|
|
imapfilter,
|
|
imaterialize,
|
|
imax,
|
|
imaxBy,
|
|
imin,
|
|
iminBy,
|
|
ipartition,
|
|
irange,
|
|
irecur,
|
|
ireduce,
|
|
irepeat,
|
|
isingle,
|
|
iskip,
|
|
istep,
|
|
isum,
|
|
iunique,
|
|
izip,
|
|
izipg,
|
|
} from "./mod.ts";
|
|
|
|
class A {}
|
|
class B {}
|
|
|
|
describe("Iterators", () => {
|
|
function checkit<T>(
|
|
base_iterator: Iterable<T>,
|
|
values: T[],
|
|
infinite = false,
|
|
) {
|
|
function checker() {
|
|
let iterator = base_iterator[Symbol.iterator]();
|
|
values.forEach((value) => {
|
|
let state = iterator.next();
|
|
expect(state.done).toBe(false);
|
|
expect(state.value).toEqual(value);
|
|
});
|
|
if (!infinite) {
|
|
for (let i = 0; i < 3; i++) {
|
|
let state = iterator.next();
|
|
expect(state.done).toBe(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make two iterations to be sure it unwinds the same
|
|
checker();
|
|
checker();
|
|
}
|
|
|
|
it("constructs an iterator from a recurrent formula", () => {
|
|
checkit(irecur(1, (x) => x + 2), [1, 3, 5], true);
|
|
checkit(irecur(4, (x) => x ? x - 1 : null), [4, 3, 2, 1, 0]);
|
|
});
|
|
|
|
it("constructs an iterator from an array", () => {
|
|
checkit(iarray([]), []);
|
|
checkit(iarray([1, 2, 3]), [1, 2, 3]);
|
|
});
|
|
|
|
it("constructs an iterator from a single value", () => {
|
|
checkit(isingle(1), [1]);
|
|
checkit(isingle("a"), ["a"]);
|
|
});
|
|
|
|
it("repeats a value", () => {
|
|
checkit(irepeat("a"), ["a", "a", "a", "a"], true);
|
|
checkit(irepeat("a", 3), ["a", "a", "a"]);
|
|
});
|
|
|
|
it("calls a function for each yielded value", () => {
|
|
let iterator = iarray([1, 2, 3]);
|
|
let result: number[] = [];
|
|
iforeach(iterator, (val) => result.push(val));
|
|
expect(result).toEqual([1, 2, 3]);
|
|
|
|
result = [];
|
|
iforeach(iterator, (i) => {
|
|
result.push(i);
|
|
if (i == 2) {
|
|
return null;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
});
|
|
expect(result).toEqual([1, 2]);
|
|
|
|
result = [];
|
|
iforeach(iterator, (i) => {
|
|
result.push(i);
|
|
return i;
|
|
}, 2);
|
|
expect(result).toEqual([1, 2]);
|
|
|
|
result = [];
|
|
for (let i of iterator) {
|
|
result.push(i);
|
|
}
|
|
expect(result).toEqual([1, 2, 3]);
|
|
});
|
|
|
|
it("finds the first item passing a predicate", () => {
|
|
expect(ifirst(iarray(<number[]> []), (i) => i % 2 == 0)).toBeNull();
|
|
expect(ifirst(iarray([1, 2, 3]), (i) => i % 2 == 0)).toBe(2);
|
|
expect(ifirst(iarray([1, 3, 5]), (i) => i % 2 == 0)).toBeNull();
|
|
});
|
|
|
|
it("finds the first item mapping to a value", () => {
|
|
let predicate = (i: number) => i % 2 == 0 ? (i * 4).toString() : null;
|
|
expect(ifirstmap(iarray([]), predicate)).toBeNull();
|
|
expect(ifirstmap(iarray([1, 2, 3]), predicate)).toBe("8");
|
|
expect(ifirstmap(iarray([1, 3, 5]), predicate)).toBeNull();
|
|
});
|
|
|
|
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]);
|
|
checkit(irange(5, 3, 2), [3, 5, 7, 9, 11]);
|
|
checkit(irange(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], true);
|
|
});
|
|
|
|
it("uses a step iterator to scan numbers", () => {
|
|
checkit(istep(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], true);
|
|
checkit(istep(3), [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], true);
|
|
checkit(istep(3, irepeat(1, 4)), [3, 4, 5, 6, 7]);
|
|
checkit(istep(8, IEMPTY), [8]);
|
|
checkit(istep(1, irange()), [1, 1, 2, 4, 7, 11, 16], true);
|
|
});
|
|
|
|
it("skips a number of values", () => {
|
|
checkit(iskip(irange(7), 3), [3, 4, 5, 6]);
|
|
checkit(iskip(irange(7), 12), []);
|
|
checkit(iskip(IEMPTY, 3), []);
|
|
});
|
|
|
|
it("gets a value at an iterator position", () => {
|
|
expect(iat(irange(), -1)).toBeNull();
|
|
expect(iat(irange(), 0)).toBe(0);
|
|
expect(iat(irange(), 8)).toBe(8);
|
|
expect(iat(irange(5), 8)).toBeNull();
|
|
expect(iat(IEMPTY, 0)).toBeNull();
|
|
});
|
|
|
|
it("chains iterator of iterators", () => {
|
|
checkit(ichainit(IEMPTY), []);
|
|
checkit(
|
|
ichainit(iarray([iarray([1, 2, 3]), iarray([]), iarray([4, 5])])),
|
|
[1, 2, 3, 4, 5],
|
|
);
|
|
});
|
|
|
|
it("chains iterators", () => {
|
|
checkit(ichain(), []);
|
|
checkit(ichain(irange(3)), [0, 1, 2]);
|
|
checkit(ichain(iarray([1, 2]), iarray([]), iarray([3, 4, 5])), [
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
]);
|
|
});
|
|
|
|
it("loops an iterator", () => {
|
|
checkit(iloop(irange(3), 2), [0, 1, 2, 0, 1, 2]);
|
|
checkit(
|
|
iloop(irange(1)),
|
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
true,
|
|
);
|
|
|
|
let onloop = mockfn();
|
|
let iterator = iloop(irange(2), 3, onloop)[Symbol.iterator]();
|
|
|
|
expect(iterator.next().value).toBe(0);
|
|
expect(onloop).toHaveBeenCalledTimes(0);
|
|
|
|
expect(iterator.next().value).toBe(1);
|
|
expect(onloop).toHaveBeenCalledTimes(0);
|
|
|
|
expect(iterator.next().value).toBe(0);
|
|
expect(onloop).toHaveBeenCalledTimes(1);
|
|
|
|
expect(iterator.next().value).toBe(1);
|
|
expect(onloop).toHaveBeenCalledTimes(1);
|
|
|
|
expect(iterator.next().value).toBe(0);
|
|
expect(onloop).toHaveBeenCalledTimes(2);
|
|
|
|
expect(iterator.next().value).toBe(1);
|
|
expect(onloop).toHaveBeenCalledTimes(2);
|
|
|
|
expect(iterator.next().value).toBeUndefined();
|
|
expect(onloop).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
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", () => {
|
|
expect(ireduce(IEMPTY, (a, b) => a + b, 2)).toBe(2);
|
|
expect(ireduce([9], (a, b) => a + b, 2)).toBe(11);
|
|
expect(ireduce([9, 1], (a, b) => a + b, 2)).toBe(12);
|
|
});
|
|
|
|
it("filters an iterator with a predicate", () => {
|
|
checkit(ifilter(IEMPTY, (i) => i % 3 == 0), []);
|
|
checkit(ifilter(irange(12), (i) => i % 3 == 0), [0, 3, 6, 9]);
|
|
});
|
|
|
|
it("maps and filters in a single pass", () => {
|
|
checkit(imapfilter(IEMPTY, (i) => i % 2 == 0 ? -i : null), []);
|
|
checkit(imapfilter(irange(8), (i) => i % 2 == 0 ? -i : null), [
|
|
-0,
|
|
-2,
|
|
-4,
|
|
-6,
|
|
]);
|
|
});
|
|
|
|
it("filters an iterator with a type guard", () => {
|
|
let result: Iterable<number> = ifiltertype(
|
|
<(number | string)[]> [1, "a", 2, "b"],
|
|
(x): x is number => typeof x == "number",
|
|
);
|
|
checkit(result, [1, 2]);
|
|
});
|
|
|
|
it("filters an iterator with a class type", () => {
|
|
let o1 = new A();
|
|
let o2 = new A();
|
|
let o3 = new B();
|
|
let result: Iterable<A> = ifilterclass([1, "a", o1, 2, o2, o3, "b"], A);
|
|
checkit(result, [o1, o2]);
|
|
});
|
|
|
|
it("combines iterators", () => {
|
|
let iterator = icombine(iarray([1, 2, 3]), iarray(["a", "b"]));
|
|
checkit(iterator, [
|
|
[1, "a"],
|
|
[1, "b"],
|
|
[2, "a"],
|
|
[2, "b"],
|
|
[3, "a"],
|
|
[3, "b"],
|
|
]);
|
|
});
|
|
|
|
it("zips iterators", () => {
|
|
checkit(izip(IEMPTY, IEMPTY), []);
|
|
checkit(izip(iarray([1, 2, 3]), iarray(["a", "b"])), [[1, "a"], [
|
|
2,
|
|
"b",
|
|
]]);
|
|
|
|
checkit(izipg(IEMPTY, IEMPTY), []);
|
|
checkit(
|
|
izipg(iarray([1, 2, 3]), iarray(["a", "b"])),
|
|
<[number | undefined, string | undefined][]> [[1, "a"], [2, "b"], [
|
|
3,
|
|
undefined,
|
|
]],
|
|
);
|
|
});
|
|
|
|
it("partitions iterators", () => {
|
|
let [it1, it2] = ipartition(IEMPTY, () => true);
|
|
checkit(it1, []);
|
|
checkit(it2, []);
|
|
|
|
[it1, it2] = ipartition(irange(5), (i) => i % 2 == 0);
|
|
checkit(it1, [0, 2, 4]);
|
|
checkit(it2, [1, 3]);
|
|
});
|
|
|
|
it("alternatively pick from several iterables", () => {
|
|
checkit(ialternate([]), []);
|
|
checkit(
|
|
ialternate([[1, 2, 3, 4], [], iarray([5, 6]), IEMPTY, iarray([7, 8, 9])]),
|
|
[1, 5, 7, 2, 6, 8, 3, 9, 4],
|
|
);
|
|
});
|
|
|
|
it("returns unique items", () => {
|
|
checkit(iunique(IEMPTY), []);
|
|
checkit(iunique(iarray([5, 3, 2, 3, 4, 5])), [5, 3, 2, 4]);
|
|
checkit(iunique(iarray([5, 3, 2, 3, 4, 5]), 4), [5, 3, 2, 4]);
|
|
expect(() => imaterialize(iunique(iarray([5, 3, 2, 3, 4, 5]), 3))).toThrow(
|
|
"Unique count limit on iterator",
|
|
);
|
|
});
|
|
|
|
it("uses ireduce for some common functions", () => {
|
|
expect(isum(IEMPTY)).toEqual(0);
|
|
expect(isum(irange(4))).toEqual(6);
|
|
|
|
expect(icat(IEMPTY)).toEqual("");
|
|
expect(icat(iarray(["a", "bc", "d"]))).toEqual("abcd");
|
|
|
|
expect(imin(IEMPTY)).toEqual(Infinity);
|
|
expect(imin(iarray([3, 8, 2, 4]))).toEqual(2);
|
|
|
|
expect(imax(IEMPTY)).toEqual(-Infinity);
|
|
expect(imax(iarray([3, 8, 2, 4]))).toEqual(8);
|
|
|
|
expect(iminBy(IEMPTY, (x) => x)).toBeUndefined();
|
|
expect(iminBy(["aaa", "b", "cc"], (x) => x.length)).toEqual("b");
|
|
|
|
expect(imaxBy(IEMPTY, (x) => x)).toBeUndefined();
|
|
expect(imaxBy(["aaa", "b", "cc"], (x) => x.length)).toEqual("aaa");
|
|
});
|
|
});
|