src/Seq.ts
File `Seq.ts` has 529 lines of code (exceeds 250 allowed). Consider refactoring.import { identity } from "@tdreyno/figment" export const DONE = Symbol() type Tramp<T> = () => typeof DONE | T `Seq` has 45 functions (exceeds 20 allowed). Consider refactoring.export class Seq<T> { public static MAX_YIELDS = 1_000_000 private yields_ = 0 constructor(private source_: () => Tramp<T>) {} public map<U>(fn: (value: T, index: number) => U): Seq<U> { return new Seq(() => { const parentNext = this.createTrampoline_() let counter = 0 return (): typeof DONE | U => { const result = parentNext() if (result === DONE) { return DONE } return fn(result, counter++) } }) } public window(size: number, allowPartialWindow = true): Seq<T[]> { // eslint-disable-next-line @typescript-eslint/no-this-alias const self: Seq<T> = this return new Seq(() => { let head: Seq<T> = self return (): typeof DONE | T[] => { const items = head.take(size).toArray() if (!allowPartialWindow && items.length < size) { return DONE } head = head.skip(size) return items } }) } public pairwise(): Seq<[T, T]> { return this.window(2, false) as unknown as Seq<[T, T]> } public isEmpty(): boolean { const next = this.createTrampoline_() return next() === DONE } public tap(fn: (value: T, index: number) => void): Seq<T> { return this.map((v, k) => { fn(v, k) return v }) } public log(): Seq<T> { return this.tap((v, k) => console.log([k, v])) } public flatMap<U>(fn: (value: T, index: number) => U[]): Seq<U> { return this.map(fn).flat() } public flat<U>(this: Seq<U[]>): Seq<U> { return new Seq(() => { const next = this.createTrampoline_() let items = next() let counter = 0 return (): typeof DONE | U => { if (items === DONE) { return DONE } if (counter >= items.length) { items = next() counter = 0 } if (items === DONE) { return DONE } return items[counter++] } }) } public filter(fn: (value: T, index: number) => unknown): Seq<T> { return new Seq(() => { const next = this.createTrampoline_() let counter = 0 return (): typeof DONE | T => { while (true) { const item = next() if (item === DONE) { return DONE } if (fn(item, counter++)) { return item } } } }) } public compact<C>(): Seq<C> { return this.filter(identity) as unknown as Seq<C> } public concat(...tail: Array<Seq<T>>): Seq<T> { return new Seq(() => { const nexts = [ this.createTrampoline_(), ...tail.map(s => s.createTrampoline_()), ] return (): typeof DONE | T => { while (true) { if (nexts.length === 0) { return DONE } const currentSeq = nexts[0] const item = currentSeq() if (item === DONE) { nexts.shift() continue } return item } } }) } public interleave(...tail: Array<Seq<T>>): Seq<T> { return new Seq(() => { const nexts = [this.source_(), ...tail.map(s => s.source_())] let index = 0 return (): typeof DONE | T => { while (true) { if (nexts.length === 0) { return DONE } const boundedIndex = index++ % nexts.length const next = nexts[boundedIndex] const item = next() if (item === DONE) { nexts.splice(boundedIndex, 1) continue } return item } } }) } public interpose(separator: T): Seq<T> { return new Seq(() => { const next = this.createTrampoline_() let backPressure: T | undefined let lastWasSep = true return (): typeof DONE | T => { if (backPressure) { const previousItem = backPressure backPressure = undefined lastWasSep = false return previousItem } const item = next() if (item === DONE) { return DONE } if (!lastWasSep) { lastWasSep = true backPressure = item return separator } lastWasSep = false return item } }) } public distinctBy<U>(fn: (value: T) => U): Seq<T> { const seen = new Set<U>() return this.filter(value => { const compareBy = fn(value) if (seen.has(compareBy)) { return false } seen.add(compareBy) return true }) } public distinct(): Seq<T> { return this.distinctBy(identity) } Function `partitionBy` has 52 lines of code (exceeds 25 allowed). Consider refactoring. public partitionBy( fn: (value: T, index: number) => unknown, ): [Seq<T>, Seq<T>] { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this const trueBackpressure: T[] = [] const falseBackpressure: T[] = [] let previousSource: ReturnType<typeof self.source_> | undefined const singletonTrampoline = (): Tramp<T> => { if (!previousSource) { previousSource = self.createTrampoline_() } return previousSource } return [ new Seq(() => { const next = singletonTrampoline() let counter = 0 return (): typeof DONE | T => { while (true) { if (trueBackpressure.length > 0) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return trueBackpressure.shift()! } const item = next() if (item === DONE) { return DONE } if (fn(item, counter++)) { return item } else { falseBackpressure.push(item) } } } }), new Seq(() => { const next = singletonTrampoline() let counter = 0 return (): typeof DONE | T => { while (true) { if (falseBackpressure.length > 0) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return falseBackpressure.shift()! } const item = next() if (item === DONE) { return DONE } if (!fn(item, counter++)) { return item } else { trueBackpressure.push(item) } } } }), ] } public includes(value: T): boolean { return !!this.find(a => a === value) } public find(fn: (value: T, index: number) => unknown): T | undefined { return this.filter(fn).first() } public reduce<A>(fn: (sum: A, value: T, index: number) => A, initial: A): A { const parentNext = this.createTrampoline_() let counter = 0 let current: A = initial while (true) { const result = parentNext() if (result === DONE) { return current } current = fn(current, result, counter++) } } public join(this: Seq<string>, separator = ","): string { return this.reduce((sum, str) => sum + str + separator, "").slice(0, -1) } public pipe<U>(fn: (value: Seq<T>) => U): U { return fn(this) } public some(fn: (value: T, index: number) => unknown): boolean { const next = this.createTrampoline_() let counter = 0 while (true) { const item = next() if (item === DONE) { return false } if (fn(item, counter++)) { return true } } } public every(fn: (value: T, index: number) => unknown): boolean { const next = this.createTrampoline_() let counter = 0 while (true) { const item = next() if (item === DONE) { return true } if (!fn(item, counter++)) { return false } } } public takeWhile(fn: (value: T, index: number) => unknown): Seq<T> { return new Seq(() => { const next = this.createTrampoline_() let counter = 0 return (): typeof DONE | T => { const item = next() if (item === DONE) { return DONE } if (!fn(item, counter++)) { return DONE } return item } }) } public take(num: number): Seq<T> { return new Seq(() => { const next = this.createTrampoline_() let i = 0 return (): typeof DONE | T => { if (i++ >= num) { return DONE } const item = next() if (item === DONE) { return DONE } return item } }) } public skipWhile(fn: (value: T, index: number) => unknown): Seq<T> { return new Seq(() => { const next = this.createTrampoline_() let counter = 0 return (): typeof DONE | T => { while (true) { const item = next() if (item === DONE) { return DONE } if (fn(item, counter++)) { continue } return item } } }) } public skip(num: number): Seq<T> { return new Seq(() => { const next = this.createTrampoline_() let doneSkipping = false return (): typeof DONE | T => { if (!doneSkipping) { for (let i = 0; i < num; i++) { const skippedItem = next() if (skippedItem === DONE) { return DONE } } doneSkipping = true } const item = next() if (item === DONE) { return DONE } return item } }) } public nth(i: number): T | undefined { return this.skip(i - 1).first() } public index(i: number): T | undefined { return this.skip(i).first() } public first(): T | undefined { const next = this.createTrampoline_() const item = next() if (item === DONE) { return undefined } return item } public zipWith<T2, T3>(Similar blocks of code found in 2 locations. Consider refactoring. fn: ( [result1, result2]: [T, T2] | [T, undefined] | [undefined, T2], index: number, ) => T3, seq2: Seq<T2>, ): Seq<T3> { return new Seq(() => { const next1 = this.createTrampoline_() const next2 = seq2.createTrampoline_() let counter = 0 return (): typeof DONE | T3 => { const result1 = next1() const result2 = next2() if (result1 === DONE && result2 === DONE) { return DONE } if (result1 === DONE && result2 !== DONE) { return fn([undefined, result2], counter++) } if (result1 !== DONE && result2 === DONE) { return fn([result1, undefined], counter++) } return fn([result1 as T, result2 as T2], counter++) } }) } public zip<T2>(seq2: Seq<T2>): Seq<[T | undefined, T2 | undefined]> { return this.zipWith(identity, seq2) } Function `zip2With` has 33 lines of code (exceeds 25 allowed). Consider refactoring. public zip2With<T2, T3, T4>(Similar blocks of code found in 2 locations. Consider refactoring. fn: ( [result1, result2, result3]: | [T, T2, T3] | [T, undefined, undefined] | [T, T2, undefined] | [T, undefined, T3] | [undefined, T2, undefined] | [undefined, T2, T3] | [undefined, undefined, T3], index: number, ) => T4, seq2: Seq<T2>, seq3: Seq<T3>, ): Seq<T4> { return new Seq(() => { const next1 = this.createTrampoline_() const next2 = seq2.createTrampoline_() const next3 = seq3.createTrampoline_() let counter = 0 return (): typeof DONE | T4 => { const result1 = next1() const result2 = next2() const result3 = next3() if (result1 === DONE && result2 === DONE && result3 === DONE) { return DONE } if (result1 !== DONE && result2 === DONE && result3 === DONE) { return fn([result1, undefined, undefined], counter++) } if (result1 !== DONE && result2 !== DONE && result3 === DONE) { return fn([result1, result2, undefined], counter++) } if (result1 !== DONE && result2 === DONE && result3 !== DONE) { return fn([result1, undefined, result3], counter++) } if (result1 === DONE && result2 !== DONE && result3 === DONE) {Avoid too many `return` statements within this function. return fn([undefined, result2, undefined], counter++) } if (result1 === DONE && result2 !== DONE && result3 !== DONE) {Avoid too many `return` statements within this function. return fn([undefined, result2, result3], counter++) } if (result1 === DONE && result2 === DONE && result3 !== DONE) {Avoid too many `return` statements within this function. return fn([undefined, undefined, result3], counter++) } Avoid too many `return` statements within this function. return fn([result1 as T, result2 as T2, result3 as T3], counter++) } }) } public zip2<T2, T3>( seq2: Seq<T2>, seq3: Seq<T3>, ): Seq<[T | undefined, T2 | undefined, T3 | undefined]> { return this.zip2With(identity, seq2, seq3) } public *[Symbol.iterator]() { const next = this.createTrampoline_() while (true) { const item = next() if (item === DONE) { return } yield item } } public toArray(): T[] { const next = this.createTrampoline_() const result: T[] = [] while (true) { const item = next() if (item === DONE) { return result } result.push(item) } } public forEach(fn: (value: T, index: number) => void): void { const next = this.createTrampoline_() let counter = 0 while (true) { const item = next() if (item === DONE) { return } fn(item, counter++) } } public sumBy(fn: (value: T) => number): number { return this.map(fn).reduce((sum, num) => sum + num, 0) } public sum(this: Seq<number>): number { return this.sumBy(identity) } public averageBy(fn: (value: T) => number): number { return this.map(fn).reduce((sum, num, i) => sum + (num - sum) / (i + 1), 0) } public average(this: Seq<number>): number { return this.averageBy(identity) } public frequencies(): Map<T, number> { return this.reduce((sum, value) => { if (sum.has(value)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return sum.set(value, sum.get(value)! + 1) } return sum.set(value, 0 + 1) }, new Map<T, number>()) } public groupBy<U>(fn: (item: T) => U): Map<U, T[]> { return this.reduce((sum, item) => { const group = fn(item) if (sum.has(group)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const currentArray = sum.get(group)! currentArray.push(item) return sum.set(group, currentArray) } return sum.set(group, [item]) }, new Map<U, T[]>()) } // Barely public. Do not use. public createTrampoline_(): () => typeof DONE | T { const nextCallback = this.source_() return (): typeof DONE | T => { const result = nextCallback() if (++this.yields_ > Seq.MAX_YIELDS) { throw new Error( `Seq has yielded ${this.yields_} times. If this is okay, set Seq.MAX_YIELDS to a higher number (currently ${Seq.MAX_YIELDS}).`, ) } return result } }}