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, imin, ipartition, irange, irecur, ireduce, irepeat, isingle, iskip, istep, isum, iunique, izip, izipg, } from "./mod.ts"; class A {} class B {} describe("Iterators", () => { function checkit( base_iterator: Iterable, 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( []), (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 = 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 = 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 = 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); }); });