/* eslint-disable @typescript-eslint/no-explicit-any */ /** Returns an array of numbers from start to end (inclusive). */ export function interval(start: number, end: number, step = 1): number[] { const result = []; for (let i = start; i <= end; i += step) { result.push(i); } return result; } type Product[]> = A extends [infer First, ...infer Rest] ? First extends Iterable ? Rest extends Iterable[] ? [F, ...Product] : never : never : number extends A["length"] ? A extends Iterable[] ? F[] : never : []; /** A fixed-length tuple with the given fill. */ export type TupleOf< Dimensions extends number, Fill, Tuple extends readonly unknown[] = [], > = number extends Dimensions ? Fill[] : Dimensions extends Tuple["length"] ? Tuple : TupleOf; /** Cartesian product of iterables. */ function* product[]>( iters: Iters, ): Generator> { if (iters.length === 1) { for (const arg of iters[0]!) { yield [arg] as unknown as Product; } return; } const [first, ...rest] = iters; for (const a of first!) { for (const b of product(rest)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment yield [a, ...b] as Product; } } } type IterMap = A extends [infer First, ...infer Rest] ? [Iterable, ...IterMap] : []; /** Map a function to a product of iterators. */ export function* mapProduct( fn: (...args: Args) => R, ...args: IterMap ): Generator { for (const arg of product(args)) { yield fn(...(arg as unknown as Args)); } } const aCharCode = "a".charCodeAt(0); /** Caesar shift a slug. */ export function caesar(slug: string, n: number): string { n %= 26; return Array.from(slug, (c) => { return String.fromCharCode( ((c.charCodeAt(0) - aCharCode + n + 26) % 26) + aCharCode, ); }).join(""); } /** Returns an iterator over [index, item] pairs. */ export function* enumerate(iter: Iterable): Generator<[number, T]> { let i = 0; for (const item of iter) { yield [i, item]; i += 1; } } /** Returns an iterator over windows of the given size. */ export function windows(iter: Iterable, size: 2): Generator<[T, T]>; export function windows(iter: Iterable, size: 3): Generator<[T, T, T]>; export function windows(iter: Iterable, size: number): Generator; export function* windows(iter: Iterable, size: number) { const buffer = []; for (const item of iter) { if (buffer.length === size) { buffer.shift(); } buffer.push(item); if (buffer.length === size) { yield buffer.slice(); } } } /** * If the given array is a non-constant arithmetic sequence, returns the start, * step, and last. Else, returns null. */ export function getArithmeticSequenceInfo(terms: number[]): { start: number; step: number; last: number; } | null { if (terms.length < 2) { return null; } terms.sort((a, b) => a - b); const start = terms[0]!; const step = terms[1]! - start; if (step === 0) { return null; } for (let i = 2; i < terms.length; i++) { if (terms[i]! - terms[i - 1]! !== step) { return null; } } return { start, step, last: terms.at(-1)! }; } /** Accumulate a function over an iterable. */ export function accumulate( iter: Iterable, initial: R, fn: (acc: R, item: T) => R, ): R[] { const result = []; let acc = initial; for (const item of iter) { acc = fn(acc, item); result.push(acc); } return result; }