From 5947eed813814d69172aa31f05ba9e01551a8e43 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Wed, 27 Dec 2023 19:26:39 +0100 Subject: [PATCH] Prettier --- src/__tests__/builtins.test.ts | 756 +++++++++++++-------------- src/__tests__/custom.test.ts | 72 +-- src/__tests__/itertools.test.ts | 656 +++++++++++------------ src/__tests__/more-itertools.test.ts | 616 +++++++++++----------- src/builtins.ts | 158 +++--- src/custom.ts | 56 +- src/index.ts | 102 ++-- src/itertools.ts | 450 ++++++++-------- src/more-itertools.ts | 234 ++++----- src/utils.ts | 52 +- 10 files changed, 1576 insertions(+), 1576 deletions(-) diff --git a/src/__tests__/builtins.test.ts b/src/__tests__/builtins.test.ts index 5a960177..bf386b2d 100644 --- a/src/__tests__/builtins.test.ts +++ b/src/__tests__/builtins.test.ts @@ -1,423 +1,423 @@ -import { describe, it, expect } from 'vitest'; -import * as fc from 'fast-check'; +import { describe, it, expect } from "vitest"; +import * as fc from "fast-check"; import { - contains, - enumerate, - every, - filter, - iter, - map, - max, - min, - range, - reduce, - some, - sorted, - sum, - zip, - zip3, -} from '../builtins'; -import { first } from '../custom'; + contains, + enumerate, + every, + filter, + iter, + map, + max, + min, + range, + reduce, + some, + sorted, + sum, + zip, + zip3, +} from "../builtins"; +import { first } from "../custom"; const isEven = (n: number) => n % 2 === 0; function isNum(value: unknown): value is number { - return typeof value === 'number'; + return typeof value === "number"; } function predicate(): fc.Arbitrary<(a: unknown) => boolean> { - return fc.oneof( - fc.constant(() => true), - fc.constant(() => false), - fc.constant((a: unknown) => (JSON.stringify(a) ?? '').length > 10), - fc.constant((a: unknown) => (JSON.stringify(a) ?? '').length !== 0), - fc.constant((a: unknown) => typeof a === 'number'), - fc.constant((a: unknown) => typeof a === 'string'), - fc.constant((a: unknown) => typeof a === 'object'), - fc.constant((a: unknown) => typeof a === 'function'), - fc.constant((a: unknown) => Array.isArray(a)) - ); + return fc.oneof( + fc.constant(() => true), + fc.constant(() => false), + fc.constant((a: unknown) => (JSON.stringify(a) ?? "").length > 10), + fc.constant((a: unknown) => (JSON.stringify(a) ?? "").length !== 0), + fc.constant((a: unknown) => typeof a === "number"), + fc.constant((a: unknown) => typeof a === "string"), + fc.constant((a: unknown) => typeof a === "object"), + fc.constant((a: unknown) => typeof a === "function"), + fc.constant((a: unknown) => Array.isArray(a)), + ); } -describe('every', () => { - it('every of empty list is true', () => { - expect(every([])).toBe(true); - }); +describe("every", () => { + it("every of empty list is true", () => { + expect(every([])).toBe(true); + }); - it('every is true if every elements are truthy', () => { - expect(every([1])).toBe(true); - expect(every([1, 2, 3])).toBe(true); - }); + it("every is true if every elements are truthy", () => { + expect(every([1])).toBe(true); + expect(every([1, 2, 3])).toBe(true); + }); - it('every is false if some elements are not truthy', () => { - expect(every([0, 1])).toBe(false); - expect(every([1, 2, undefined, 3, 4])).toBe(false); - }); + it("every is false if some elements are not truthy", () => { + expect(every([0, 1])).toBe(false); + expect(every([1, 2, undefined, 3, 4])).toBe(false); + }); }); -describe('some', () => { - it('some of empty list is false', () => { - expect(some([])).toBe(false); - }); - - it('some is true if some elements are truthy', () => { - expect(some([1, 2, 3])).toBe(true); - expect(some([0, 1])).toBe(true); - expect(some([1, 0])).toBe(true); - expect(some([0, undefined, NaN, 1])).toBe(true); - }); - - it('some is false if no elements are truthy', () => { - expect(some([0, null, NaN, undefined])).toBe(false); - }); +describe("some", () => { + it("some of empty list is false", () => { + expect(some([])).toBe(false); + }); + + it("some is true if some elements are truthy", () => { + expect(some([1, 2, 3])).toBe(true); + expect(some([0, 1])).toBe(true); + expect(some([1, 0])).toBe(true); + expect(some([0, undefined, NaN, 1])).toBe(true); + }); + + it("some is false if no elements are truthy", () => { + expect(some([0, null, NaN, undefined])).toBe(false); + }); }); -describe('every vs some', () => { - it('every is always true with empty list', () => { - fc.assert( - fc.property( - predicate(), - - (pred) => { - expect(every([], pred)).toBe(true); - } - ) - ); - }); - - it('some is always false with empty list', () => { - fc.assert( - fc.property( - predicate(), - - (pred) => { - expect(some([], pred)).toBe(false); - } - ) - ); - }); - - it('every and some complete each other', () => { - fc.assert( - fc.property( - fc.array(fc.anything()), - predicate(), - - (arr, pred) => { - const inverse = (x: unknown) => !pred(x); - expect(every(arr, pred)).toEqual(!some(arr, inverse)); - expect(some(arr, pred)).toEqual(!every(arr, inverse)); - } - ) - ); - }); +describe("every vs some", () => { + it("every is always true with empty list", () => { + fc.assert( + fc.property( + predicate(), + + (pred) => { + expect(every([], pred)).toBe(true); + }, + ), + ); + }); + + it("some is always false with empty list", () => { + fc.assert( + fc.property( + predicate(), + + (pred) => { + expect(some([], pred)).toBe(false); + }, + ), + ); + }); + + it("every and some complete each other", () => { + fc.assert( + fc.property( + fc.array(fc.anything()), + predicate(), + + (arr, pred) => { + const inverse = (x: unknown) => !pred(x); + expect(every(arr, pred)).toEqual(!some(arr, inverse)); + expect(some(arr, pred)).toEqual(!every(arr, inverse)); + }, + ), + ); + }); }); -describe('contains', () => { - it('contains of empty list is false', () => { - expect(contains([], 0)).toBe(false); - expect(contains([], 1)).toBe(false); - expect(contains([], null)).toBe(false); - expect(contains([], undefined)).toBe(false); - }); - - it('contains is true iff iterable contains the given exact value', () => { - expect(contains([1], 1)).toBe(true); - expect(contains([1], 2)).toBe(false); - expect(contains([1, 2, 3], 1)).toBe(true); - expect(contains([1, 2, 3], 2)).toBe(true); - expect(contains([1, 2, 3], 3)).toBe(true); - expect(contains([1, 2, 3], 4)).toBe(false); - }); - - it('contains does not work for elements with identity equality', () => { - expect(contains([{}], {})).toBe(false); - expect(contains([{ x: 123 }], { x: 123 })).toBe(false); - expect(contains([[1, 2, 3]], [1, 2, 3])).toBe(false); - }); +describe("contains", () => { + it("contains of empty list is false", () => { + expect(contains([], 0)).toBe(false); + expect(contains([], 1)).toBe(false); + expect(contains([], null)).toBe(false); + expect(contains([], undefined)).toBe(false); + }); + + it("contains is true iff iterable contains the given exact value", () => { + expect(contains([1], 1)).toBe(true); + expect(contains([1], 2)).toBe(false); + expect(contains([1, 2, 3], 1)).toBe(true); + expect(contains([1, 2, 3], 2)).toBe(true); + expect(contains([1, 2, 3], 3)).toBe(true); + expect(contains([1, 2, 3], 4)).toBe(false); + }); + + it("contains does not work for elements with identity equality", () => { + expect(contains([{}], {})).toBe(false); + expect(contains([{ x: 123 }], { x: 123 })).toBe(false); + expect(contains([[1, 2, 3]], [1, 2, 3])).toBe(false); + }); }); -describe('enumerate', () => { - it('enumerate empty list', () => { - expect(Array.from(enumerate([]))).toEqual([]); - }); - - it('enumerate attaches indexes', () => { - // We'll have to wrap it in a take() call to avoid infinite-length arrays :) - expect(Array.from(enumerate(['x']))).toEqual([[0, 'x']]); - expect(Array.from(enumerate(['even', 'odd']))).toEqual([ - [0, 'even'], - [1, 'odd'], - ]); - }); - - it('enumerate from 3 up', () => { - expect(Array.from(enumerate('abc', 3))).toEqual([ - [3, 'a'], - [4, 'b'], - [5, 'c'], - ]); - }); +describe("enumerate", () => { + it("enumerate empty list", () => { + expect(Array.from(enumerate([]))).toEqual([]); + }); + + it("enumerate attaches indexes", () => { + // We'll have to wrap it in a take() call to avoid infinite-length arrays :) + expect(Array.from(enumerate(["x"]))).toEqual([[0, "x"]]); + expect(Array.from(enumerate(["even", "odd"]))).toEqual([ + [0, "even"], + [1, "odd"], + ]); + }); + + it("enumerate from 3 up", () => { + expect(Array.from(enumerate("abc", 3))).toEqual([ + [3, "a"], + [4, "b"], + [5, "c"], + ]); + }); }); -describe('filter', () => { - it('filters empty list', () => { - expect(filter([], isEven)).toEqual([]); - }); +describe("filter", () => { + it("filters empty list", () => { + expect(filter([], isEven)).toEqual([]); + }); - it('ifilter works like Array.filter, but lazy', () => { - expect(filter([0, 1, 2, 3], isEven)).toEqual([0, 2]); - }); + it("ifilter works like Array.filter, but lazy", () => { + expect(filter([0, 1, 2, 3], isEven)).toEqual([0, 2]); + }); - it('filter retains rich type info', () => { - const filtered = filter([3, 'hi', null, -7], isNum); - expect(filtered).toEqual([3, -7]); - // ^^^^^^^^ number[] - }); + it("filter retains rich type info", () => { + const filtered = filter([3, "hi", null, -7], isNum); + expect(filtered).toEqual([3, -7]); + // ^^^^^^^^ number[] + }); }); -describe('iter', () => { - it('iter makes some iterable a one-time iterable', () => { - expect( - Array.from( - iter( - new Map([ - [1, 'x'], - [2, 'y'], - [3, 'z'], - ]) - ) - ) - ).toEqual([ - [1, 'x'], - [2, 'y'], - [3, 'z'], - ]); - expect(Array.from(iter([1, 2, 3]))).toEqual([1, 2, 3]); - expect(Array.from(iter(new Set([1, 2, 3])))).toEqual([1, 2, 3]); - }); - - it('iter results can be consumed only once', () => { - const it = iter([1, 2, 3]); - expect(it.next()).toEqual({ value: 1, done: false }); - expect(it.next()).toEqual({ value: 2, done: false }); - expect(it.next()).toEqual({ value: 3, done: false }); - expect(it.next()).toEqual({ value: undefined, done: true }); - - // Keeps returning "done" when exhausted... - expect(it.next()).toEqual({ value: undefined, done: true }); - expect(it.next()).toEqual({ value: undefined, done: true }); - // ... - }); - - it('iter results can be consumed in pieces', () => { - const it = iter([1, 2, 3, 4, 5]); - expect(first(it)).toBe(1); - expect(first(it)).toBe(2); - expect(first(it)).toBe(3); - expect(Array.from(it)).toEqual([4, 5]); - - // Keeps returning "[]" when exhausted... - expect(Array.from(it)).toEqual([]); - expect(Array.from(it)).toEqual([]); - // ... - }); - - it("wrapping iter()s has no effect on the iterator's state", () => { - const originalIter = iter([1, 2, 3, 4, 5]); - expect(first(originalIter)).toBe(1); - - const wrappedIter = iter(iter(iter(originalIter))); - expect(first(wrappedIter)).toBe(2); - expect(first(iter(wrappedIter))).toBe(3); - expect(Array.from(iter(originalIter))).toEqual([4, 5]); - - // Keeps returning "[]" when exhausted... - expect(Array.from(originalIter)).toEqual([]); - expect(Array.from(wrappedIter)).toEqual([]); - // ... - }); +describe("iter", () => { + it("iter makes some iterable a one-time iterable", () => { + expect( + Array.from( + iter( + new Map([ + [1, "x"], + [2, "y"], + [3, "z"], + ]), + ), + ), + ).toEqual([ + [1, "x"], + [2, "y"], + [3, "z"], + ]); + expect(Array.from(iter([1, 2, 3]))).toEqual([1, 2, 3]); + expect(Array.from(iter(new Set([1, 2, 3])))).toEqual([1, 2, 3]); + }); + + it("iter results can be consumed only once", () => { + const it = iter([1, 2, 3]); + expect(it.next()).toEqual({ value: 1, done: false }); + expect(it.next()).toEqual({ value: 2, done: false }); + expect(it.next()).toEqual({ value: 3, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + + // Keeps returning "done" when exhausted... + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + // ... + }); + + it("iter results can be consumed in pieces", () => { + const it = iter([1, 2, 3, 4, 5]); + expect(first(it)).toBe(1); + expect(first(it)).toBe(2); + expect(first(it)).toBe(3); + expect(Array.from(it)).toEqual([4, 5]); + + // Keeps returning "[]" when exhausted... + expect(Array.from(it)).toEqual([]); + expect(Array.from(it)).toEqual([]); + // ... + }); + + it("wrapping iter()s has no effect on the iterator's state", () => { + const originalIter = iter([1, 2, 3, 4, 5]); + expect(first(originalIter)).toBe(1); + + const wrappedIter = iter(iter(iter(originalIter))); + expect(first(wrappedIter)).toBe(2); + expect(first(iter(wrappedIter))).toBe(3); + expect(Array.from(iter(originalIter))).toEqual([4, 5]); + + // Keeps returning "[]" when exhausted... + expect(Array.from(originalIter)).toEqual([]); + expect(Array.from(wrappedIter)).toEqual([]); + // ... + }); }); -describe('map', () => { - it('map on empty iterable', () => { - expect(map([], (x) => x)).toEqual([]); - }); +describe("map", () => { + it("map on empty iterable", () => { + expect(map([], (x) => x)).toEqual([]); + }); - it('imap works like Array.map, but lazy', () => { - expect(map([1, 2, 3], (x) => x)).toEqual([1, 2, 3]); - expect(map([1, 2, 3], (x) => 2 * x)).toEqual([2, 4, 6]); - expect(map([1, 2, 3], (x) => x.toString())).toEqual(['1', '2', '3']); - }); + it("imap works like Array.map, but lazy", () => { + expect(map([1, 2, 3], (x) => x)).toEqual([1, 2, 3]); + expect(map([1, 2, 3], (x) => 2 * x)).toEqual([2, 4, 6]); + expect(map([1, 2, 3], (x) => x.toString())).toEqual(["1", "2", "3"]); + }); }); -describe('max', () => { - it("can't take max of empty list", () => { - expect(max([])).toBeUndefined(); - }); - - it('max of single-item array', () => { - expect(max([1])).toEqual(1); - expect(max([2])).toEqual(2); - expect(max([5])).toEqual(5); - }); - - it('max of multi-item array', () => { - expect(max([1, 2, 3])).toEqual(3); - expect(max([2, 2, 2, 2])).toEqual(2); - expect(max([5, 4, 3, 2, 1])).toEqual(5); - expect(max([-3, -2, -1])).toEqual(-1); - }); - - it('max of multi-item array with key function', () => { - expect(max([{ n: 2 }, { n: 3 }, { n: 1 }], (o) => o.n)).toEqual({ n: 3 }); - }); +describe("max", () => { + it("can't take max of empty list", () => { + expect(max([])).toBeUndefined(); + }); + + it("max of single-item array", () => { + expect(max([1])).toEqual(1); + expect(max([2])).toEqual(2); + expect(max([5])).toEqual(5); + }); + + it("max of multi-item array", () => { + expect(max([1, 2, 3])).toEqual(3); + expect(max([2, 2, 2, 2])).toEqual(2); + expect(max([5, 4, 3, 2, 1])).toEqual(5); + expect(max([-3, -2, -1])).toEqual(-1); + }); + + it("max of multi-item array with key function", () => { + expect(max([{ n: 2 }, { n: 3 }, { n: 1 }], (o) => o.n)).toEqual({ n: 3 }); + }); }); -describe('min', () => { - it("can't take min of empty list", () => { - expect(min([])).toBeUndefined(); - }); - - it('min of single-item array', () => { - expect(min([1])).toEqual(1); - expect(min([2])).toEqual(2); - expect(min([5])).toEqual(5); - }); - - it('min of multi-item array', () => { - expect(min([1, 2, 3])).toEqual(1); - expect(min([2, 2, 2, 2])).toEqual(2); - expect(min([5, 4, 3, 2, 1])).toEqual(1); - expect(min([-3, -2, -1])).toEqual(-3); - }); - - it('min of multi-item array with key function', () => { - expect(min([{ n: 2 }, { n: 3 }, { n: 1 }], (o) => o.n)).toEqual({ n: 1 }); - }); +describe("min", () => { + it("can't take min of empty list", () => { + expect(min([])).toBeUndefined(); + }); + + it("min of single-item array", () => { + expect(min([1])).toEqual(1); + expect(min([2])).toEqual(2); + expect(min([5])).toEqual(5); + }); + + it("min of multi-item array", () => { + expect(min([1, 2, 3])).toEqual(1); + expect(min([2, 2, 2, 2])).toEqual(2); + expect(min([5, 4, 3, 2, 1])).toEqual(1); + expect(min([-3, -2, -1])).toEqual(-3); + }); + + it("min of multi-item array with key function", () => { + expect(min([{ n: 2 }, { n: 3 }, { n: 1 }], (o) => o.n)).toEqual({ n: 1 }); + }); }); -describe('range', () => { - it('range with end', () => { - expect(Array.from(range(0))).toEqual([]); - expect(Array.from(range(1))).toEqual([0]); - expect(Array.from(range(2))).toEqual([0, 1]); - expect(Array.from(range(5))).toEqual([0, 1, 2, 3, 4]); - expect(Array.from(range(-1))).toEqual([]); - }); - - it('range with start and end', () => { - expect(Array.from(range(3, 5))).toEqual([3, 4]); - expect(Array.from(range(4, 7))).toEqual([4, 5, 6]); - - // If end < start, then range is empty - expect(Array.from(range(5, 1))).toEqual([]); - }); - - it('range with start, end, and step', () => { - expect(Array.from(range(3, 9, 3))).toEqual([3, 6]); - expect(Array.from(range(3, 10, 3))).toEqual([3, 6, 9]); - expect(Array.from(range(5, 1, -1))).toEqual([5, 4, 3, 2]); - expect(Array.from(range(5, -3, -2))).toEqual([5, 3, 1, -1]); - }); +describe("range", () => { + it("range with end", () => { + expect(Array.from(range(0))).toEqual([]); + expect(Array.from(range(1))).toEqual([0]); + expect(Array.from(range(2))).toEqual([0, 1]); + expect(Array.from(range(5))).toEqual([0, 1, 2, 3, 4]); + expect(Array.from(range(-1))).toEqual([]); + }); + + it("range with start and end", () => { + expect(Array.from(range(3, 5))).toEqual([3, 4]); + expect(Array.from(range(4, 7))).toEqual([4, 5, 6]); + + // If end < start, then range is empty + expect(Array.from(range(5, 1))).toEqual([]); + }); + + it("range with start, end, and step", () => { + expect(Array.from(range(3, 9, 3))).toEqual([3, 6]); + expect(Array.from(range(3, 10, 3))).toEqual([3, 6, 9]); + expect(Array.from(range(5, 1, -1))).toEqual([5, 4, 3, 2]); + expect(Array.from(range(5, -3, -2))).toEqual([5, 3, 1, -1]); + }); }); -describe('reduce', () => { - const adder = (x: number, y: number) => x + y; - const firstOne = (x: unknown) => x; - - it('reduce without initializer', () => { - expect(reduce([], adder)).toBeUndefined(); - expect(reduce([1], adder)).toEqual(1); - expect(reduce([1, 2], adder)).toEqual(3); - }); - - it('reduce on empty list returns start value', () => { - expect(reduce([], adder, 0)).toEqual(0); - expect(reduce([], adder, 13)).toEqual(13); - }); - - it('reduce on list with only one item', () => { - expect(reduce([5], adder, 0)).toEqual(5); - expect(reduce([5], adder, 13)).toEqual(18); - }); - - it('reduce on list with multiple items', () => { - expect(reduce([1, 2, 3, 4], adder, 0)).toEqual(10); - expect(reduce([1, 2, 3, 4, 5], adder, 13)).toEqual(28); - }); - - it('reduce on list with multiple items (no initializer)', () => { - expect(reduce([13, 2, 3, 4], firstOne)).toEqual(13); - expect(reduce([undefined, null, 1, 2, 3, 4], firstOne)).toEqual(undefined); - }); +describe("reduce", () => { + const adder = (x: number, y: number) => x + y; + const firstOne = (x: unknown) => x; + + it("reduce without initializer", () => { + expect(reduce([], adder)).toBeUndefined(); + expect(reduce([1], adder)).toEqual(1); + expect(reduce([1, 2], adder)).toEqual(3); + }); + + it("reduce on empty list returns start value", () => { + expect(reduce([], adder, 0)).toEqual(0); + expect(reduce([], adder, 13)).toEqual(13); + }); + + it("reduce on list with only one item", () => { + expect(reduce([5], adder, 0)).toEqual(5); + expect(reduce([5], adder, 13)).toEqual(18); + }); + + it("reduce on list with multiple items", () => { + expect(reduce([1, 2, 3, 4], adder, 0)).toEqual(10); + expect(reduce([1, 2, 3, 4, 5], adder, 13)).toEqual(28); + }); + + it("reduce on list with multiple items (no initializer)", () => { + expect(reduce([13, 2, 3, 4], firstOne)).toEqual(13); + expect(reduce([undefined, null, 1, 2, 3, 4], firstOne)).toEqual(undefined); + }); }); -describe('sorted', () => { - it('sorted w/ empty list', () => { - expect(sorted([])).toEqual([]); - }); - - it('sorted values', () => { - expect(sorted([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]); - expect(sorted([2, 1, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]); - expect(sorted([5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5]); - - // Explicitly test numeral ordering... in plain JS the following is true: - // [4, 44, 100, 80, 9].sort() ~~> [100, 4, 44, 80, 9] - expect(sorted([4, 44, 100, 80, 9])).toEqual([4, 9, 44, 80, 100]); - expect(sorted(['4', '44', '100', '80', '44', '9'])).toEqual(['100', '4', '44', '44', '80', '9']); - expect(sorted([false, true, true, false])).toEqual([false, false, true, true]); - }); - - it('sorted does not modify input', () => { - const values = [4, 0, -3, 7, 1]; - expect(sorted(values)).toEqual([-3, 0, 1, 4, 7]); - expect(values).toEqual([4, 0, -3, 7, 1]); - }); - - it('sorted in reverse', () => { - expect(sorted([2, 1, 3, 4, 5], undefined, true)).toEqual([5, 4, 3, 2, 1]); - }); +describe("sorted", () => { + it("sorted w/ empty list", () => { + expect(sorted([])).toEqual([]); + }); + + it("sorted values", () => { + expect(sorted([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]); + expect(sorted([2, 1, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]); + expect(sorted([5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5]); + + // Explicitly test numeral ordering... in plain JS the following is true: + // [4, 44, 100, 80, 9].sort() ~~> [100, 4, 44, 80, 9] + expect(sorted([4, 44, 100, 80, 9])).toEqual([4, 9, 44, 80, 100]); + expect(sorted(["4", "44", "100", "80", "44", "9"])).toEqual(["100", "4", "44", "44", "80", "9"]); + expect(sorted([false, true, true, false])).toEqual([false, false, true, true]); + }); + + it("sorted does not modify input", () => { + const values = [4, 0, -3, 7, 1]; + expect(sorted(values)).toEqual([-3, 0, 1, 4, 7]); + expect(values).toEqual([4, 0, -3, 7, 1]); + }); + + it("sorted in reverse", () => { + expect(sorted([2, 1, 3, 4, 5], undefined, true)).toEqual([5, 4, 3, 2, 1]); + }); }); -describe('sum', () => { - it('sum w/ empty iterable', () => { - expect(sum([])).toEqual(0); - }); +describe("sum", () => { + it("sum w/ empty iterable", () => { + expect(sum([])).toEqual(0); + }); - it('sum works', () => { - expect(sum([1, 2, 3, 4, 5, 6])).toEqual(21); - expect(sum([-3, -2, -1, 0, 1, 2, 3])).toEqual(0); - expect(sum([0.1, 0.2, 0.3])).toBeCloseTo(0.6); - }); + it("sum works", () => { + expect(sum([1, 2, 3, 4, 5, 6])).toEqual(21); + expect(sum([-3, -2, -1, 0, 1, 2, 3])).toEqual(0); + expect(sum([0.1, 0.2, 0.3])).toBeCloseTo(0.6); + }); }); -describe('zip', () => { - it('zip with empty iterable', () => { - expect(zip([], [])).toEqual([]); - expect(zip('abc', [])).toEqual([]); - }); - - it('izip with two iterables', () => { - expect(zip('abc', 'ABC')).toEqual([ - ['a', 'A'], - ['b', 'B'], - ['c', 'C'], - ]); - }); - - it('izip with three iterables', () => { - expect(zip3('abc', 'ABC', [5, 4, 3])).toEqual([ - ['a', 'A', 5], - ['b', 'B', 4], - ['c', 'C', 3], - ]); - }); - - it('izip different input lengths', () => { - // Shortest lengthed input determines result length - expect(zip('a', 'ABC')).toEqual([['a', 'A']]); - expect(zip3('', 'ABCD', 'PQR')).toEqual([]); - }); +describe("zip", () => { + it("zip with empty iterable", () => { + expect(zip([], [])).toEqual([]); + expect(zip("abc", [])).toEqual([]); + }); + + it("izip with two iterables", () => { + expect(zip("abc", "ABC")).toEqual([ + ["a", "A"], + ["b", "B"], + ["c", "C"], + ]); + }); + + it("izip with three iterables", () => { + expect(zip3("abc", "ABC", [5, 4, 3])).toEqual([ + ["a", "A", 5], + ["b", "B", 4], + ["c", "C", 3], + ]); + }); + + it("izip different input lengths", () => { + // Shortest lengthed input determines result length + expect(zip("a", "ABC")).toEqual([["a", "A"]]); + expect(zip3("", "ABCD", "PQR")).toEqual([]); + }); }); diff --git a/src/__tests__/custom.test.ts b/src/__tests__/custom.test.ts index b2914e43..ae6b826c 100644 --- a/src/__tests__/custom.test.ts +++ b/src/__tests__/custom.test.ts @@ -1,46 +1,46 @@ -import { describe, it, expect } from 'vitest'; -import { compact, compactObject, flatmap } from '../custom'; -import { repeat } from '../itertools'; +import { describe, it, expect } from "vitest"; +import { compact, compactObject, flatmap } from "../custom"; +import { repeat } from "../itertools"; -describe('compact', () => { - it('compact w/ empty list', () => { - expect(compact([])).toEqual([]); - }); +describe("compact", () => { + it("compact w/ empty list", () => { + expect(compact([])).toEqual([]); + }); - it('icompact removes nullish values', () => { - expect(compact('abc')).toEqual(['a', 'b', 'c']); - expect(compact(['x', undefined])).toEqual(['x']); - expect(compact([0, null, undefined, NaN, Infinity])).toEqual([0, NaN, Infinity]); - }); + it("icompact removes nullish values", () => { + expect(compact("abc")).toEqual(["a", "b", "c"]); + expect(compact(["x", undefined])).toEqual(["x"]); + expect(compact([0, null, undefined, NaN, Infinity])).toEqual([0, NaN, Infinity]); + }); }); -describe('compactObject', () => { - it('compactObject w/ empty object', () => { - expect(compactObject({})).toEqual({}); - }); +describe("compactObject", () => { + it("compactObject w/ empty object", () => { + expect(compactObject({})).toEqual({}); + }); - it('compactObject removes nullish values', () => { - expect(compactObject({ a: 1, b: 'foo', c: 0, d: null })).toEqual({ a: 1, b: 'foo', c: 0 }); - expect(compactObject({ a: undefined, b: false, c: 0, d: null })).toEqual({ b: false, c: 0 }); - }); + it("compactObject removes nullish values", () => { + expect(compactObject({ a: 1, b: "foo", c: 0, d: null })).toEqual({ a: 1, b: "foo", c: 0 }); + expect(compactObject({ a: undefined, b: false, c: 0, d: null })).toEqual({ b: false, c: 0 }); + }); }); -describe('flatmap', () => { - it('flatmap w/ empty list', () => { - expect(Array.from(flatmap([], (x) => [x]))).toEqual([]); - }); +describe("flatmap", () => { + it("flatmap w/ empty list", () => { + expect(Array.from(flatmap([], (x) => [x]))).toEqual([]); + }); - it('flatmap works', () => { - const dupeEvens = (x: number) => (x % 2 === 0 ? [x, x] : [x]); - const triple = (x: T) => [x, x, x]; - const nothin = () => []; - expect(Array.from(flatmap([1, 2, 3, 4, 5], dupeEvens))).toEqual([1, 2, 2, 3, 4, 4, 5]); - expect(Array.from(flatmap(['hi', 'ha'], triple))).toEqual(['hi', 'hi', 'hi', 'ha', 'ha', 'ha']); - expect(Array.from(flatmap(['hi', 'ha'], nothin))).toEqual([]); - }); + it("flatmap works", () => { + const dupeEvens = (x: number) => (x % 2 === 0 ? [x, x] : [x]); + const triple = (x: T) => [x, x, x]; + const nothin = () => []; + expect(Array.from(flatmap([1, 2, 3, 4, 5], dupeEvens))).toEqual([1, 2, 2, 3, 4, 4, 5]); + expect(Array.from(flatmap(["hi", "ha"], triple))).toEqual(["hi", "hi", "hi", "ha", "ha", "ha"]); + expect(Array.from(flatmap(["hi", "ha"], nothin))).toEqual([]); + }); - it('flatmap example', () => { - const repeatN = (n: number) => repeat(n, n); - expect(Array.from(flatmap([0, 1, 2, 3, 4], repeatN))).toEqual([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]); - }); + it("flatmap example", () => { + const repeatN = (n: number) => repeat(n, n); + expect(Array.from(flatmap([0, 1, 2, 3, 4], repeatN))).toEqual([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]); + }); }); diff --git a/src/__tests__/itertools.test.ts b/src/__tests__/itertools.test.ts index 64d82fe2..7b753a65 100644 --- a/src/__tests__/itertools.test.ts +++ b/src/__tests__/itertools.test.ts @@ -1,388 +1,388 @@ -import { describe, it, expect } from 'vitest'; -import { all, range } from '../builtins'; +import { describe, it, expect } from "vitest"; +import { all, range } from "../builtins"; import { - chain, - compress, - count, - cycle, - dropwhile, - groupby, - ifilter, - imap, - islice, - permutations, - repeat, - takewhile, - zipLongest, - zipLongest3, - zipMany, -} from '../itertools'; -import { take } from '../more-itertools'; + chain, + compress, + count, + cycle, + dropwhile, + groupby, + ifilter, + imap, + islice, + permutations, + repeat, + takewhile, + zipLongest, + zipLongest3, + zipMany, +} from "../itertools"; +import { take } from "../more-itertools"; const isEven = (x: number) => x % 2 === 0; const isPositive = (x: number) => x >= 0; function isNum(value: unknown): value is number { - return typeof value === 'number'; + return typeof value === "number"; } -describe('chain', () => { - it('chains empty iterables', () => { - expect(Array.from(chain([], []))).toEqual([]); - }); - - it('chains iterables together', () => { - expect(Array.from(chain(['foo'], []))).toEqual(['foo']); - expect(Array.from(chain([], ['bar']))).toEqual(['bar']); - expect(Array.from(chain([], ['bar'], ['qux']))).toEqual(['bar', 'qux']); - expect(Array.from(chain(['foo', 'bar'], ['qux']))).toEqual(['foo', 'bar', 'qux']); - }); +describe("chain", () => { + it("chains empty iterables", () => { + expect(Array.from(chain([], []))).toEqual([]); + }); + + it("chains iterables together", () => { + expect(Array.from(chain(["foo"], []))).toEqual(["foo"]); + expect(Array.from(chain([], ["bar"]))).toEqual(["bar"]); + expect(Array.from(chain([], ["bar"], ["qux"]))).toEqual(["bar", "qux"]); + expect(Array.from(chain(["foo", "bar"], ["qux"]))).toEqual(["foo", "bar", "qux"]); + }); }); -describe('compress', () => { - it('compress on empty list', () => { - expect(compress([], [])).toEqual([]); - }); - - it('compress removes selected items', () => { - expect(compress('abc', [])).toEqual([]); - expect(compress('abc', [true])).toEqual(['a']); - expect(compress('abc', [false, false, false])).toEqual([]); - expect(compress('abc', [true, false, true])).toEqual(['a', 'c']); - }); +describe("compress", () => { + it("compress on empty list", () => { + expect(compress([], [])).toEqual([]); + }); + + it("compress removes selected items", () => { + expect(compress("abc", [])).toEqual([]); + expect(compress("abc", [true])).toEqual(["a"]); + expect(compress("abc", [false, false, false])).toEqual([]); + expect(compress("abc", [true, false, true])).toEqual(["a", "c"]); + }); }); -describe('count', () => { - it('default counter', () => { - expect(take(6, count())).toEqual([0, 1, 2, 3, 4, 5]); - }); +describe("count", () => { + it("default counter", () => { + expect(take(6, count())).toEqual([0, 1, 2, 3, 4, 5]); + }); - it('counter from different start value', () => { - expect(take(6, count(1))).toEqual([1, 2, 3, 4, 5, 6]); - expect(take(6, count(-3))).toEqual([-3, -2, -1, 0, 1, 2]); - }); + it("counter from different start value", () => { + expect(take(6, count(1))).toEqual([1, 2, 3, 4, 5, 6]); + expect(take(6, count(-3))).toEqual([-3, -2, -1, 0, 1, 2]); + }); - it('counter backwards', () => { - expect(take(6, count(4, -1))).toEqual([4, 3, 2, 1, 0, -1]); - expect(take(5, count(-3, -2))).toEqual([-3, -5, -7, -9, -11]); - }); + it("counter backwards", () => { + expect(take(6, count(4, -1))).toEqual([4, 3, 2, 1, 0, -1]); + expect(take(5, count(-3, -2))).toEqual([-3, -5, -7, -9, -11]); + }); }); -describe('cycle', () => { - it('cycle with empty list', () => { - expect(Array.from(cycle([]))).toEqual([]); - }); - - it('cycles', () => { - // We'll have to wrap it in a take() call to avoid infinite-length arrays :) - expect(take(3, cycle(['x']))).toEqual(['x', 'x', 'x']); - expect(take(5, cycle(['even', 'odd']))).toEqual(['even', 'odd', 'even', 'odd', 'even']); - }); - - it('cycles with infinite iterable', () => { - // Function `cycle` should properly work with infinite iterators (`repeat('x')` in this case) - expect(take(3, cycle(repeat('x')))).toEqual(['x', 'x', 'x']); - }); +describe("cycle", () => { + it("cycle with empty list", () => { + expect(Array.from(cycle([]))).toEqual([]); + }); + + it("cycles", () => { + // We'll have to wrap it in a take() call to avoid infinite-length arrays :) + expect(take(3, cycle(["x"]))).toEqual(["x", "x", "x"]); + expect(take(5, cycle(["even", "odd"]))).toEqual(["even", "odd", "even", "odd", "even"]); + }); + + it("cycles with infinite iterable", () => { + // Function `cycle` should properly work with infinite iterators (`repeat('x')` in this case) + expect(take(3, cycle(repeat("x")))).toEqual(["x", "x", "x"]); + }); }); -describe('dropwhile', () => { - it('dropwhile on empty list', () => { - expect(Array.from(dropwhile([], isEven))).toEqual([]); - expect(Array.from(dropwhile([], isPositive))).toEqual([]); - }); +describe("dropwhile", () => { + it("dropwhile on empty list", () => { + expect(Array.from(dropwhile([], isEven))).toEqual([]); + expect(Array.from(dropwhile([], isPositive))).toEqual([]); + }); - it('dropwhile on list', () => { - expect(Array.from(dropwhile([1], isEven))).toEqual([1]); - expect(Array.from(dropwhile([1], isPositive))).toEqual([]); + it("dropwhile on list", () => { + expect(Array.from(dropwhile([1], isEven))).toEqual([1]); + expect(Array.from(dropwhile([1], isPositive))).toEqual([]); - expect(Array.from(dropwhile([-1, 0, 1], isEven))).toEqual([-1, 0, 1]); - expect(Array.from(dropwhile([4, -1, 0, 1], isEven))).toEqual([-1, 0, 1]); - expect(Array.from(dropwhile([-1, 0, 1], isPositive))).toEqual([-1, 0, 1]); - expect(Array.from(dropwhile([7, -1, 0, 1], isPositive))).toEqual([-1, 0, 1]); + expect(Array.from(dropwhile([-1, 0, 1], isEven))).toEqual([-1, 0, 1]); + expect(Array.from(dropwhile([4, -1, 0, 1], isEven))).toEqual([-1, 0, 1]); + expect(Array.from(dropwhile([-1, 0, 1], isPositive))).toEqual([-1, 0, 1]); + expect(Array.from(dropwhile([7, -1, 0, 1], isPositive))).toEqual([-1, 0, 1]); - expect(Array.from(dropwhile([0, 2, 4, 6, 7, 8, 10], isEven))).toEqual([7, 8, 10]); - expect(Array.from(dropwhile([0, 1, 2, -2, 3, 4, 5, 6, 7], isPositive))).toEqual([-2, 3, 4, 5, 6, 7]); - }); + expect(Array.from(dropwhile([0, 2, 4, 6, 7, 8, 10], isEven))).toEqual([7, 8, 10]); + expect(Array.from(dropwhile([0, 1, 2, -2, 3, 4, 5, 6, 7], isPositive))).toEqual([-2, 3, 4, 5, 6, 7]); + }); }); -describe('groupby', () => { - const countValues = (grouped: Iterable<[K, Iterable]>) => - Array.from(imap(grouped, ([k, v]) => [k, Array.from(v).length])); - - it('groupby with empty list', () => { - expect(Array.from(groupby([]))).toEqual([]); - }); - - it('groups elements', () => { - expect(countValues(groupby('aaabbbbcddddaa'))).toEqual([ - ['a', 3], - ['b', 4], - ['c', 1], - ['d', 4], - ['a', 2], - ]); - }); - - it('groups element with key function', () => { - expect(countValues(groupby('aaaAbb'))).toEqual([ - ['a', 3], - ['A', 1], - ['b', 2], - ]); - expect(countValues(groupby('aaaAbb', (val) => val.toUpperCase()))).toEqual([ - ['A', 4], - ['B', 2], - ]); - }); - - it('handles not using the inner iterator', () => { - expect(Array.from(imap(groupby('aaabbbbcddddaa'), ([k]) => k))).toEqual(['a', 'b', 'c', 'd', 'a']); - }); - - it('handles using the inner iterator after the iteration has advanced', () => { - expect(Array.from(groupby('aaabb')).map(([, v]) => Array.from(v))).toEqual([[], []]); - const it = groupby('aaabbccc'); - // Flow does not like that I use next on an iterable (it is actually - // a generator but the Generator type is awful. - - const [, v1] = it.next().value!; - const [, v2] = it.next().value!; - const [, v3] = it.next().value!; - - expect([...v1]).toEqual([]); - expect([...v2]).toEqual([]); - expect(v3.next().value!).toEqual('c'); - Array.from(it); // exhaust the groupby iterator - expect([...v3]).toEqual([]); - }); +describe("groupby", () => { + const countValues = (grouped: Iterable<[K, Iterable]>) => + Array.from(imap(grouped, ([k, v]) => [k, Array.from(v).length])); + + it("groupby with empty list", () => { + expect(Array.from(groupby([]))).toEqual([]); + }); + + it("groups elements", () => { + expect(countValues(groupby("aaabbbbcddddaa"))).toEqual([ + ["a", 3], + ["b", 4], + ["c", 1], + ["d", 4], + ["a", 2], + ]); + }); + + it("groups element with key function", () => { + expect(countValues(groupby("aaaAbb"))).toEqual([ + ["a", 3], + ["A", 1], + ["b", 2], + ]); + expect(countValues(groupby("aaaAbb", (val) => val.toUpperCase()))).toEqual([ + ["A", 4], + ["B", 2], + ]); + }); + + it("handles not using the inner iterator", () => { + expect(Array.from(imap(groupby("aaabbbbcddddaa"), ([k]) => k))).toEqual(["a", "b", "c", "d", "a"]); + }); + + it("handles using the inner iterator after the iteration has advanced", () => { + expect(Array.from(groupby("aaabb")).map(([, v]) => Array.from(v))).toEqual([[], []]); + const it = groupby("aaabbccc"); + // Flow does not like that I use next on an iterable (it is actually + // a generator but the Generator type is awful. + + const [, v1] = it.next().value!; + const [, v2] = it.next().value!; + const [, v3] = it.next().value!; + + expect([...v1]).toEqual([]); + expect([...v2]).toEqual([]); + expect(v3.next().value!).toEqual("c"); + Array.from(it); // exhaust the groupby iterator + expect([...v3]).toEqual([]); + }); }); -describe('icompress', () => { - it('icompress is tested through compress() tests', () => { - // This is okay - }); +describe("icompress", () => { + it("icompress is tested through compress() tests", () => { + // This is okay + }); }); -describe('ifilter', () => { - it('ifilter is tested through filter() tests (see builtins)', () => { - // This is okay - }); +describe("ifilter", () => { + it("ifilter is tested through filter() tests (see builtins)", () => { + // This is okay + }); - it('ifilter can handle infinite inputs', () => { - expect(take(5, ifilter(range(9999), isEven))).toEqual([0, 2, 4, 6, 8]); - }); + it("ifilter can handle infinite inputs", () => { + expect(take(5, ifilter(range(9999), isEven))).toEqual([0, 2, 4, 6, 8]); + }); - it('ifilter retains rich type info', () => { - const filtered = take(5, ifilter([3, 'hi', null, -7], isNum)); - expect(filtered).toEqual([3, -7]); - // ^^^^^^^^ number[] - }); + it("ifilter retains rich type info", () => { + const filtered = take(5, ifilter([3, "hi", null, -7], isNum)); + expect(filtered).toEqual([3, -7]); + // ^^^^^^^^ number[] + }); }); -describe('imap', () => { - it('imap is tested through map() tests (see builtins)', () => { - // This is okay - }); - - it('...but imap can handle infinite inputs', () => { - expect( - take( - 3, - imap(range(9999), (x) => -x) - ) - ).toEqual([-0, -1, -2]); - }); +describe("imap", () => { + it("imap is tested through map() tests (see builtins)", () => { + // This is okay + }); + + it("...but imap can handle infinite inputs", () => { + expect( + take( + 3, + imap(range(9999), (x) => -x), + ), + ).toEqual([-0, -1, -2]); + }); }); -describe('islice', () => { - it('islice an empty iterable', () => { - expect(Array.from(islice([], 2))).toEqual([]); - }); - - it('islice with arguments', () => { - expect(Array.from(islice('ABCDEFG', /*stop*/ 2))).toEqual(['A', 'B']); - expect(Array.from(islice('ABCDEFG', 2, 4))).toEqual(['C', 'D']); - expect(Array.from(islice('ABCDEFG', /*start*/ 2, /*stop*/ undefined))).toEqual(['A', 'B']); - expect(Array.from(islice('ABCDEFG', /*start*/ 2, /*stop*/ null))).toEqual(['C', 'D', 'E', 'F', 'G']); - expect(Array.from(islice('ABCDEFG', /*start*/ 0, /*stop*/ null, /*step*/ 2))).toEqual(['A', 'C', 'E', 'G']); - expect(Array.from(islice('ABCDEFG', /*start*/ 1, /*stop*/ null, /*step*/ 2))).toEqual(['B', 'D', 'F']); - }); - - it('islice invalid stop argument', () => { - expect(() => Array.from(islice('ABCDEFG', /*stop*/ -2))).toThrow(); - expect(() => Array.from(islice('ABCDEFG', -2, -3))).toThrow(); - expect(() => Array.from(islice('ABCDEFG', 0, 3, 0))).toThrow(); - expect(() => Array.from(islice('ABCDEFG', 0, 3, -1))).toThrow(); - }); +describe("islice", () => { + it("islice an empty iterable", () => { + expect(Array.from(islice([], 2))).toEqual([]); + }); + + it("islice with arguments", () => { + expect(Array.from(islice("ABCDEFG", /*stop*/ 2))).toEqual(["A", "B"]); + expect(Array.from(islice("ABCDEFG", 2, 4))).toEqual(["C", "D"]); + expect(Array.from(islice("ABCDEFG", /*start*/ 2, /*stop*/ undefined))).toEqual(["A", "B"]); + expect(Array.from(islice("ABCDEFG", /*start*/ 2, /*stop*/ null))).toEqual(["C", "D", "E", "F", "G"]); + expect(Array.from(islice("ABCDEFG", /*start*/ 0, /*stop*/ null, /*step*/ 2))).toEqual(["A", "C", "E", "G"]); + expect(Array.from(islice("ABCDEFG", /*start*/ 1, /*stop*/ null, /*step*/ 2))).toEqual(["B", "D", "F"]); + }); + + it("islice invalid stop argument", () => { + expect(() => Array.from(islice("ABCDEFG", /*stop*/ -2))).toThrow(); + expect(() => Array.from(islice("ABCDEFG", -2, -3))).toThrow(); + expect(() => Array.from(islice("ABCDEFG", 0, 3, 0))).toThrow(); + expect(() => Array.from(islice("ABCDEFG", 0, 3, -1))).toThrow(); + }); }); -describe('izip', () => { - it('izip is tested through zip() tests (see builtins)', () => { - // This is okay - }); +describe("izip", () => { + it("izip is tested through zip() tests (see builtins)", () => { + // This is okay + }); }); -describe('izip3', () => { - it('izip3 is tested through zip3() tests (see builtins)', () => { - // This is okay - }); +describe("izip3", () => { + it("izip3 is tested through zip3() tests (see builtins)", () => { + // This is okay + }); }); -describe('izipMany', () => { - it('izipMany is tested through zipMany() tests', () => { - // This is okay - }); +describe("izipMany", () => { + it("izipMany is tested through zipMany() tests", () => { + // This is okay + }); }); -describe('izipLongest', () => { - it('izipLongest is tested through zipLongest() tests', () => { - // This is okay - }); +describe("izipLongest", () => { + it("izipLongest is tested through zipLongest() tests", () => { + // This is okay + }); }); -describe('permutations', () => { - it('permutations of empty list', () => { - expect(Array.from(permutations([]))).toEqual([[]]); - }); - - it('permutations of unique values', () => { - expect(Array.from(permutations([1, 2]))).toEqual([ - [1, 2], - [2, 1], - ]); - - expect(Array.from(permutations([1, 2, 3]))).toEqual([ - [1, 2, 3], - [1, 3, 2], - [2, 1, 3], - [2, 3, 1], - [3, 1, 2], - [3, 2, 1], - ]); - - // Duplicates have no effect on the results - expect(Array.from(permutations([2, 2, 3]))).toEqual([ - [2, 2, 3], - [2, 3, 2], - [2, 2, 3], - [2, 3, 2], - [3, 2, 2], - [3, 2, 2], - ]); - }); - - it('permutations with r param', () => { - // r too big - expect(Array.from(permutations([1, 2], 5))).toEqual([]); - - // prettier-ignore - expect(Array.from(permutations(range(4), 2))).toEqual([ +describe("permutations", () => { + it("permutations of empty list", () => { + expect(Array.from(permutations([]))).toEqual([[]]); + }); + + it("permutations of unique values", () => { + expect(Array.from(permutations([1, 2]))).toEqual([ + [1, 2], + [2, 1], + ]); + + expect(Array.from(permutations([1, 2, 3]))).toEqual([ + [1, 2, 3], + [1, 3, 2], + [2, 1, 3], + [2, 3, 1], + [3, 1, 2], + [3, 2, 1], + ]); + + // Duplicates have no effect on the results + expect(Array.from(permutations([2, 2, 3]))).toEqual([ + [2, 2, 3], + [2, 3, 2], + [2, 2, 3], + [2, 3, 2], + [3, 2, 2], + [3, 2, 2], + ]); + }); + + it("permutations with r param", () => { + // r too big + expect(Array.from(permutations([1, 2], 5))).toEqual([]); + + // prettier-ignore + expect(Array.from(permutations(range(4), 2))).toEqual([ [0, 1], [0, 2], [0, 3], [1, 0], [1, 2], [1, 3], [2, 0], [2, 1], [2, 3], [3, 0], [3, 1], [3, 2], ]); - }); + }); }); -describe('repeat', () => { - it('repeat indefinitely #1', () => { - // practically limit it to something (in this case 99) - const items = take(99, repeat(123)); - expect(all(items, (n) => n === 123)).toEqual(true); - }); - - it('repeat indefinitely #2', () => { - const items = take(99, repeat('foo')); - expect(all(items, (n) => n === 'foo')).toEqual(true); - }); - - it('repeat a fixed number of times', () => { - const items = repeat('foo', 100); - expect(all(items, (n) => n === 'foo')).toEqual(true); - }); +describe("repeat", () => { + it("repeat indefinitely #1", () => { + // practically limit it to something (in this case 99) + const items = take(99, repeat(123)); + expect(all(items, (n) => n === 123)).toEqual(true); + }); + + it("repeat indefinitely #2", () => { + const items = take(99, repeat("foo")); + expect(all(items, (n) => n === "foo")).toEqual(true); + }); + + it("repeat a fixed number of times", () => { + const items = repeat("foo", 100); + expect(all(items, (n) => n === "foo")).toEqual(true); + }); }); -describe('takewhile', () => { - it('takewhile on empty list', () => { - expect(Array.from(takewhile([], isEven))).toEqual([]); - expect(Array.from(takewhile([], isPositive))).toEqual([]); - }); +describe("takewhile", () => { + it("takewhile on empty list", () => { + expect(Array.from(takewhile([], isEven))).toEqual([]); + expect(Array.from(takewhile([], isPositive))).toEqual([]); + }); - it('takewhile on list', () => { - expect(Array.from(takewhile([1], isEven))).toEqual([]); - expect(Array.from(takewhile([1], isPositive))).toEqual([1]); + it("takewhile on list", () => { + expect(Array.from(takewhile([1], isEven))).toEqual([]); + expect(Array.from(takewhile([1], isPositive))).toEqual([1]); - expect(Array.from(takewhile([-1, 0, 1], isEven))).toEqual([]); - expect(Array.from(takewhile([-1, 0, 1], isPositive))).toEqual([]); + expect(Array.from(takewhile([-1, 0, 1], isEven))).toEqual([]); + expect(Array.from(takewhile([-1, 0, 1], isPositive))).toEqual([]); - expect(Array.from(takewhile([0, 2, 4, 6, 7, 8, 10], isEven))).toEqual([0, 2, 4, 6]); - expect(Array.from(takewhile([0, 1, 2, -2, 3, 4, 5, 6, 7], isPositive))).toEqual([0, 1, 2]); - }); + expect(Array.from(takewhile([0, 2, 4, 6, 7, 8, 10], isEven))).toEqual([0, 2, 4, 6]); + expect(Array.from(takewhile([0, 1, 2, -2, 3, 4, 5, 6, 7], isPositive))).toEqual([0, 1, 2]); + }); }); -describe('zipMany', () => { - it('zipMany with empty iterable', () => { - expect(zipMany([])).toEqual([]); - expect(zipMany([], [])).toEqual([]); - }); - - it('zipMany takes any number of (homogenous) iterables', () => { - expect(zipMany('abc', 'ABC')).toEqual([ - ['a', 'A'], - ['b', 'B'], - ['c', 'C'], - ]); - expect(zipMany('abc', 'ABC', 'pqrs', 'xyz')).toEqual([ - ['a', 'A', 'p', 'x'], - ['b', 'B', 'q', 'y'], - ['c', 'C', 'r', 'z'], - ]); - }); +describe("zipMany", () => { + it("zipMany with empty iterable", () => { + expect(zipMany([])).toEqual([]); + expect(zipMany([], [])).toEqual([]); + }); + + it("zipMany takes any number of (homogenous) iterables", () => { + expect(zipMany("abc", "ABC")).toEqual([ + ["a", "A"], + ["b", "B"], + ["c", "C"], + ]); + expect(zipMany("abc", "ABC", "pqrs", "xyz")).toEqual([ + ["a", "A", "p", "x"], + ["b", "B", "q", "y"], + ["c", "C", "r", "z"], + ]); + }); }); -describe('zipLongest', () => { - it('zipLongest with empty iterable', () => { - expect(zipLongest([], [])).toEqual([]); - }); - - it('zipLongest with two iterables', () => { - expect(zipLongest('abc', '')).toEqual([ - ['a', undefined], - ['b', undefined], - ['c', undefined], - ]); - expect(zipLongest('x', 'abc')).toEqual([ - ['x', 'a'], - [undefined, 'b'], - [undefined, 'c'], - ]); - expect(zipLongest('x', 'abc', /* filler */ 0)).toEqual([ - ['x', 'a'], - [0, 'b'], - [0, 'c'], - ]); - }); +describe("zipLongest", () => { + it("zipLongest with empty iterable", () => { + expect(zipLongest([], [])).toEqual([]); + }); + + it("zipLongest with two iterables", () => { + expect(zipLongest("abc", "")).toEqual([ + ["a", undefined], + ["b", undefined], + ["c", undefined], + ]); + expect(zipLongest("x", "abc")).toEqual([ + ["x", "a"], + [undefined, "b"], + [undefined, "c"], + ]); + expect(zipLongest("x", "abc", /* filler */ 0)).toEqual([ + ["x", "a"], + [0, "b"], + [0, "c"], + ]); + }); }); -describe('zipLongest3', () => { - it('zipLongest3 with empty iterable', () => { - expect(zipLongest3([], [], [])).toEqual([]); - }); - - it('zipLongest3 with two iterables', () => { - expect(zipLongest3('abc', '', [1, 2, 3])).toEqual([ - ['a', undefined, 1], - ['b', undefined, 2], - ['c', undefined, 3], - ]); - expect(zipLongest3('x', 'abc', [1, 2, 3])).toEqual([ - ['x', 'a', 1], - [undefined, 'b', 2], - [undefined, 'c', 3], - ]); - expect(zipLongest3('x', 'abc', [1, 2], /* filler */ 0)).toEqual([ - ['x', 'a', 1], - [0, 'b', 2], - [0, 'c', 0], - ]); - }); +describe("zipLongest3", () => { + it("zipLongest3 with empty iterable", () => { + expect(zipLongest3([], [], [])).toEqual([]); + }); + + it("zipLongest3 with two iterables", () => { + expect(zipLongest3("abc", "", [1, 2, 3])).toEqual([ + ["a", undefined, 1], + ["b", undefined, 2], + ["c", undefined, 3], + ]); + expect(zipLongest3("x", "abc", [1, 2, 3])).toEqual([ + ["x", "a", 1], + [undefined, "b", 2], + [undefined, "c", 3], + ]); + expect(zipLongest3("x", "abc", [1, 2], /* filler */ 0)).toEqual([ + ["x", "a", 1], + [0, "b", 2], + [0, "c", 0], + ]); + }); }); diff --git a/src/__tests__/more-itertools.test.ts b/src/__tests__/more-itertools.test.ts index 58632db1..30920e01 100644 --- a/src/__tests__/more-itertools.test.ts +++ b/src/__tests__/more-itertools.test.ts @@ -1,344 +1,344 @@ -import { describe, it, expect } from 'vitest'; -import { range } from '../builtins'; -import { find, first } from '../custom'; +import { describe, it, expect } from "vitest"; +import { range } from "../builtins"; +import { find, first } from "../custom"; import { - chunked, - flatten, - heads, - intersperse, - pairwise, - partition, - roundrobin, - take, - uniqueEverseen, - uniqueJustseen, -} from '../more-itertools'; -import * as fc from 'fast-check'; + chunked, + flatten, + heads, + intersperse, + pairwise, + partition, + roundrobin, + take, + uniqueEverseen, + uniqueJustseen, +} from "../more-itertools"; +import * as fc from "fast-check"; const isEven = (x: number) => x % 2 === 0; const isPositive = (x: number) => x >= 0; function isNum(value: unknown): value is number { - return typeof value === 'number'; + return typeof value === "number"; } -describe('chunked', () => { - it('empty', () => { - expect(Array.from(chunked([], 3))).toEqual([]); - expect(Array.from(chunked([], 1337))).toEqual([]); - }); - - it('fails with invalid chunk size', () => { - expect(() => Array.from(chunked([3, 2, 1], 0))).toThrow(); - expect(() => Array.from(chunked([3, 2, 1], -3))).toThrow(); - }); - - it('works with chunk size of 1', () => { - expect(Array.from(chunked([5, 4, 3, 2, 1], 1))).toEqual([[5], [4], [3], [2], [1]]); - }); - - it('works with array smaller than chunk size', () => { - expect(Array.from(chunked([1], 3))).toEqual([[1]]); - }); - - it('works with array of values', () => { - expect(Array.from(chunked([1, 2, 3, 4, 5], 3))).toEqual([ - [1, 2, 3], - [4, 5], - ]); - }); - - it('works with exactly chunkable list', () => { - expect(Array.from(chunked([1, 2, 3, 4, 5, 6], 3))).toEqual([ - [1, 2, 3], - [4, 5, 6], - ]); - }); - - it('works with chunkable list with remainder', () => { - const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - - expect(Array.from(chunked(numbers, 3))).toEqual([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]); - expect(Array.from(chunked(numbers, 5))).toEqual([ - [1, 2, 3, 4, 5], - [6, 7, 8, 9, 10], - ]); - expect(Array.from(chunked(numbers, 9999))).toEqual([numbers]); - }); - - it('no chunk will be larger than the chunk size', () => { - fc.assert( - fc.property( - fc.array(fc.anything()), - fc.integer({ min: 1 }), - - (input, chunkSize) => { - const output = Array.from(chunked(input, chunkSize)); - fc.pre(output.length > 0); - - const lastChunk = output.pop()!; - expect(lastChunk.length).toBeGreaterThan(0); - expect(lastChunk.length).toBeLessThanOrEqual(chunkSize); - - // The remaining chunks are all exactly the chunk size - for (const chunk of output) { - expect(chunk.length).toEqual(chunkSize); - } - } - ) - ); - }); - - it('chunks contain all elements, in the same order as the input', () => { - fc.assert( - fc.property( - fc.array(fc.anything()), - fc.integer({ min: 1 }), - - (input, chunkSize) => { - const output = Array.from(chunked(input, chunkSize)); - - // Exact same elements as input array - expect(output.flatMap((x) => x)).toEqual(input); - } - ) - ); - }); +describe("chunked", () => { + it("empty", () => { + expect(Array.from(chunked([], 3))).toEqual([]); + expect(Array.from(chunked([], 1337))).toEqual([]); + }); + + it("fails with invalid chunk size", () => { + expect(() => Array.from(chunked([3, 2, 1], 0))).toThrow(); + expect(() => Array.from(chunked([3, 2, 1], -3))).toThrow(); + }); + + it("works with chunk size of 1", () => { + expect(Array.from(chunked([5, 4, 3, 2, 1], 1))).toEqual([[5], [4], [3], [2], [1]]); + }); + + it("works with array smaller than chunk size", () => { + expect(Array.from(chunked([1], 3))).toEqual([[1]]); + }); + + it("works with array of values", () => { + expect(Array.from(chunked([1, 2, 3, 4, 5], 3))).toEqual([ + [1, 2, 3], + [4, 5], + ]); + }); + + it("works with exactly chunkable list", () => { + expect(Array.from(chunked([1, 2, 3, 4, 5, 6], 3))).toEqual([ + [1, 2, 3], + [4, 5, 6], + ]); + }); + + it("works with chunkable list with remainder", () => { + const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + expect(Array.from(chunked(numbers, 3))).toEqual([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]); + expect(Array.from(chunked(numbers, 5))).toEqual([ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + ]); + expect(Array.from(chunked(numbers, 9999))).toEqual([numbers]); + }); + + it("no chunk will be larger than the chunk size", () => { + fc.assert( + fc.property( + fc.array(fc.anything()), + fc.integer({ min: 1 }), + + (input, chunkSize) => { + const output = Array.from(chunked(input, chunkSize)); + fc.pre(output.length > 0); + + const lastChunk = output.pop()!; + expect(lastChunk.length).toBeGreaterThan(0); + expect(lastChunk.length).toBeLessThanOrEqual(chunkSize); + + // The remaining chunks are all exactly the chunk size + for (const chunk of output) { + expect(chunk.length).toEqual(chunkSize); + } + }, + ), + ); + }); + + it("chunks contain all elements, in the same order as the input", () => { + fc.assert( + fc.property( + fc.array(fc.anything()), + fc.integer({ min: 1 }), + + (input, chunkSize) => { + const output = Array.from(chunked(input, chunkSize)); + + // Exact same elements as input array + expect(output.flatMap((x) => x)).toEqual(input); + }, + ), + ); + }); }); -describe('find', () => { - it('returns nothing for an empty array', () => { - expect(find([])).toBeUndefined(); - expect(find([undefined, undefined])).toBeUndefined(); - }); - - it('returns the first value in the array', () => { - expect(find([3, 'ohai'])).toBe(3); - expect(find(['ohai', 3])).toBe('ohai'); - }); - - it('find may returns falsey values too', () => { - expect(find([0, 1, 2])).toBe(0); - expect(find([false, true])).toBe(false); - expect(find([null, false, true])).toBe(null); - expect(find([undefined, 3, 'ohai'])).toBe(undefined); - expect(find([NaN, 3, 'ohai'])).toBe(NaN); - }); - - it('find uses a predicate if provided', () => { - expect(find([0, 1, 2, 3, 4], (n) => !!n)).toBe(1); - expect(find([0, 1, 2, 3, 4], (n) => n > 1)).toBe(2); - expect(find([0, 1, 2, 3, 4], (n) => n < 0)).toBeUndefined(); - expect(find([false, true], (x) => x)).toBe(true); - }); +describe("find", () => { + it("returns nothing for an empty array", () => { + expect(find([])).toBeUndefined(); + expect(find([undefined, undefined])).toBeUndefined(); + }); + + it("returns the first value in the array", () => { + expect(find([3, "ohai"])).toBe(3); + expect(find(["ohai", 3])).toBe("ohai"); + }); + + it("find may returns falsey values too", () => { + expect(find([0, 1, 2])).toBe(0); + expect(find([false, true])).toBe(false); + expect(find([null, false, true])).toBe(null); + expect(find([undefined, 3, "ohai"])).toBe(undefined); + expect(find([NaN, 3, "ohai"])).toBe(NaN); + }); + + it("find uses a predicate if provided", () => { + expect(find([0, 1, 2, 3, 4], (n) => !!n)).toBe(1); + expect(find([0, 1, 2, 3, 4], (n) => n > 1)).toBe(2); + expect(find([0, 1, 2, 3, 4], (n) => n < 0)).toBeUndefined(); + expect(find([false, true], (x) => x)).toBe(true); + }); }); -describe('first', () => { - it('returns nothing for an empty array', () => { - expect(first([])).toBeUndefined(); - expect(first([undefined, undefined])).toBeUndefined(); - }); - - it('returns the first value in the array', () => { - expect(first([3, 'ohai'])).toBe(3); - expect(first(['ohai', 3])).toBe('ohai'); - expect(first([undefined, 3, 'ohai'])).toBe(3); - }); - - it('find may returns falsey values too', () => { - expect(first([0, 1, 2])).toBe(0); - expect(first([false, true])).toBe(false); - expect(first([null, false, true])).toBe(null); - expect(first([NaN, 3, 'ohai'])).toBe(NaN); - }); - - it('find uses a predicate if provided', () => { - expect(first([0, 1, 2, 3, 4], (n) => !!n)).toBe(1); - expect(first([0, 1, 2, 3, 4], (n) => n > 1)).toBe(2); - expect(first([0, 1, 2, 3, 4], (n) => n < 0)).toBeUndefined(); - expect(first([false, true], (x) => x)).toBe(true); - }); +describe("first", () => { + it("returns nothing for an empty array", () => { + expect(first([])).toBeUndefined(); + expect(first([undefined, undefined])).toBeUndefined(); + }); + + it("returns the first value in the array", () => { + expect(first([3, "ohai"])).toBe(3); + expect(first(["ohai", 3])).toBe("ohai"); + expect(first([undefined, 3, "ohai"])).toBe(3); + }); + + it("find may returns falsey values too", () => { + expect(first([0, 1, 2])).toBe(0); + expect(first([false, true])).toBe(false); + expect(first([null, false, true])).toBe(null); + expect(first([NaN, 3, "ohai"])).toBe(NaN); + }); + + it("find uses a predicate if provided", () => { + expect(first([0, 1, 2, 3, 4], (n) => !!n)).toBe(1); + expect(first([0, 1, 2, 3, 4], (n) => n > 1)).toBe(2); + expect(first([0, 1, 2, 3, 4], (n) => n < 0)).toBeUndefined(); + expect(first([false, true], (x) => x)).toBe(true); + }); }); -describe('flatten', () => { - it('flatten w/ empty list', () => { - expect(Array.from(flatten([]))).toEqual([]); - expect(Array.from(flatten([[], [], [], [], []]))).toEqual([]); - }); - - it('flatten works', () => { - expect( - Array.from( - flatten([ - [1, 2], - [3, 4, 5], - ]) - ) - ).toEqual([1, 2, 3, 4, 5]); - expect(Array.from(flatten(['hi', 'ha']))).toEqual(['h', 'i', 'h', 'a']); - }); +describe("flatten", () => { + it("flatten w/ empty list", () => { + expect(Array.from(flatten([]))).toEqual([]); + expect(Array.from(flatten([[], [], [], [], []]))).toEqual([]); + }); + + it("flatten works", () => { + expect( + Array.from( + flatten([ + [1, 2], + [3, 4, 5], + ]), + ), + ).toEqual([1, 2, 3, 4, 5]); + expect(Array.from(flatten(["hi", "ha"]))).toEqual(["h", "i", "h", "a"]); + }); }); -describe('intersperse', () => { - it('intersperse on empty sequence', () => { - expect(Array.from(intersperse(0, []))).toEqual([]); - }); +describe("intersperse", () => { + it("intersperse on empty sequence", () => { + expect(Array.from(intersperse(0, []))).toEqual([]); + }); - it('intersperse', () => { - expect(Array.from(intersperse(-1, [13]))).toEqual([13]); - expect(Array.from(intersperse(null, [13, 14]))).toEqual([13, null, 14]); - expect(Array.from(intersperse('foo', [1, 2, 3, 4]))).toEqual([1, 'foo', 2, 'foo', 3, 'foo', 4]); - }); + it("intersperse", () => { + expect(Array.from(intersperse(-1, [13]))).toEqual([13]); + expect(Array.from(intersperse(null, [13, 14]))).toEqual([13, null, 14]); + expect(Array.from(intersperse("foo", [1, 2, 3, 4]))).toEqual([1, "foo", 2, "foo", 3, "foo", 4]); + }); }); -describe('itake', () => { - it('itake is tested through take() tests', () => { - // This is okay - }); +describe("itake", () => { + it("itake is tested through take() tests", () => { + // This is okay + }); }); -describe('pairwise', () => { - it('does nothing for empty array', () => { - expect(Array.from(pairwise([]))).toEqual([]); - expect(Array.from(pairwise([1]))).toEqual([]); - }); - - it('it returns pairs of input', () => { - expect(Array.from(pairwise([0, 1, 2]))).toEqual([ - [0, 1], - [1, 2], - ]); - expect(Array.from(pairwise([1, 2]))).toEqual([[1, 2]]); - expect(Array.from(pairwise([1, 2, 3, 4]))).toEqual([ - [1, 2], - [2, 3], - [3, 4], - ]); - }); +describe("pairwise", () => { + it("does nothing for empty array", () => { + expect(Array.from(pairwise([]))).toEqual([]); + expect(Array.from(pairwise([1]))).toEqual([]); + }); + + it("it returns pairs of input", () => { + expect(Array.from(pairwise([0, 1, 2]))).toEqual([ + [0, 1], + [1, 2], + ]); + expect(Array.from(pairwise([1, 2]))).toEqual([[1, 2]]); + expect(Array.from(pairwise([1, 2, 3, 4]))).toEqual([ + [1, 2], + [2, 3], + [3, 4], + ]); + }); }); -describe('partition', () => { - it('partition empty list', () => { - expect(partition([], isEven)).toEqual([[], []]); - }); - - it('partition splits input list into two lists', () => { - const values = [1, -2, 3, 4, 5, 6, 8, 8, 0, -2, -3]; - expect(partition(values, isEven)).toEqual([ - [-2, 4, 6, 8, 8, 0, -2], - [1, 3, 5, -3], - ]); - expect(partition(values, isPositive)).toEqual([ - [1, 3, 4, 5, 6, 8, 8, 0], - [-2, -2, -3], - ]); - }); - - it('parition retains rich type info', () => { - const values = ['hi', 3, null, 'foo', -7]; - const [good, bad] = partition(values, isNum); - expect(good).toEqual([3, -7]); - // ^^^^ number[] - expect(bad).toEqual(['hi', null, 'foo']); - // ^^^ (string | null)[] - }); +describe("partition", () => { + it("partition empty list", () => { + expect(partition([], isEven)).toEqual([[], []]); + }); + + it("partition splits input list into two lists", () => { + const values = [1, -2, 3, 4, 5, 6, 8, 8, 0, -2, -3]; + expect(partition(values, isEven)).toEqual([ + [-2, 4, 6, 8, 8, 0, -2], + [1, 3, 5, -3], + ]); + expect(partition(values, isPositive)).toEqual([ + [1, 3, 4, 5, 6, 8, 8, 0], + [-2, -2, -3], + ]); + }); + + it("parition retains rich type info", () => { + const values = ["hi", 3, null, "foo", -7]; + const [good, bad] = partition(values, isNum); + expect(good).toEqual([3, -7]); + // ^^^^ number[] + expect(bad).toEqual(["hi", null, "foo"]); + // ^^^ (string | null)[] + }); }); -describe('roundrobin', () => { - it('roundrobin on empty list', () => { - expect(Array.from(roundrobin())).toEqual([]); - expect(Array.from(roundrobin([]))).toEqual([]); - expect(Array.from(roundrobin([], []))).toEqual([]); - expect(Array.from(roundrobin([], [], []))).toEqual([]); - expect(Array.from(roundrobin([], [], [], []))).toEqual([]); - }); - - it('roundrobin on equally sized lists', () => { - expect(Array.from(roundrobin([1], [2], [3]))).toEqual([1, 2, 3]); - expect(Array.from(roundrobin([1, 2], [3, 4]))).toEqual([1, 3, 2, 4]); - expect(Array.from(roundrobin('foo', 'bar')).join('')).toEqual('fboaor'); - }); - - it('roundrobin on unequally sized lists', () => { - expect(Array.from(roundrobin([1], [], [2, 3, 4]))).toEqual([1, 2, 3, 4]); - expect(Array.from(roundrobin([1, 2, 3, 4, 5], [6, 7]))).toEqual([1, 6, 2, 7, 3, 4, 5]); - expect(Array.from(roundrobin([1, 2, 3], [4], [5, 6, 7, 8]))).toEqual([1, 4, 5, 2, 6, 3, 7, 8]); - }); +describe("roundrobin", () => { + it("roundrobin on empty list", () => { + expect(Array.from(roundrobin())).toEqual([]); + expect(Array.from(roundrobin([]))).toEqual([]); + expect(Array.from(roundrobin([], []))).toEqual([]); + expect(Array.from(roundrobin([], [], []))).toEqual([]); + expect(Array.from(roundrobin([], [], [], []))).toEqual([]); + }); + + it("roundrobin on equally sized lists", () => { + expect(Array.from(roundrobin([1], [2], [3]))).toEqual([1, 2, 3]); + expect(Array.from(roundrobin([1, 2], [3, 4]))).toEqual([1, 3, 2, 4]); + expect(Array.from(roundrobin("foo", "bar")).join("")).toEqual("fboaor"); + }); + + it("roundrobin on unequally sized lists", () => { + expect(Array.from(roundrobin([1], [], [2, 3, 4]))).toEqual([1, 2, 3, 4]); + expect(Array.from(roundrobin([1, 2, 3, 4, 5], [6, 7]))).toEqual([1, 6, 2, 7, 3, 4, 5]); + expect(Array.from(roundrobin([1, 2, 3], [4], [5, 6, 7, 8]))).toEqual([1, 4, 5, 2, 6, 3, 7, 8]); + }); }); -describe('heads', () => { - it('heads on empty list', () => { - expect(Array.from(heads())).toEqual([]); - expect(Array.from(heads([]))).toEqual([]); - expect(Array.from(heads([], []))).toEqual([]); - expect(Array.from(heads([], [], []))).toEqual([]); - expect(Array.from(heads([], [], [], []))).toEqual([]); - }); - - it('heads on equally sized lists', () => { - expect(Array.from(heads([1], [2], [3]))).toEqual([[1, 2, 3]]); - expect(Array.from(heads([1, 2], [3, 4]))).toEqual([ - [1, 3], - [2, 4], - ]); - expect(Array.from(heads('foo', 'bar')).map((s) => s.join(''))).toEqual(['fb', 'oa', 'or']); - }); - - it('heads on unequally sized lists', () => { - expect(Array.from(heads([1], [], [2, 3, 4]))).toEqual([[1, 2], [3], [4]]); - expect(Array.from(heads([1, 2, 3, 4, 5], [6, 7]))).toEqual([[1, 6], [2, 7], [3], [4], [5]]); - expect(Array.from(heads([1, 2, 3], [4], [5, 6, 7, 8]))).toEqual([[1, 4, 5], [2, 6], [3, 7], [8]]); - }); +describe("heads", () => { + it("heads on empty list", () => { + expect(Array.from(heads())).toEqual([]); + expect(Array.from(heads([]))).toEqual([]); + expect(Array.from(heads([], []))).toEqual([]); + expect(Array.from(heads([], [], []))).toEqual([]); + expect(Array.from(heads([], [], [], []))).toEqual([]); + }); + + it("heads on equally sized lists", () => { + expect(Array.from(heads([1], [2], [3]))).toEqual([[1, 2, 3]]); + expect(Array.from(heads([1, 2], [3, 4]))).toEqual([ + [1, 3], + [2, 4], + ]); + expect(Array.from(heads("foo", "bar")).map((s) => s.join(""))).toEqual(["fb", "oa", "or"]); + }); + + it("heads on unequally sized lists", () => { + expect(Array.from(heads([1], [], [2, 3, 4]))).toEqual([[1, 2], [3], [4]]); + expect(Array.from(heads([1, 2, 3, 4, 5], [6, 7]))).toEqual([[1, 6], [2, 7], [3], [4], [5]]); + expect(Array.from(heads([1, 2, 3], [4], [5, 6, 7, 8]))).toEqual([[1, 4, 5], [2, 6], [3, 7], [8]]); + }); }); -describe('take', () => { - it('take on empty array', () => { - expect(take(0, [])).toEqual([]); - expect(take(1, [])).toEqual([]); - expect(take(99, [])).toEqual([]); - }); - - it('take on infinite input', () => { - expect(take(5, Math.PI.toString())).toEqual(['3', '.', '1', '4', '1']); - }); - - it('take on infinite input', () => { - expect(take(0, range(999)).length).toEqual(0); - expect(take(1, range(999)).length).toEqual(1); - expect(take(99, range(999)).length).toEqual(99); - }); +describe("take", () => { + it("take on empty array", () => { + expect(take(0, [])).toEqual([]); + expect(take(1, [])).toEqual([]); + expect(take(99, [])).toEqual([]); + }); + + it("take on infinite input", () => { + expect(take(5, Math.PI.toString())).toEqual(["3", ".", "1", "4", "1"]); + }); + + it("take on infinite input", () => { + expect(take(0, range(999)).length).toEqual(0); + expect(take(1, range(999)).length).toEqual(1); + expect(take(99, range(999)).length).toEqual(99); + }); }); -describe('uniqueJustseen', () => { - it('uniqueJustseen w/ empty list', () => { - expect(Array.from(uniqueJustseen([]))).toEqual([]); - }); +describe("uniqueJustseen", () => { + it("uniqueJustseen w/ empty list", () => { + expect(Array.from(uniqueJustseen([]))).toEqual([]); + }); - it('uniqueJustseen', () => { - expect(Array.from(uniqueJustseen([1, 2, 3, 4, 5]))).toEqual([1, 2, 3, 4, 5]); - expect(Array.from(uniqueJustseen([1, 1, 1, 2, 2]))).toEqual([1, 2]); - expect(Array.from(uniqueJustseen([1, 1, 1, 2, 2, 1, 1, 1, 1]))).toEqual([1, 2, 1]); - }); + it("uniqueJustseen", () => { + expect(Array.from(uniqueJustseen([1, 2, 3, 4, 5]))).toEqual([1, 2, 3, 4, 5]); + expect(Array.from(uniqueJustseen([1, 1, 1, 2, 2]))).toEqual([1, 2]); + expect(Array.from(uniqueJustseen([1, 1, 1, 2, 2, 1, 1, 1, 1]))).toEqual([1, 2, 1]); + }); - it('uniqueEverseen with key function', () => { - expect(Array.from(uniqueJustseen('AaABbBCcaABBb', (s) => s.toLowerCase()))).toEqual(['A', 'B', 'C', 'a', 'B']); - }); + it("uniqueEverseen with key function", () => { + expect(Array.from(uniqueJustseen("AaABbBCcaABBb", (s) => s.toLowerCase()))).toEqual(["A", "B", "C", "a", "B"]); + }); }); -describe('uniqueEverseen', () => { - it('uniqueEverseen w/ empty list', () => { - expect(Array.from(uniqueEverseen([]))).toEqual([]); - }); - - it('uniqueEverseen never emits dupes, but keeps input ordering', () => { - expect(Array.from(uniqueEverseen([1, 2, 3, 4, 5]))).toEqual([1, 2, 3, 4, 5]); - expect(Array.from(uniqueEverseen([1, 1, 1, 2, 2, 3, 1, 3, 0, 4]))).toEqual([1, 2, 3, 0, 4]); - expect(Array.from(uniqueEverseen([1, 1, 1, 2, 2, 1, 1, 1, 1]))).toEqual([1, 2]); - }); - - it('uniqueEverseen with key function', () => { - expect(Array.from(uniqueEverseen('AAAABBBCCDAABBB'))).toEqual(['A', 'B', 'C', 'D']); - expect(Array.from(uniqueEverseen('ABCcAb', (s) => s.toLowerCase()))).toEqual(['A', 'B', 'C']); - expect(Array.from(uniqueEverseen('AbCBBcAb', (s) => s.toLowerCase()))).toEqual(['A', 'b', 'C']); - }); +describe("uniqueEverseen", () => { + it("uniqueEverseen w/ empty list", () => { + expect(Array.from(uniqueEverseen([]))).toEqual([]); + }); + + it("uniqueEverseen never emits dupes, but keeps input ordering", () => { + expect(Array.from(uniqueEverseen([1, 2, 3, 4, 5]))).toEqual([1, 2, 3, 4, 5]); + expect(Array.from(uniqueEverseen([1, 1, 1, 2, 2, 3, 1, 3, 0, 4]))).toEqual([1, 2, 3, 0, 4]); + expect(Array.from(uniqueEverseen([1, 1, 1, 2, 2, 1, 1, 1, 1]))).toEqual([1, 2]); + }); + + it("uniqueEverseen with key function", () => { + expect(Array.from(uniqueEverseen("AAAABBBCCDAABBB"))).toEqual(["A", "B", "C", "D"]); + expect(Array.from(uniqueEverseen("ABCcAb", (s) => s.toLowerCase()))).toEqual(["A", "B", "C"]); + expect(Array.from(uniqueEverseen("AbCBBcAb", (s) => s.toLowerCase()))).toEqual(["A", "b", "C"]); + }); }); diff --git a/src/builtins.ts b/src/builtins.ts index 01599e22..7da4ba65 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -1,7 +1,7 @@ -import { find } from './custom'; -import { count, ifilter, imap, izip, izip3, takewhile } from './itertools'; -import type { Predicate, Primitive } from './types'; -import { identityPredicate, keyToCmp, numberIdentity, primitiveIdentity } from './utils'; +import { find } from "./custom"; +import { count, ifilter, imap, izip, izip3, takewhile } from "./itertools"; +import type { Predicate, Primitive } from "./types"; +import { identityPredicate, keyToCmp, numberIdentity, primitiveIdentity } from "./utils"; /** * Returns true when all of the items in iterable are truthy. An optional key @@ -22,13 +22,13 @@ import { identityPredicate, keyToCmp, numberIdentity, primitiveIdentity } from ' * */ export function every(iterable: Iterable, keyFn: Predicate = identityPredicate): boolean { - for (const item of iterable) { - if (!keyFn(item)) { - return false; - } + for (const item of iterable) { + if (!keyFn(item)) { + return false; } + } - return true; + return true; } /** @@ -49,13 +49,13 @@ export function every(iterable: Iterable, keyFn: Predicate = identityPr * */ export function some(iterable: Iterable, keyFn: Predicate = identityPredicate): boolean { - for (const item of iterable) { - if (keyFn(item)) { - return true; - } + for (const item of iterable) { + if (keyFn(item)) { + return true; } + } - return false; + return false; } /** @@ -80,7 +80,7 @@ export const any = some; * */ export function contains(haystack: Iterable, needle: T): boolean { - return some(haystack, (x) => x === needle); + return some(haystack, (x) => x === needle); } /** @@ -97,10 +97,10 @@ export function contains(haystack: Iterable, needle: T): boolean { * // [0, 'hello'], [1, 'world']] */ export function* enumerate(iterable: Iterable, start = 0): Iterable<[number, T]> { - let index: number = start; - for (const value of iterable) { - yield [index++, value]; - } + let index: number = start; + for (const value of iterable) { + yield [index++, value]; + } } /** @@ -109,7 +109,7 @@ export function* enumerate(iterable: Iterable, start = 0): Iterable<[numbe export function filter(iterable: Iterable, predicate: (item: T) => item is N): N[]; export function filter(iterable: Iterable, predicate: Predicate): T[]; export function filter(iterable: Iterable, predicate: Predicate): T[] { - return Array.from(ifilter(iterable, predicate)); + return Array.from(ifilter(iterable, predicate)); } /** @@ -119,29 +119,29 @@ export function filter(iterable: Iterable, predicate: Predicate): T[] { * state, think of it as a "cursor") which can only be consumed once. */ export function iter(iterable: Iterable): IterableIterator { - // class SelfIter implements IterableIterator { - // #iterator: Iterator; - // constructor(orig: Iterable) { - // this.#iterator = orig[Symbol.iterator](); - // } - // [Symbol.iterator]() { - // return this; - // } - // next() { - // return this.#iterator.next(); - // } - // } - // return new SelfIter(iterable); + // class SelfIter implements IterableIterator { + // #iterator: Iterator; + // constructor(orig: Iterable) { + // this.#iterator = orig[Symbol.iterator](); + // } + // [Symbol.iterator]() { + // return this; + // } + // next() { + // return this.#iterator.next(); + // } + // } + // return new SelfIter(iterable); - return iterable[Symbol.iterator]() as IterableIterator; - // ^^^^^^^^^^^^^^^^^^^^^^ Not safe! + return iterable[Symbol.iterator]() as IterableIterator; + // ^^^^^^^^^^^^^^^^^^^^^^ Not safe! } /** * Non-lazy version of imap(). */ export function map(iterable: Iterable, mapper: (item: T) => V): V[] { - return Array.from(imap(iterable, mapper)); + return Array.from(imap(iterable, mapper)); } /** @@ -156,7 +156,7 @@ export function map(iterable: Iterable, mapper: (item: T) => V): V[] { * which one is not defined. */ export function max(iterable: Iterable, keyFn: (item: T) => number = numberIdentity): T | undefined { - return reduce2(iterable, (x, y) => (keyFn(x) > keyFn(y) ? x : y)); + return reduce2(iterable, (x, y) => (keyFn(x) > keyFn(y) ? x : y)); } /** @@ -171,16 +171,16 @@ export function max(iterable: Iterable, keyFn: (item: T) => number = numbe * which one is not defined. */ export function min(iterable: Iterable, keyFn: (item: T) => number = numberIdentity): T | undefined { - return reduce2(iterable, (x, y) => (keyFn(x) < keyFn(y) ? x : y)); + return reduce2(iterable, (x, y) => (keyFn(x) < keyFn(y) ? x : y)); } /** * Internal helper for the range function */ function range_(start: number, stop: number, step: number): Iterable { - const counter = count(start, step); - const pred = step >= 0 ? (n: number) => n < stop : (n: number) => n > stop; - return takewhile(counter, pred); + const counter = count(start, step); + const pred = step >= 0 ? (n: number) => n < stop : (n: number) => n > stop; + return takewhile(counter, pred); } /** @@ -211,11 +211,11 @@ function range_(start: number, stop: number, step: number): Iterable { export function range(stop: number): Iterable; export function range(start: number, stop: number, step?: number): Iterable; export function range(startOrStop: number, definitelyStop?: number, step = 1): Iterable { - if (definitelyStop !== undefined) { - return range_(startOrStop /* as start */, definitelyStop, step); - } else { - return range_(0, startOrStop /* as stop */, step); - } + if (definitelyStop !== undefined) { + return range_(startOrStop /* as start */, definitelyStop, step); + } else { + return range_(0, startOrStop /* as stop */, step); + } } /** @@ -242,34 +242,34 @@ export function range(startOrStop: number, definitelyStop?: number, step = 1): I export function reduce(iterable: Iterable, reducer: (agg: T, item: T, index: number) => T): T | undefined; export function reduce(iterable: Iterable, reducer: (agg: O, item: T, index: number) => O, start: O): O; export function reduce( - iterable: Iterable, - reducer: ((agg: T, item: T, index: number) => T) | ((agg: O, item: T, index: number) => O), - start?: O + iterable: Iterable, + reducer: ((agg: T, item: T, index: number) => T) | ((agg: O, item: T, index: number) => O), + start?: O, ): O | (T | undefined) { - if (start === undefined) { - return reduce2(iterable, reducer as (agg: T, item: T, index: number) => T); - } else { - return reduce3(iterable, reducer as (agg: O, item: T, index: number) => O, start); - } + if (start === undefined) { + return reduce2(iterable, reducer as (agg: T, item: T, index: number) => T); + } else { + return reduce3(iterable, reducer as (agg: O, item: T, index: number) => O, start); + } } function reduce3(iterable: Iterable, reducer: (agg: O, item: T, index: number) => O, start: O): O { - let output = start; - let index = 0; - for (const item of iterable) { - output = reducer(output, item, index++); - } - return output; + let output = start; + let index = 0; + for (const item of iterable) { + output = reducer(output, item, index++); + } + return output; } function reduce2(iterable: Iterable, reducer: (agg: T, item: T, index: number) => T): T | undefined { - const it = iter(iterable); - const start = find(it); - if (start === undefined) { - return undefined; - } else { - return reduce3(it, reducer, start); - } + const it = iter(iterable); + const start = find(it); + if (start === undefined) { + return undefined; + } else { + return reduce3(it, reducer, start); + } } /** @@ -286,18 +286,18 @@ function reduce2(iterable: Iterable, reducer: (agg: T, item: T, index: num * sorted as if each comparison were reversed. */ export function sorted( - iterable: Iterable, - keyFn: (item: T) => Primitive = primitiveIdentity, - reverse = false + iterable: Iterable, + keyFn: (item: T) => Primitive = primitiveIdentity, + reverse = false, ): T[] { - const result = Array.from(iterable); - result.sort(keyToCmp(keyFn)); // sort in-place + const result = Array.from(iterable); + result.sort(keyToCmp(keyFn)); // sort in-place - if (reverse) { - result.reverse(); // reverse in-place - } + if (reverse) { + result.reverse(); // reverse in-place + } - return result; + return result; } /** @@ -305,19 +305,19 @@ export function sorted( * sum will defaults to 0 if the iterable is empty. */ export function sum(iterable: Iterable): number { - return reduce(iterable, (x, y) => x + y, 0); + return reduce(iterable, (x, y) => x + y, 0); } /** * See izip. */ export function zip(xs: Iterable, ys: Iterable): Array<[T1, T2]> { - return Array.from(izip(xs, ys)); + return Array.from(izip(xs, ys)); } /** * See izip3. */ export function zip3(xs: Iterable, ys: Iterable, zs: Iterable): Array<[T1, T2, T3]> { - return Array.from(izip3(xs, ys, zs)); + return Array.from(izip3(xs, ys, zs)); } diff --git a/src/custom.ts b/src/custom.ts index f0a0ce9a..d4aab008 100644 --- a/src/custom.ts +++ b/src/custom.ts @@ -1,13 +1,13 @@ -import { ifilter, imap } from './itertools'; -import { flatten } from './more-itertools'; -import type { Predicate } from './types'; +import { ifilter, imap } from "./itertools"; +import { flatten } from "./more-itertools"; +import type { Predicate } from "./types"; function isNullish(x: T): x is NonNullable { - return x != null; + return x != null; } function isDefined(x: T): boolean { - return x !== undefined; + return x !== undefined; } /** @@ -19,7 +19,7 @@ function isDefined(x: T): boolean { * For an eager version, @see compact(). */ export function icompact(iterable: Iterable): Iterable { - return ifilter(iterable, isNullish); + return ifilter(iterable, isNullish); } /** @@ -31,7 +31,7 @@ export function icompact(iterable: Iterable): Iterable< * For a lazy version, @see icompact(). */ export function compact(iterable: Iterable): T[] { - return Array.from(icompact(iterable)); + return Array.from(icompact(iterable)); } /** @@ -42,14 +42,14 @@ export function compact(iterable: Iterable): T[] { * */ export function compactObject(obj: Record): Record { - const result = {} as Record; - for (const [key, value_] of Object.entries(obj)) { - const value = value_ as V | null | undefined; - if (value != null) { - result[key as K] = value; - } + const result = {} as Record; + for (const [key, value_] of Object.entries(obj)) { + const value = value_ as V | null | undefined; + if (value != null) { + result[key as K] = value; } - return result; + } + return result; } /** @@ -58,19 +58,19 @@ export function compactObject(obj: Record(iterable: Iterable, keyFn?: Predicate): T | undefined { - if (keyFn === undefined) { - for (const value of iterable) { - return value; - } - return undefined; - } else { - for (const value of iterable) { - if (keyFn(value)) { - return value; - } - } - return undefined; + if (keyFn === undefined) { + for (const value of iterable) { + return value; } + return undefined; + } else { + for (const value of iterable) { + if (keyFn(value)) { + return value; + } + } + return undefined; + } } /** @@ -80,7 +80,7 @@ export function find(iterable: Iterable, keyFn?: Predicate): T | undefi * iterable. */ export function first(iterable: Iterable, keyFn?: Predicate): T | undefined { - return find(iterable, keyFn ?? isDefined); + return find(iterable, keyFn ?? isDefined); } /** @@ -98,5 +98,5 @@ export function first(iterable: Iterable, keyFn?: Predicate): T | undef * */ export function flatmap(iterable: Iterable, mapper: (item: T) => Iterable): Iterable { - return flatten(imap(iterable, mapper)); + return flatten(imap(iterable, mapper)); } diff --git a/src/index.ts b/src/index.ts index 2df8bfbc..e91588d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,55 +1,55 @@ -export type { Predicate, Primitive } from './types'; +export type { Predicate, Primitive } from "./types"; export { - all, - any, - contains, - enumerate, - every, - filter, - iter, - map, - max, - min, - range, - reduce, - some, - sorted, - sum, - zip, - zip3, -} from './builtins'; + all, + any, + contains, + enumerate, + every, + filter, + iter, + map, + max, + min, + range, + reduce, + some, + sorted, + sum, + zip, + zip3, +} from "./builtins"; export { - chain, - compress, - count, - cycle, - dropwhile, - groupby, - icompress, - ifilter, - imap, - islice, - izip, - izip2, - izip3, - izipMany, - izipLongest, - permutations, - takewhile, - zipLongest, - zipMany, -} from './itertools'; + chain, + compress, + count, + cycle, + dropwhile, + groupby, + icompress, + ifilter, + imap, + islice, + izip, + izip2, + izip3, + izipMany, + izipLongest, + permutations, + takewhile, + zipLongest, + zipMany, +} from "./itertools"; export { - chunked, - flatten, - heads, - itake, - pairwise, - partition, - roundrobin, - take, - uniqueEverseen, - uniqueJustseen, -} from './more-itertools'; -export { compact, compactObject, find, first, flatmap, icompact } from './custom'; + chunked, + flatten, + heads, + itake, + pairwise, + partition, + roundrobin, + take, + uniqueEverseen, + uniqueJustseen, +} from "./more-itertools"; +export { compact, compactObject, find, first, flatmap, icompact } from "./custom"; diff --git a/src/itertools.ts b/src/itertools.ts index 9fde062e..f21e293b 100644 --- a/src/itertools.ts +++ b/src/itertools.ts @@ -1,31 +1,31 @@ -import { enumerate, every, iter, range } from './builtins'; -import { flatten } from './more-itertools'; -import type { Predicate, Primitive } from './types'; -import { primitiveIdentity } from './utils'; +import { enumerate, every, iter, range } from "./builtins"; +import { flatten } from "./more-itertools"; +import type { Predicate, Primitive } from "./types"; +import { primitiveIdentity } from "./utils"; const SENTINEL = Symbol(); function composeAnd(f1: (v1: number) => boolean, f2: (v2: number) => boolean): (v3: number) => boolean { - return (n: number) => f1(n) && f2(n); + return (n: number) => f1(n) && f2(n); } function slicePredicate(start: number, stop: number | null, step: number) { - if (start < 0) throw new Error('start cannot be negative'); - if (stop !== null && stop < 0) throw new Error('stop cannot be negative'); - if (step <= 0) throw new Error('step cannot be negative'); + if (start < 0) throw new Error("start cannot be negative"); + if (stop !== null && stop < 0) throw new Error("stop cannot be negative"); + if (step <= 0) throw new Error("step cannot be negative"); - let pred = (n: number) => n >= start; + let pred = (n: number) => n >= start; - if (stop !== null) { - const definedStop = stop; - pred = composeAnd(pred, (n: number) => n < definedStop); - } + if (stop !== null) { + const definedStop = stop; + pred = composeAnd(pred, (n: number) => n < definedStop); + } - if (step > 1) { - pred = composeAnd(pred, (n: number) => (n - start) % step === 0); - } + if (step > 1) { + pred = composeAnd(pred, (n: number) => (n - start) % step === 0); + } - return pred; + return pred; } /** @@ -35,7 +35,7 @@ function slicePredicate(start: number, stop: number | null, step: number) { * sequence. */ export function chain(...iterables: Iterable[]): Iterable { - return flatten(iterables); + return flatten(iterables); } /** @@ -44,18 +44,18 @@ export function chain(...iterables: Iterable[]): Iterable { * number. */ export function* count(start = 0, step = 1): Iterable { - let n = start; - for (;;) { - yield n; - n += step; - } + let n = start; + for (;;) { + yield n; + n += step; + } } /** * Non-lazy version of icompress(). */ export function compress(data: Iterable, selectors: Iterable): T[] { - return Array.from(icompress(data, selectors)); + return Array.from(icompress(data, selectors)); } /** @@ -64,17 +64,17 @@ export function compress(data: Iterable, selectors: Iterable): T[ * copy. Repeats indefinitely. */ export function* cycle(iterable: Iterable): Iterable { - const saved = []; - for (const element of iterable) { - yield element; - saved.push(element); - } - - while (saved.length > 0) { - for (const element of saved) { - yield element; - } + const saved = []; + for (const element of iterable) { + yield element; + saved.push(element); + } + + while (saved.length > 0) { + for (const element of saved) { + yield element; } + } } /** @@ -84,56 +84,56 @@ export function* cycle(iterable: Iterable): Iterable { * false. */ export function* dropwhile(iterable: Iterable, predicate: Predicate): Iterable { - const it = iter(iterable); - for (const value of it) { - if (!predicate(value)) { - yield value; - break; - } + const it = iter(iterable); + for (const value of it) { + if (!predicate(value)) { + yield value; + break; } + } - for (const value of it) { - yield value; - } + for (const value of it) { + yield value; + } } export function* groupby( - iterable: Iterable, - keyFn: (item: T) => K = primitiveIdentity + iterable: Iterable, + keyFn: (item: T) => K = primitiveIdentity, ): Generator<[K, Generator], undefined> { - const it = iter(iterable); - - let currentValue: T; - let currentKey: K = SENTINEL as unknown as K; - // ^^^^^^^^^^^^^^^ Hack! - let targetKey: K = currentKey; + const it = iter(iterable); - const grouper = function* grouper(tgtKey: K): Generator { - while (currentKey === tgtKey) { - yield currentValue; + let currentValue: T; + let currentKey: K = SENTINEL as unknown as K; + // ^^^^^^^^^^^^^^^ Hack! + let targetKey: K = currentKey; - const nextVal = it.next(); - if (nextVal.done) return; - currentValue = nextVal.value; - currentKey = keyFn(currentValue); - } - }; + const grouper = function* grouper(tgtKey: K): Generator { + while (currentKey === tgtKey) { + yield currentValue; - for (;;) { - while (currentKey === targetKey) { - const nextVal = it.next(); - if (nextVal.done) { - currentKey = SENTINEL as unknown as K; - // ^^^^^^^^^^^^^^^ Hack! - return; - } - currentValue = nextVal.value; - currentKey = keyFn(currentValue); - } - - targetKey = currentKey; - yield [currentKey, grouper(targetKey)]; + const nextVal = it.next(); + if (nextVal.done) return; + currentValue = nextVal.value; + currentKey = keyFn(currentValue); + } + }; + + for (;;) { + while (currentKey === targetKey) { + const nextVal = it.next(); + if (nextVal.done) { + currentKey = SENTINEL as unknown as K; + // ^^^^^^^^^^^^^^^ Hack! + return; + } + currentValue = nextVal.value; + currentKey = keyFn(currentValue); } + + targetKey = currentKey; + yield [currentKey, grouper(targetKey)]; + } } /** @@ -142,11 +142,11 @@ export function* groupby( * Stops when either the data or selectors iterables has been exhausted. */ export function* icompress(data: Iterable, selectors: Iterable): Iterable { - for (const [d, s] of izip(data, selectors)) { - if (s) { - yield d; - } + for (const [d, s] of izip(data, selectors)) { + if (s) { + yield d; } + } } /** @@ -156,11 +156,11 @@ export function* icompress(data: Iterable, selectors: Iterable): export function ifilter(iterable: Iterable, predicate: (item: T) => item is N): Iterable; export function ifilter(iterable: Iterable, predicate: Predicate): Iterable; export function* ifilter(iterable: Iterable, predicate: Predicate): Iterable { - for (const value of iterable) { - if (predicate(value)) { - yield value; - } + for (const value of iterable) { + if (predicate(value)) { + yield value; } + } } /** @@ -168,9 +168,9 @@ export function* ifilter(iterable: Iterable, predicate: Predicate): Ite * from each of the iterables. */ export function* imap(iterable: Iterable, mapper: (item: T) => V): Iterable { - for (const value of iterable) { - yield mapper(value); - } + for (const value of iterable) { + yield mapper(value); + } } /** @@ -185,28 +185,28 @@ export function* imap(iterable: Iterable, mapper: (item: T) => V): Iter export function islice(iterable: Iterable, stop: number): Iterable; export function islice(iterable: Iterable, start: number, stop?: number | null, step?: number): Iterable; export function* islice( - iterable: Iterable, - stopOrStart: number, - possiblyStop?: number | null, - step = 1 + iterable: Iterable, + stopOrStart: number, + possiblyStop?: number | null, + step = 1, ): Iterable { - let start, stop; - if (possiblyStop !== undefined) { - // islice(iterable, start, stop[, step]) - start = stopOrStart; - stop = possiblyStop; - } else { - // islice(iterable, stop) - start = 0; - stop = stopOrStart; - } - - const pred = slicePredicate(start, stop, step); - for (const [i, value] of enumerate(iterable)) { - if (pred(i)) { - yield value; - } + let start, stop; + if (possiblyStop !== undefined) { + // islice(iterable, start, stop[, step]) + start = stopOrStart; + stop = possiblyStop; + } else { + // islice(iterable, stop) + start = 0; + stop = stopOrStart; + } + + const pred = slicePredicate(start, stop, step); + for (const [i, value] of enumerate(iterable)) { + if (pred(i)) { + yield value; } + } } /** @@ -216,38 +216,38 @@ export function* islice( * iterables, use `izip3`, etc. `izip` is an alias for `izip2`. */ export function* izip2(xs: Iterable, ys: Iterable): Iterable<[T1, T2]> { - const ixs = iter(xs); - const iys = iter(ys); - for (;;) { - const x = ixs.next(); - const y = iys.next(); - if (!x.done && !y.done) { - yield [x.value, y.value]; - } else { - // One of the iterables exhausted - return; - } + const ixs = iter(xs); + const iys = iter(ys); + for (;;) { + const x = ixs.next(); + const y = iys.next(); + if (!x.done && !y.done) { + yield [x.value, y.value]; + } else { + // One of the iterables exhausted + return; } + } } /** * Like izip2, but for three input iterables. */ export function* izip3(xs: Iterable, ys: Iterable, zs: Iterable): Iterable<[T1, T2, T3]> { - const ixs = iter(xs); - const iys = iter(ys); - const izs = iter(zs); - for (;;) { - const x = ixs.next(); - const y = iys.next(); - const z = izs.next(); - if (!x.done && !y.done && !z.done) { - yield [x.value, y.value, z.value]; - } else { - // One of the iterables exhausted - return; - } + const ixs = iter(xs); + const iys = iter(ys); + const izs = iter(zs); + for (;;) { + const x = ixs.next(); + const y = iys.next(); + const z = izs.next(); + if (!x.done && !y.done && !z.done) { + yield [x.value, y.value, z.value]; + } else { + // One of the iterables exhausted + return; } + } } export const izip = izip2; @@ -258,45 +258,45 @@ export const izip = izip2; * fillvalue. Iteration continues until the longest iterable is exhausted. */ export function* izipLongest2(xs: Iterable, ys: Iterable, filler?: D): Iterable<[T1 | D, T2 | D]> { - const filler_ = filler as D; - const ixs = iter(xs); - const iys = iter(ys); - for (;;) { - const x = ixs.next(); - const y = iys.next(); - if (x.done && y.done) { - // All iterables exhausted - return; - } else { - yield [!x.done ? x.value : filler_, !y.done ? y.value : filler_]; - } + const filler_ = filler as D; + const ixs = iter(xs); + const iys = iter(ys); + for (;;) { + const x = ixs.next(); + const y = iys.next(); + if (x.done && y.done) { + // All iterables exhausted + return; + } else { + yield [!x.done ? x.value : filler_, !y.done ? y.value : filler_]; } + } } /** * See izipLongest2, but for three. */ export function* izipLongest3( - xs: Iterable, - ys: Iterable, - zs: Iterable, - filler?: D + xs: Iterable, + ys: Iterable, + zs: Iterable, + filler?: D, ): Iterable<[T1 | D, T2 | D, T3 | D]> { - const filler_ = filler as D; - const ixs = iter(xs); - const iys = iter(ys); - const izs = iter(zs); - for (;;) { - const x = ixs.next(); - const y = iys.next(); - const z = izs.next(); - if (x.done && y.done && z.done) { - // All iterables exhausted - return; - } else { - yield [!x.done ? x.value : filler_, !y.done ? y.value : filler_, !z.done ? z.value : filler_]; - } + const filler_ = filler as D; + const ixs = iter(xs); + const iys = iter(ys); + const izs = iter(zs); + for (;;) { + const x = ixs.next(); + const y = iys.next(); + const z = izs.next(); + if (x.done && y.done && z.done) { + // All iterables exhausted + return; + } else { + yield [!x.done ? x.value : filler_, !y.done ? y.value : filler_, !z.done ? z.value : filler_]; } + } } /** @@ -308,18 +308,18 @@ export function* izipLongest3( * you can with izip2(). */ export function* izipMany(...iters: Iterable[]): Iterable { - // Make them all iterables - const iterables = iters.map(iter); + // Make them all iterables + const iterables = iters.map(iter); - for (;;) { - const heads: Array> = iterables.map((xs) => xs.next()); - if (every(heads, (h) => !h.done)) { - yield heads.map((h) => h.value as T); - } else { - // One of the iterables exhausted - return; - } + for (;;) { + const heads: Array> = iterables.map((xs) => xs.next()); + if (every(heads, (h) => !h.done)) { + yield heads.map((h) => h.value as T); + } else { + // One of the iterables exhausted + return; } + } } /** @@ -336,46 +336,46 @@ export function* izipMany(...iters: Iterable[]): Iterable { * permutation. */ export function* permutations(iterable: Iterable, r?: number): Iterable { - const pool = Array.from(iterable); - const n = pool.length; - const x = r === undefined ? n : r; - - if (x > n) { - return; + const pool = Array.from(iterable); + const n = pool.length; + const x = r === undefined ? n : r; + + if (x > n) { + return; + } + + let indices: number[] = Array.from(range(n)); + const cycles: number[] = Array.from(range(n, n - x, -1)); + const poolgetter = (i: number) => pool[i]; + + yield indices.slice(0, x).map(poolgetter); + + while (n > 0) { + let cleanExit = true; + for (const i of range(x - 1, -1, -1)) { + cycles[i] -= 1; + if (cycles[i] === 0) { + indices = indices + .slice(0, i) + .concat(indices.slice(i + 1)) + .concat(indices.slice(i, i + 1)); + cycles[i] = n - i; + } else { + const j: number = cycles[i]; + + const [p, q] = [indices[indices.length - j], indices[i]]; + indices[i] = p; + indices[indices.length - j] = q; + yield indices.slice(0, x).map(poolgetter); + cleanExit = false; + break; + } } - let indices: number[] = Array.from(range(n)); - const cycles: number[] = Array.from(range(n, n - x, -1)); - const poolgetter = (i: number) => pool[i]; - - yield indices.slice(0, x).map(poolgetter); - - while (n > 0) { - let cleanExit = true; - for (const i of range(x - 1, -1, -1)) { - cycles[i] -= 1; - if (cycles[i] === 0) { - indices = indices - .slice(0, i) - .concat(indices.slice(i + 1)) - .concat(indices.slice(i, i + 1)); - cycles[i] = n - i; - } else { - const j: number = cycles[i]; - - const [p, q] = [indices[indices.length - j], indices[i]]; - indices[i] = p; - indices[indices.length - j] = q; - yield indices.slice(0, x).map(poolgetter); - cleanExit = false; - break; - } - } - - if (cleanExit) { - return; - } + if (cleanExit) { + return; } + } } /** @@ -383,15 +383,15 @@ export function* permutations(iterable: Iterable, r?: number): Iterable(thing: T, times?: number): Iterable { - if (times === undefined) { - for (;;) { - yield thing; - } - } else { - for (const _ of range(times)) { - yield thing; - } + if (times === undefined) { + for (;;) { + yield thing; } + } else { + for (const _ of range(times)) { + yield thing; + } + } } /** @@ -399,28 +399,28 @@ export function* repeat(thing: T, times?: number): Iterable { * predicate is true. */ export function* takewhile(iterable: Iterable, predicate: Predicate): Iterable { - for (const value of iterable) { - if (!predicate(value)) return; - yield value; - } + for (const value of iterable) { + if (!predicate(value)) return; + yield value; + } } export function zipLongest2(xs: Iterable, ys: Iterable, filler?: D): Array<[T1 | D, T2 | D]> { - return Array.from(izipLongest2(xs, ys, filler)); + return Array.from(izipLongest2(xs, ys, filler)); } export function zipLongest3( - xs: Iterable, - ys: Iterable, - zs: Iterable, - filler?: D + xs: Iterable, + ys: Iterable, + zs: Iterable, + filler?: D, ): Array<[T1 | D, T2 | D, T3 | D]> { - return Array.from(izipLongest3(xs, ys, zs, filler)); + return Array.from(izipLongest3(xs, ys, zs, filler)); } export const izipLongest = izipLongest2; export const zipLongest = zipLongest2; export function zipMany(...iters: Iterable[]): T[][] { - return Array.from(izipMany(...iters)); + return Array.from(izipMany(...iters)); } diff --git a/src/more-itertools.ts b/src/more-itertools.ts index f159b335..5618f5e0 100644 --- a/src/more-itertools.ts +++ b/src/more-itertools.ts @@ -1,7 +1,7 @@ -import { iter, map } from './builtins'; -import { izip, repeat } from './itertools'; -import type { Predicate, Primitive } from './types'; -import { primitiveIdentity } from './utils'; +import { iter, map } from "./builtins"; +import { izip, repeat } from "./itertools"; +import type { Predicate, Primitive } from "./types"; +import { primitiveIdentity } from "./utils"; /** * Break iterable into lists of length `size`: @@ -16,20 +16,20 @@ import { primitiveIdentity } from './utils'; * // [[1, 2, 3], [4, 5, 6], [7, 8]] */ export function* chunked(iterable: Iterable, size: number): Iterable { - if (size < 1) { - throw new Error(`Invalid chunk size: ${size}`); - } + if (size < 1) { + throw new Error(`Invalid chunk size: ${size}`); + } - const it = iter(iterable); - for (;;) { - const chunk = take(size, it); - if (chunk.length > 0) { - yield chunk; - } - if (chunk.length < size) { - return; - } + const it = iter(iterable); + for (;;) { + const chunk = take(size, it); + if (chunk.length > 0) { + yield chunk; + } + if (chunk.length < size) { + return; } + } } /** @@ -40,11 +40,11 @@ export function* chunked(iterable: Iterable, size: number): Iterable * */ export function* flatten(iterableOfIterables: Iterable>): Iterable { - for (const iterable of iterableOfIterables) { - for (const item of iterable) { - yield item; - } + for (const iterable of iterableOfIterables) { + for (const item of iterable) { + yield item; } + } } /** @@ -55,9 +55,9 @@ export function* flatten(iterableOfIterables: Iterable>): Iterabl * */ export function intersperse(value: V, iterable: Iterable): Iterable { - const stream = flatten(izip(repeat(value), iterable)); - take(1, stream); // eat away and discard the first value from the output - return stream; + const stream = flatten(izip(repeat(value), iterable)); + take(1, stream); // eat away and discard the first value from the output + return stream; } /** @@ -65,17 +65,17 @@ export function intersperse(value: V, iterable: Iterable): Iterable(n: number, iterable: Iterable): Iterable { - const it = iter(iterable); - let count = n; - while (count-- > 0) { - const s = it.next(); - if (!s.done) { - yield s.value; - } else { - // Iterable exhausted, quit early - return; - } + const it = iter(iterable); + let count = n; + while (count-- > 0) { + const s = it.next(); + if (!s.done) { + yield s.value; + } else { + // Iterable exhausted, quit early + return; } + } } /** @@ -88,17 +88,17 @@ export function* itake(n: number, iterable: Iterable): Iterable { * */ export function* pairwise(iterable: Iterable): Iterable<[T, T]> { - const it = iter(iterable); - const first = it.next(); - if (first.done) { - return; - } + const it = iter(iterable); + const first = it.next(); + if (first.done) { + return; + } - let r1: T = first.value; - for (const r2 of it) { - yield [r1, r2]; - r1 = r2; - } + let r1: T = first.value; + for (const r2 of it) { + yield [r1, r2]; + r1 = r2; + } } /** @@ -116,23 +116,23 @@ export function* pairwise(iterable: Iterable): Iterable<[T, T]> { * */ export function partition( - iterable: Iterable, - predicate: (item: T) => item is N + iterable: Iterable, + predicate: (item: T) => item is N, ): [N[], Exclude[]]; export function partition(iterable: Iterable, predicate: Predicate): [T[], T[]]; export function partition(iterable: Iterable, predicate: Predicate): [T[], T[]] { - const good = []; - const bad = []; + const good = []; + const bad = []; - for (const item of iterable) { - if (predicate(item)) { - good.push(item); - } else { - bad.push(item); - } + for (const item of iterable) { + if (predicate(item)) { + good.push(item); + } else { + bad.push(item); } + } - return [good, bad]; + return [good, bad]; } /** @@ -143,29 +143,29 @@ export function partition(iterable: Iterable, predicate: Predicate): [T * [1, 4, 5, 2, 6, 3, 7, 8] */ export function* roundrobin(...iters: Iterable[]): Iterable { - // We'll only keep lazy versions of the input iterables in here that we'll - // slowly going to exhaust. Once an iterable is exhausted, it will be - // removed from this list. Once the entire list is empty, this algorithm - // ends. - const iterables: Array> = map(iters, iter); + // We'll only keep lazy versions of the input iterables in here that we'll + // slowly going to exhaust. Once an iterable is exhausted, it will be + // removed from this list. Once the entire list is empty, this algorithm + // ends. + const iterables: Array> = map(iters, iter); - while (iterables.length > 0) { - let index = 0; - while (index < iterables.length) { - const it = iterables[index]; - const result = it.next(); + while (iterables.length > 0) { + let index = 0; + while (index < iterables.length) { + const it = iterables[index]; + const result = it.next(); - if (!result.done) { - yield result.value; - index++; - } else { - // This iterable is exhausted, make sure to remove it from the - // list of iterables. We'll splice the array from under our - // feet, and NOT advancing the index counter. - iterables.splice(index, 1); // intentional side-effect! - } - } + if (!result.done) { + yield result.value; + index++; + } else { + // This iterable is exhausted, make sure to remove it from the + // list of iterables. We'll splice the array from under our + // feet, and NOT advancing the index counter. + iterables.splice(index, 1); // intentional side-effect! + } } + } } /** @@ -180,40 +180,40 @@ export function* roundrobin(...iters: Iterable[]): Iterable { * each round can decrease over time, rather than being filled with a filler. */ export function* heads(...iters: Array>): Iterable { - // We'll only keep lazy versions of the input iterables in here that we'll - // slowly going to exhaust. Once an iterable is exhausted, it will be - // removed from this list. Once the entire list is empty, this algorithm - // ends. - const iterables: Array> = map(iters, iter); + // We'll only keep lazy versions of the input iterables in here that we'll + // slowly going to exhaust. Once an iterable is exhausted, it will be + // removed from this list. Once the entire list is empty, this algorithm + // ends. + const iterables: Array> = map(iters, iter); - while (iterables.length > 0) { - let index = 0; - const round = []; - while (index < iterables.length) { - const it = iterables[index]; - const result = it.next(); + while (iterables.length > 0) { + let index = 0; + const round = []; + while (index < iterables.length) { + const it = iterables[index]; + const result = it.next(); - if (!result.done) { - round.push(result.value); - index++; - } else { - // This iterable is exhausted, make sure to remove it from the - // list of iterables. We'll splice the array from under our - // feet, and NOT advancing the index counter. - iterables.splice(index, 1); // intentional side-effect! - } - } - if (round.length > 0) { - yield round; - } + if (!result.done) { + round.push(result.value); + index++; + } else { + // This iterable is exhausted, make sure to remove it from the + // list of iterables. We'll splice the array from under our + // feet, and NOT advancing the index counter. + iterables.splice(index, 1); // intentional side-effect! + } + } + if (round.length > 0) { + yield round; } + } } /** * Non-lazy version of itake(). */ export function take(n: number, iterable: Iterable): T[] { - return Array.from(itake(n, iterable)); + return Array.from(itake(n, iterable)); } /** @@ -226,17 +226,17 @@ export function take(n: number, iterable: Iterable): T[] { * */ export function* uniqueEverseen( - iterable: Iterable, - keyFn: (item: T) => Primitive = primitiveIdentity + iterable: Iterable, + keyFn: (item: T) => Primitive = primitiveIdentity, ): Iterable { - const seen = new Set(); - for (const item of iterable) { - const key = keyFn(item); - if (!seen.has(key)) { - seen.add(key); - yield item; - } + const seen = new Set(); + for (const item of iterable) { + const key = keyFn(item); + if (!seen.has(key)) { + seen.add(key); + yield item; } + } } /** @@ -249,15 +249,15 @@ export function* uniqueEverseen( * */ export function* uniqueJustseen( - iterable: Iterable, - keyFn: (item: T) => Primitive = primitiveIdentity + iterable: Iterable, + keyFn: (item: T) => Primitive = primitiveIdentity, ): Iterable { - let last = undefined; - for (const item of iterable) { - const key = keyFn(item); - if (key !== last) { - yield item; - last = key; - } + let last = undefined; + for (const item of iterable) { + const key = keyFn(item); + if (key !== last) { + yield item; + last = key; } + } } diff --git a/src/utils.ts b/src/utils.ts index 3ee297f7..da283a99 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,42 +1,42 @@ -import type { Primitive } from './types'; +import type { Primitive } from "./types"; type CmpFn = (a: T, b: T) => number; export function keyToCmp(keyFn: (item: T) => Primitive): CmpFn { - return (a: T, b: T) => { - const ka = keyFn(a); - const kb = keyFn(b); - // istanbul ignore else -- @preserve - if (typeof ka === 'boolean' && typeof kb === 'boolean') { - return ka === kb ? 0 : !ka && kb ? -1 : 1; - } else if (typeof ka === 'number' && typeof kb === 'number') { - return ka - kb; - } else if (typeof ka === 'string' && typeof kb === 'string') { - return ka === kb ? 0 : ka < kb ? -1 : 1; - } else { - return -1; - } - }; + return (a: T, b: T) => { + const ka = keyFn(a); + const kb = keyFn(b); + // istanbul ignore else -- @preserve + if (typeof ka === "boolean" && typeof kb === "boolean") { + return ka === kb ? 0 : !ka && kb ? -1 : 1; + } else if (typeof ka === "number" && typeof kb === "number") { + return ka - kb; + } else if (typeof ka === "string" && typeof kb === "string") { + return ka === kb ? 0 : ka < kb ? -1 : 1; + } else { + return -1; + } + }; } export function identityPredicate(x: unknown): boolean { - return !!x; + return !!x; } export function numberIdentity(x: unknown): number { - // istanbul ignore if -- @preserve - if (typeof x !== 'number') { - throw new Error('Inputs must be numbers'); - } - return x; + // istanbul ignore if -- @preserve + if (typeof x !== "number") { + throw new Error("Inputs must be numbers"); + } + return x; } export function primitiveIdentity

(x: P): P; export function primitiveIdentity(x: unknown): Primitive; export function primitiveIdentity(x: unknown): Primitive { - // istanbul ignore if -- @preserve - if (typeof x !== 'string' && typeof x !== 'number' && typeof x !== 'boolean') { - throw new Error('Please provide a key function that can establish object identity'); - } - return x; + // istanbul ignore if -- @preserve + if (typeof x !== "string" && typeof x !== "number" && typeof x !== "boolean") { + throw new Error("Please provide a key function that can establish object identity"); + } + return x; }