diff --git a/package.json b/package.json index 28f8ccb3..485805b5 100644 --- a/package.json +++ b/package.json @@ -1,69 +1,69 @@ { - "name": "itertools", - "version": "2.1.2", - "description": "A JavaScript port of Python's awesome itertools standard library", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/nvie/itertools.git" - }, - "author": "Vincent Driessen", - "homepage": "https://github.com/nvie/itertools#readme", - "bugs": { - "url": "https://github.com/nvie/itertools/issues" - }, - "types": "./dist/index.d.ts", - "main": "./dist/index.js", - "exports": { - ".": { - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.ts", - "module": "./dist/index.mjs", - "default": "./dist/index.js" - } - } - }, - "scripts": { - "actually-prepublish": "echo \"\\n\\nPlease run \\`bin/publish.sh \\` instead.\\n\\n\" && exit 2", - "build": "tsup", - "clean": "rimraf dist", - "format": "prettier --write src/", - "lint": "npm run lint:eslint && npm run lint:prettier", - "lint:eslint": "eslint --report-unused-disable-directives src/", - "lint:prettier": "prettier --list-different src/", - "lint:package": "publint --strict && attw --pack", - "prepublish": "in-publish && npm run actually-prepublish || not-in-publish", - "test": "vitest run --coverage" - }, - "files": [ - "dist/", - "LICENSE", - "README.md" - ], - "keywords": [ - "itertool", - "itertools", - "node-itertools" - ], - "devDependencies": { - "@arethetypeswrong/cli": "^0.13.5", - "@typescript-eslint/eslint-plugin": "^5.58.0", - "@typescript-eslint/parser": "^5.58.0", - "@vitest/coverage-istanbul": "^1.1.0", - "eslint": "^8.56.0", - "fast-check": "^3.15.0", - "in-publish": "^2.0.1", - "prettier": "^3.1.1", - "publint": "^0.2.7", - "tsup": "^8.0.1", - "typescript": "^5.3.3", - "vite-tsconfig-paths": "^4.2.2", - "vitest": "^1.1.0" - }, - "githubUrl": "https://github.com/nvie/itertools", - "sideEffects": false + "name": "itertools", + "version": "2.1.2", + "description": "A JavaScript port of Python's awesome itertools standard library", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/nvie/itertools.git" + }, + "author": "Vincent Driessen", + "homepage": "https://github.com/nvie/itertools#readme", + "bugs": { + "url": "https://github.com/nvie/itertools/issues" + }, + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "module": "./dist/index.mjs", + "default": "./dist/index.js" + } + } + }, + "scripts": { + "actually-prepublish": "echo \"\\n\\nPlease run \\`bin/publish.sh \\` instead.\\n\\n\" && exit 2", + "build": "tsup", + "clean": "rimraf dist", + "format": "prettier --write src/ test/", + "lint": "npm run lint:eslint && npm run lint:prettier", + "lint:eslint": "eslint --report-unused-disable-directives src/ test/", + "lint:prettier": "prettier --list-different src/ test/", + "lint:package": "publint --strict && attw --pack", + "prepublish": "in-publish && npm run actually-prepublish || not-in-publish", + "test": "vitest run --coverage" + }, + "files": [ + "dist/", + "LICENSE", + "README.md" + ], + "keywords": [ + "itertool", + "itertools", + "node-itertools" + ], + "devDependencies": { + "@arethetypeswrong/cli": "^0.13.5", + "@typescript-eslint/eslint-plugin": "^5.58.0", + "@typescript-eslint/parser": "^5.58.0", + "@vitest/coverage-istanbul": "^1.1.0", + "eslint": "^8.56.0", + "fast-check": "^3.15.0", + "in-publish": "^2.0.1", + "prettier": "^3.1.1", + "publint": "^0.2.7", + "tsup": "^8.0.1", + "typescript": "^5.3.3", + "vite-tsconfig-paths": "^4.2.2", + "vitest": "^1.1.0" + }, + "githubUrl": "https://github.com/nvie/itertools", + "sideEffects": false } diff --git a/src/builtins.ts b/src/builtins.ts index 01599e22..b585b63f 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -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..8c474233 100644 --- a/src/custom.ts +++ b/src/custom.ts @@ -3,11 +3,11 @@ 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..a1aae079 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,55 +1,55 @@ export type { Predicate, Primitive } from './types'; export { - all, - any, - contains, - enumerate, - every, - filter, - iter, - map, - max, - min, - range, - reduce, - some, - sorted, - sum, - zip, - zip3, + 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, + 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, + 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..e8123e17 100644 --- a/src/itertools.ts +++ b/src/itertools.ts @@ -6,26 +6,26 @@ 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..61142787 100644 --- a/src/more-itertools.ts +++ b/src/more-itertools.ts @@ -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..5bdef49f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,40 +3,40 @@ 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; } diff --git a/test/builtins.test.ts b/test/builtins.test.ts index ec0feea9..94ea7b5a 100644 --- a/test/builtins.test.ts +++ b/test/builtins.test.ts @@ -1,423 +1,423 @@ 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, + 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); - }); - - 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 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 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); - }); + 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)); - } - ) - ); - }); + 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); - }); + 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'], - ]); - }); + 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([]); - }); - - 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('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('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([ + it('iter makes some iterable a one-time iterable', () => { + expect( + Array.from( + iter( + new Map([ [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([]); - // ... - }); + ]), + ), + ), + ).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([]); - }); - - 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('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']); + }); }); 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 }); - }); + 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 }); - }); + 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]); - }); + 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); - }); + 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]); - }); + 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); - }); - - 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 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); + }); }); 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([]); - }); + 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/test/custom.test.ts b/test/custom.test.ts index 78610500..bd2e19ad 100644 --- a/test/custom.test.ts +++ b/test/custom.test.ts @@ -3,44 +3,44 @@ import { compact, compactObject, flatmap } from '~/custom'; import { repeat } from '~/itertools'; describe('compact', () => { - it('compact w/ empty list', () => { - expect(compact([])).toEqual([]); - }); + 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({}); - }); + 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([]); - }); + 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/test/itertools.test.ts b/test/itertools.test.ts index d7f35210..71b122b7 100644 --- a/test/itertools.test.ts +++ b/test/itertools.test.ts @@ -1,21 +1,21 @@ 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, + chain, + compress, + count, + cycle, + dropwhile, + groupby, + ifilter, + imap, + islice, + permutations, + repeat, + takewhile, + zipLongest, + zipLongest3, + zipMany, } from '~/itertools'; import { take } from '~/more-itertools'; @@ -23,366 +23,366 @@ 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']); - }); + 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']); - }); + 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]); - }); - - 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('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 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']); - }); + 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([]); - }); - - 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([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]); - }); + 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([]); + + 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]); + }); }); 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([]); - }); + 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 - }); + it('icompress is tested through compress() tests', () => { + // 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 retains rich type info', () => { - const filtered = take(5, ifilter([3, 'hi', null, -7], isNum)); - expect(filtered).toEqual([3, -7]); - // ^^^^^^^^ number[] - }); + 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 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]); - }); + 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(); - }); + 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 - }); + 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 - }); + it('izip3 is tested through zip3() tests (see builtins)', () => { + // This is okay + }); }); describe('izipMany', () => { - it('izipMany is tested through zipMany() tests', () => { - // This is okay - }); + it('izipMany is tested through zipMany() tests', () => { + // This is okay + }); }); describe('izipLongest', () => { - it('izipLongest is tested through zipLongest() tests', () => { - // This is okay - }); + 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([ + 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); - }); + 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([]); - }); + 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'], - ]); - }); + 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'], - ]); - }); + 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], - ]); - }); + 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/test/more-itertools.test.ts b/test/more-itertools.test.ts index 8dc78d99..69af3246 100644 --- a/test/more-itertools.test.ts +++ b/test/more-itertools.test.ts @@ -2,16 +2,16 @@ 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, + chunked, + flatten, + heads, + intersperse, + pairwise, + partition, + roundrobin, + take, + uniqueEverseen, + uniqueJustseen, } from '~/more-itertools'; import * as fc from 'fast-check'; @@ -19,326 +19,326 @@ 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); - } - ) - ); - }); + 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); - }); + 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); - }); + 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']); - }); + 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([]); - }); - - 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 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]); + }); }); describe('itake', () => { - it('itake is tested through take() tests', () => { - // This is okay - }); + 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], - ]); - }); + 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)[] - }); + 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]); - }); + 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]]); - }); + 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); - }); + 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([]); - }); - - 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('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('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']); - }); + 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']); + }); });