From 24838b8f77c8dbc44d1dd61c0eb5e2ccf4cd6d8f Mon Sep 17 00:00:00 2001 From: Infendro Date: Mon, 27 Apr 2026 19:00:49 +0200 Subject: [PATCH] Implement Collection --- .gitea/workflows/main.yaml | 2 +- .npmrc | 2 +- package-lock.json | 4 +- package.json | 2 +- src/collection.ts | 434 ++++++++++++++++++++++++++++++ src/collection/all.ts | 6 + src/collection/any.ts | 6 + src/collection/associate.ts | 7 + src/collection/associateBy.ts | 7 + src/collection/associateWith.ts | 7 + src/collection/average.ts | 10 + src/collection/averageBy.ts | 10 + src/collection/averageByOrNull.ts | 8 + src/collection/averageOrNull.ts | 8 + src/collection/chunked.ts | 16 ++ src/collection/contains.ts | 6 + src/collection/containsAll.ts | 7 + src/collection/count.ts | 8 + src/collection/distinct.ts | 10 + src/collection/distinctBy.ts | 11 + src/collection/drop.ts | 10 + src/collection/dropUntil.ts | 11 + src/collection/dropWhile.ts | 11 + src/collection/filter.ts | 7 + src/collection/first.ts | 10 + src/collection/firstOrNull.ts | 8 + src/collection/flatMap.ts | 7 + src/collection/flatten.ts | 7 + src/collection/fold.ts | 7 + src/collection/forEach.ts | 5 + src/collection/get.ts | 6 + src/collection/getOrNull.ts | 4 + src/collection/groupBy.ts | 9 + src/collection/indices.ts | 7 + src/collection/intersect.ts | 8 + src/collection/isEmpty.ts | 3 + src/collection/isNotEmpty.ts | 3 + src/collection/joinToString.ts | 7 + src/collection/last.ts | 12 + src/collection/lastOrNull.ts | 10 + src/collection/map.ts | 7 + src/collection/mapNotNull.ts | 8 + src/collection/max.ts | 11 + src/collection/maxBy.ts | 16 ++ src/collection/maxByOrNull.ts | 14 + src/collection/maxOf.ts | 12 + src/collection/maxOfOrNull.ts | 10 + src/collection/maxOrNull.ts | 9 + src/collection/maxWith.ts | 11 + src/collection/maxWithOrNull.ts | 9 + src/collection/min.ts | 11 + src/collection/minBy.ts | 16 ++ src/collection/minByOrNull.ts | 14 + src/collection/minOf.ts | 12 + src/collection/minOfOrNull.ts | 10 + src/collection/minOrNull.ts | 9 + src/collection/minWith.ts | 11 + src/collection/minWithOrNull.ts | 9 + src/collection/minus.ts | 8 + src/collection/none.ts | 6 + src/collection/onEach.ts | 6 + src/collection/partition.ts | 9 + src/collection/plus.ts | 10 + src/collection/reduce.ts | 10 + src/collection/reduceOrNull.ts | 8 + src/collection/reversed.ts | 3 + src/collection/scan.ts | 9 + src/collection/single.ts | 18 ++ src/collection/singleOrNull.ts | 16 ++ src/collection/slice.ts | 15 ++ src/collection/sorted.ts | 3 + src/collection/sortedBy.ts | 3 + src/collection/sortedWith.ts | 3 + src/collection/sum.ts | 7 + src/collection/sumBy.ts | 7 + src/collection/take.ts | 11 + src/collection/takeUntil.ts | 8 + src/collection/takeWhile.ts | 8 + src/collection/toArray.ts | 3 + src/collection/toMap.ts | 3 + src/collection/toSet.ts | 3 + src/collection/unzip.ts | 9 + src/collection/windowed.ts | 22 ++ src/collection/withIndex.ts | 7 + src/collection/zip.ts | 13 + src/index.ts | 1 + src/util/error.ts | 3 + tsconfig.json | 9 +- 88 files changed, 1153 insertions(+), 10 deletions(-) create mode 100644 src/collection.ts create mode 100644 src/collection/all.ts create mode 100644 src/collection/any.ts create mode 100644 src/collection/associate.ts create mode 100644 src/collection/associateBy.ts create mode 100644 src/collection/associateWith.ts create mode 100644 src/collection/average.ts create mode 100644 src/collection/averageBy.ts create mode 100644 src/collection/averageByOrNull.ts create mode 100644 src/collection/averageOrNull.ts create mode 100644 src/collection/chunked.ts create mode 100644 src/collection/contains.ts create mode 100644 src/collection/containsAll.ts create mode 100644 src/collection/count.ts create mode 100644 src/collection/distinct.ts create mode 100644 src/collection/distinctBy.ts create mode 100644 src/collection/drop.ts create mode 100644 src/collection/dropUntil.ts create mode 100644 src/collection/dropWhile.ts create mode 100644 src/collection/filter.ts create mode 100644 src/collection/first.ts create mode 100644 src/collection/firstOrNull.ts create mode 100644 src/collection/flatMap.ts create mode 100644 src/collection/flatten.ts create mode 100644 src/collection/fold.ts create mode 100644 src/collection/forEach.ts create mode 100644 src/collection/get.ts create mode 100644 src/collection/getOrNull.ts create mode 100644 src/collection/groupBy.ts create mode 100644 src/collection/indices.ts create mode 100644 src/collection/intersect.ts create mode 100644 src/collection/isEmpty.ts create mode 100644 src/collection/isNotEmpty.ts create mode 100644 src/collection/joinToString.ts create mode 100644 src/collection/last.ts create mode 100644 src/collection/lastOrNull.ts create mode 100644 src/collection/map.ts create mode 100644 src/collection/mapNotNull.ts create mode 100644 src/collection/max.ts create mode 100644 src/collection/maxBy.ts create mode 100644 src/collection/maxByOrNull.ts create mode 100644 src/collection/maxOf.ts create mode 100644 src/collection/maxOfOrNull.ts create mode 100644 src/collection/maxOrNull.ts create mode 100644 src/collection/maxWith.ts create mode 100644 src/collection/maxWithOrNull.ts create mode 100644 src/collection/min.ts create mode 100644 src/collection/minBy.ts create mode 100644 src/collection/minByOrNull.ts create mode 100644 src/collection/minOf.ts create mode 100644 src/collection/minOfOrNull.ts create mode 100644 src/collection/minOrNull.ts create mode 100644 src/collection/minWith.ts create mode 100644 src/collection/minWithOrNull.ts create mode 100644 src/collection/minus.ts create mode 100644 src/collection/none.ts create mode 100644 src/collection/onEach.ts create mode 100644 src/collection/partition.ts create mode 100644 src/collection/plus.ts create mode 100644 src/collection/reduce.ts create mode 100644 src/collection/reduceOrNull.ts create mode 100644 src/collection/reversed.ts create mode 100644 src/collection/scan.ts create mode 100644 src/collection/single.ts create mode 100644 src/collection/singleOrNull.ts create mode 100644 src/collection/slice.ts create mode 100644 src/collection/sorted.ts create mode 100644 src/collection/sortedBy.ts create mode 100644 src/collection/sortedWith.ts create mode 100644 src/collection/sum.ts create mode 100644 src/collection/sumBy.ts create mode 100644 src/collection/take.ts create mode 100644 src/collection/takeUntil.ts create mode 100644 src/collection/takeWhile.ts create mode 100644 src/collection/toArray.ts create mode 100644 src/collection/toMap.ts create mode 100644 src/collection/toSet.ts create mode 100644 src/collection/unzip.ts create mode 100644 src/collection/windowed.ts create mode 100644 src/collection/withIndex.ts create mode 100644 src/collection/zip.ts create mode 100644 src/util/error.ts diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml index 3832093..58b2e04 100644 --- a/.gitea/workflows/main.yaml +++ b/.gitea/workflows/main.yaml @@ -30,4 +30,4 @@ jobs: - name: Publish run: npm publish env: - INFENDRO__TOKEN: ${{ secrets.token }} + TOKEN: ${{ secrets.token }} diff --git a/.npmrc b/.npmrc index fde131e..78de52a 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ @infendro:registry=https://git.infendro.com/api/packages/Infendro/npm/ -//git.infendro.com/api/packages/Infendro/npm/:_authToken="${INFENDRO__TOKEN}" +//git.infendro.com/api/packages/Infendro/npm/:_authToken="${TOKEN}" diff --git a/package-lock.json b/package-lock.json index 2722d00..017950a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@infendro/sequence", + "name": "@infendro/collection", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@infendro/sequence", + "name": "@infendro/collection", "version": "0.0.1", "devDependencies": { "typescript": "^6.0.3" diff --git a/package.json b/package.json index 9c22a76..42bc6ca 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@infendro/sequence", + "name": "@infendro/collection", "version": "0.0.1", "scripts": { "build": "tsc" diff --git a/src/collection.ts b/src/collection.ts new file mode 100644 index 0000000..f0749cf --- /dev/null +++ b/src/collection.ts @@ -0,0 +1,434 @@ +import {all} from "./collection/all"; +import {any} from "./collection/any"; +import {associate} from "./collection/associate"; +import {associateBy} from "./collection/associateBy"; +import {associateWith} from "./collection/associateWith"; +import {average} from "./collection/average"; +import {averageBy} from "./collection/averageBy"; +import {averageByOrNull} from "./collection/averageByOrNull"; +import {averageOrNull} from "./collection/averageOrNull"; +import {chunked} from "./collection/chunked"; +import {contains} from "./collection/contains"; +import {containsAll} from "./collection/containsAll"; +import {count} from "./collection/count"; +import {distinct} from "./collection/distinct"; +import {distinctBy} from "./collection/distinctBy"; +import {drop} from "./collection/drop"; +import {dropUntil} from "./collection/dropUntil"; +import {dropWhile} from "./collection/dropWhile"; +import {filter} from "./collection/filter"; +import {first} from "./collection/first"; +import {firstOrNull} from "./collection/firstOrNull"; +import {flatMap} from "./collection/flatMap"; +import {flatten} from "./collection/flatten"; +import {fold} from "./collection/fold"; +import {forEach} from "./collection/forEach"; +import {get} from "./collection/get"; +import {getOrNull} from "./collection/getOrNull"; +import {groupBy} from "./collection/groupBy"; +import {indices} from "./collection/indices"; +import {intersect} from "./collection/intersect"; +import {isEmpty} from "./collection/isEmpty"; +import {isNotEmpty} from "./collection/isNotEmpty"; +import {joinToString} from "./collection/joinToString"; +import {last} from "./collection/last"; +import {lastOrNull} from "./collection/lastOrNull"; +import {map} from "./collection/map"; +import {mapNotNull} from "./collection/mapNotNull"; +import {max} from "./collection/max"; +import {maxBy} from "./collection/maxBy"; +import {maxByOrNull} from "./collection/maxByOrNull"; +import {maxOf} from "./collection/maxOf"; +import {maxOfOrNull} from "./collection/maxOfOrNull"; +import {maxOrNull} from "./collection/maxOrNull"; +import {maxWith} from "./collection/maxWith"; +import {maxWithOrNull} from "./collection/maxWithOrNull"; +import {min} from "./collection/min"; +import {minBy} from "./collection/minBy"; +import {minByOrNull} from "./collection/minByOrNull"; +import {minOf} from "./collection/minOf"; +import {minOfOrNull} from "./collection/minOfOrNull"; +import {minOrNull} from "./collection/minOrNull"; +import {minWith} from "./collection/minWith"; +import {minWithOrNull} from "./collection/minWithOrNull"; +import {minus} from "./collection/minus"; +import {none} from "./collection/none"; +import {onEach} from "./collection/onEach"; +import {partition} from "./collection/partition"; +import {plus} from "./collection/plus"; +import {reduce} from "./collection/reduce"; +import {reduceOrNull} from "./collection/reduceOrNull"; +import {reversed} from "./collection/reversed"; +import {scan} from "./collection/scan"; +import {single} from "./collection/single"; +import {singleOrNull} from "./collection/singleOrNull"; +import {slice} from "./collection/slice"; +import {sorted} from "./collection/sorted"; +import {sortedBy} from "./collection/sortedBy"; +import {sortedWith} from "./collection/sortedWith"; +import {sum} from "./collection/sum"; +import {sumBy} from "./collection/sumBy"; +import {take} from "./collection/take"; +import {takeUntil} from "./collection/takeUntil"; +import {takeWhile} from "./collection/takeWhile"; +import {toArray} from "./collection/toArray"; +import {toMap} from "./collection/toMap"; +import {toSet} from "./collection/toSet"; +import {unzip} from "./collection/unzip"; +import {windowed} from "./collection/windowed"; +import {withIndex} from "./collection/withIndex"; +import {zip} from "./collection/zip"; + +export type Collection = { + [Symbol.iterator](): Iterator + + toArray(): T[] + toSet(): Set + toMap(this: Collection<[K, V]>): Map + + onEach(action: (it: T) => void): Collection + forEach(action: (it: T) => void): void + + isEmpty(): boolean + isNotEmpty(): boolean + count(predicate?: (it: T) => boolean): number + + indices(): Collection + withIndex(): Collection<[number, T]> + + contains(item: T): boolean + containsAll(items: Iterable): boolean + any(predicate: (it: T) => boolean): boolean + all(predicate: (it: T) => boolean): boolean + none(predicate: (it: T) => boolean): boolean + + get(index: number): T + getOrNull(index: number): T | null + first(predicate?: (it: T) => boolean): T + firstOrNull(predicate?: (it: T) => boolean): T | null + last(predicate?: (it: T) => boolean): T + lastOrNull(predicate?: (it: T) => boolean): T | null + single(predicate?: (it: T) => boolean): T + singleOrNull(predicate?: (it: T) => boolean): T | null + + reduce(accumulate: (acc: T, it: T) => T): T + reduceOrNull(accumulate: (acc: T, it: T) => T): T | null + fold(initial: R, accumulate: (acc: R, it: T) => R): R + scan(initial: R, accumulate: (acc: R, it: T) => R): Collection + sum(this: Collection): number + sumBy(transform: (it: T) => number): number + average(this: Collection): number + averageOrNull(this: Collection): number | null + averageBy(transform: (it: T) => number): number + averageByOrNull(transform: (it: T) => number): number | null + + min(this: Collection): number + minOrNull(this: Collection): number | null + minWith(compare: (a: T, b: T) => number): T + minWithOrNull(compare: (a: T, b: T) => number): T | null + minBy(transform: (it: T) => number): T + minByOrNull(transform: (it: T) => number): T | null + minOf(transform: (it: T) => number): number + minOfOrNull(transform: (it: T) => number): number | null + + max(this: Collection): number + maxOrNull(this: Collection): number | null + maxWith(compare: (a: T, b: T) => number): T + maxWithOrNull(compare: (a: T, b: T) => number): T | null + maxBy(transform: (it: T) => number): T + maxByOrNull(transform: (it: T) => number): T | null + maxOf(transform: (it: T) => number): number + maxOfOrNull(transform: (it: T) => number): number | null + + joinToString(separator: string, transform?: (it: T) => string): string + + plus(items: Iterable): Collection + minus(items: Iterable): Collection + intersect(items: Iterable): Collection + + filter(predicate: (it: T) => it is R): Collection + filter(predicate: (it: T) => boolean): Collection + partition(predicate: (it: T) => boolean): [T[], T[]] + distinct(): Collection + distinctBy(transform: (it: T) => K): Collection + + drop(count: number): Collection + dropWhile(predicate: (it: T) => boolean): Collection + dropUntil(predicate: (it: T) => boolean): Collection + take(count: number): Collection + takeWhile(predicate: (it: T) => boolean): Collection + takeUntil(predicate: (it: T) => boolean): Collection + slice(start: number, end?: number): Collection + + map(transform: (it: T) => R): Collection + mapNotNull(transform: (it: T) => R | null): Collection + flatten(this: Collection>): Collection + flatMap(transform: (it: T) => Iterable): Collection + + groupBy(transform: (it: T) => K): Collection<[K, T[]]> + associate(transform: (it: T) => [K, V]): Collection<[K, V]> + associateBy(transform: (it: T) => K): Collection<[K, T]> + associateWith(transform: (it: T) => V): Collection<[T, V]> + zip(items: Iterable): Collection<[T, B]> + unzip(this: Collection<[A, B]>): [A[], B[]] + + windowed(size: number, step?: number, partial?: boolean): Collection + chunked(size: number): Collection + + reversed(): Collection + sorted(this: Collection): Collection + sortedWith(compare: (a: T, b: T) => number): Collection + sortedBy(transform: (it: T) => number): Collection +} + +export function collection(value: Iterable): Collection { + return wrap([...value]); +} + +function wrap(value: T[]): Collection { + return { + [Symbol.iterator]() { + return value[Symbol.iterator](); + }, + toArray() { + return toArray(value); + }, + toSet() { + return toSet(value); + }, + toMap() { + return toMap(value as [K, V][]); + }, + onEach(action) { + return wrap(onEach(value, action)); + }, + forEach(action) { + forEach(value, action); + }, + isEmpty() { + return isEmpty(value); + }, + isNotEmpty() { + return isNotEmpty(value); + }, + count(predicate) { + return count(value, predicate); + }, + indices() { + return wrap(indices(value)); + }, + withIndex() { + return wrap(withIndex(value)); + }, + contains(item) { + return contains(value, item); + }, + containsAll(items) { + return containsAll(value, items); + }, + any(predicate) { + return any(value, predicate); + }, + all(predicate) { + return all(value, predicate); + }, + none(predicate) { + return none(value, predicate); + }, + get(index) { + return get(value, index); + }, + getOrNull(index) { + return getOrNull(value, index); + }, + first(predicate) { + return first(value, predicate); + }, + firstOrNull(predicate) { + return firstOrNull(value, predicate); + }, + last(predicate) { + return last(value, predicate); + }, + lastOrNull(predicate) { + return lastOrNull(value, predicate); + }, + single(predicate) { + return single(value, predicate); + }, + singleOrNull(predicate) { + return singleOrNull(value, predicate); + }, + reduce(accumulate) { + return reduce(value, accumulate); + }, + reduceOrNull(accumulate) { + return reduceOrNull(value, accumulate); + }, + fold(initial, accumulate) { + return fold(value, initial, accumulate); + }, + scan(initial, accumulate) { + return wrap(scan(value, initial, accumulate)); + }, + sum() { + return sum(value as number[]); + }, + sumBy(transform) { + return sumBy(value, transform); + }, + average() { + return average(value as number[]); + }, + averageOrNull() { + return averageOrNull(value as number[]); + }, + averageBy(transform) { + return averageBy(value, transform); + }, + averageByOrNull(transform) { + return averageByOrNull(value, transform); + }, + min() { + return min(value as number[]); + }, + minOrNull() { + return minOrNull(value as number[]); + }, + minWith(compare) { + return minWith(value, compare); + }, + minWithOrNull(compare) { + return minWithOrNull(value, compare); + }, + minBy(transform) { + return minBy(value, transform); + }, + minByOrNull(transform) { + return minByOrNull(value, transform); + }, + minOf(transform) { + return minOf(value, transform); + }, + minOfOrNull(transform) { + return minOfOrNull(value, transform); + }, + max() { + return max(value as number[]); + }, + maxOrNull() { + return maxOrNull(value as number[]); + }, + maxWith(compare) { + return maxWith(value, compare); + }, + maxWithOrNull(compare) { + return maxWithOrNull(value, compare); + }, + maxBy(transform) { + return maxBy(value, transform); + }, + maxByOrNull(transform) { + return maxByOrNull(value, transform); + }, + maxOf(transform) { + return maxOf(value, transform); + }, + maxOfOrNull(transform) { + return maxOfOrNull(value, transform); + }, + joinToString(separator, transform) { + return joinToString(value, separator, transform); + }, + plus(items) { + return wrap(plus(value, items)); + }, + minus(items) { + return wrap(minus(value, items)); + }, + intersect(items) { + return wrap(intersect(value, items)); + }, + filter(predicate: (it: T) => boolean) { + return wrap(filter(value, predicate)); + }, + partition(predicate) { + return partition(value, predicate); + }, + distinct() { + return wrap(distinct(value)); + }, + distinctBy(transform) { + return wrap(distinctBy(value, transform)); + }, + drop(count) { + return wrap(drop(value, count)); + }, + dropWhile(predicate) { + return wrap(dropWhile(value, predicate)); + }, + dropUntil(predicate) { + return wrap(dropUntil(value, predicate)); + }, + take(count) { + return wrap(take(value, count)); + }, + takeWhile(predicate) { + return wrap(takeWhile(value, predicate)); + }, + takeUntil(predicate) { + return wrap(takeUntil(value, predicate)); + }, + slice(start, end) { + return wrap(slice(value, start, end)); + }, + map(transform) { + return wrap(map(value, transform)); + }, + mapNotNull(transform) { + return wrap(mapNotNull(value, transform)); + }, + flatten() { + return wrap(flatten(value as R[][])); + }, + flatMap(transform) { + return wrap(flatMap(value, transform)); + }, + groupBy(transform) { + return wrap(groupBy(value, transform)); + }, + associate(transform) { + return wrap(associate(value, transform)); + }, + associateBy(transform) { + return wrap(associateBy(value, transform)); + }, + associateWith(transform) { + return wrap(associateWith(value, transform)); + }, + zip(items) { + return wrap(zip(value, items)); + }, + unzip() { + return unzip(value as [A, B][]); + }, + windowed(size, step, partial) { + return wrap(windowed(value, size, step, partial)); + }, + chunked(size) { + return wrap(chunked(value, size)); + }, + reversed() { + return wrap(reversed(value)); + }, + sorted() { + return wrap(sorted(value as number[])); + }, + sortedWith(compare) { + return wrap(sortedWith(value, compare)); + }, + sortedBy(transform) { + return wrap(sortedBy(value, transform)); + }, + }; +} diff --git a/src/collection/all.ts b/src/collection/all.ts new file mode 100644 index 0000000..6451727 --- /dev/null +++ b/src/collection/all.ts @@ -0,0 +1,6 @@ +export function all(value: T[], predicate: (it: T) => boolean): boolean { + for (const it of value) { + if (!predicate(it)) return false; + } + return true; +} diff --git a/src/collection/any.ts b/src/collection/any.ts new file mode 100644 index 0000000..27991bb --- /dev/null +++ b/src/collection/any.ts @@ -0,0 +1,6 @@ +export function any(value: T[], predicate: (it: T) => boolean): boolean { + for (const it of value) { + if (predicate(it)) return true; + } + return false; +} diff --git a/src/collection/associate.ts b/src/collection/associate.ts new file mode 100644 index 0000000..4cb5775 --- /dev/null +++ b/src/collection/associate.ts @@ -0,0 +1,7 @@ +export function associate(value: T[], transform: (it: T) => [K, V]): [K, V][] { + const result: [K, V][] = []; + for (const it of value) { + result.push(transform(it)); + } + return result; +} diff --git a/src/collection/associateBy.ts b/src/collection/associateBy.ts new file mode 100644 index 0000000..e046653 --- /dev/null +++ b/src/collection/associateBy.ts @@ -0,0 +1,7 @@ +export function associateBy(value: T[], transform: (it: T) => K): [K, T][] { + const result: [K, T][] = []; + for (const it of value) { + result.push([transform(it), it]); + } + return result; +} diff --git a/src/collection/associateWith.ts b/src/collection/associateWith.ts new file mode 100644 index 0000000..454edc3 --- /dev/null +++ b/src/collection/associateWith.ts @@ -0,0 +1,7 @@ +export function associateWith(value: T[], transform: (it: T) => V): [T, V][] { + const result: [T, V][] = []; + for (const it of value) { + result.push([it, transform(it)]); + } + return result; +} diff --git a/src/collection/average.ts b/src/collection/average.ts new file mode 100644 index 0000000..70c48e6 --- /dev/null +++ b/src/collection/average.ts @@ -0,0 +1,10 @@ +import {error} from "../util/error"; + +export function average(value: number[]): number { + if (value.length === 0) error(); + let sum = 0; + for (const it of value) { + sum += it; + } + return sum / value.length; +} diff --git a/src/collection/averageBy.ts b/src/collection/averageBy.ts new file mode 100644 index 0000000..5114823 --- /dev/null +++ b/src/collection/averageBy.ts @@ -0,0 +1,10 @@ +import {error} from "../util/error"; + +export function averageBy(value: T[], transform: (it: T) => number): number { + if (value.length === 0) error(); + let sum = 0; + for (const it of value) { + sum += transform(it); + } + return sum / value.length; +} diff --git a/src/collection/averageByOrNull.ts b/src/collection/averageByOrNull.ts new file mode 100644 index 0000000..8bd11ab --- /dev/null +++ b/src/collection/averageByOrNull.ts @@ -0,0 +1,8 @@ +export function averageByOrNull(value: T[], transform: (it: T) => number): number | null { + if (value.length === 0) return null; + let sum = 0; + for (const it of value) { + sum += transform(it); + } + return sum / value.length; +} diff --git a/src/collection/averageOrNull.ts b/src/collection/averageOrNull.ts new file mode 100644 index 0000000..48f267a --- /dev/null +++ b/src/collection/averageOrNull.ts @@ -0,0 +1,8 @@ +export function averageOrNull(value: number[]): number | null { + if (value.length === 0) return null; + let sum = 0; + for (const it of value) { + sum += it; + } + return sum / value.length; +} diff --git a/src/collection/chunked.ts b/src/collection/chunked.ts new file mode 100644 index 0000000..e941924 --- /dev/null +++ b/src/collection/chunked.ts @@ -0,0 +1,16 @@ +import {error} from "../util/error"; + +export function chunked(value: T[], size: number): T[][] { + if (size < 1) error(); + const result: T[][] = []; + let buffer: T[] = []; + for (const it of value) { + buffer.push(it); + if (buffer.length === size) { + result.push(buffer); + buffer = []; + } + } + if (buffer.length !== 0) result.push(buffer); + return result; +} diff --git a/src/collection/contains.ts b/src/collection/contains.ts new file mode 100644 index 0000000..ce1aaf2 --- /dev/null +++ b/src/collection/contains.ts @@ -0,0 +1,6 @@ +export function contains(value: T[], item: T): boolean { + for (const it of value) { + if (it === item) return true; + } + return false; +} diff --git a/src/collection/containsAll.ts b/src/collection/containsAll.ts new file mode 100644 index 0000000..ac76882 --- /dev/null +++ b/src/collection/containsAll.ts @@ -0,0 +1,7 @@ +export function containsAll(value: T[], items: Iterable): boolean { + const set = new Set(value); + for (const it of items) { + if (!set.has(it)) return false; + } + return true; +} diff --git a/src/collection/count.ts b/src/collection/count.ts new file mode 100644 index 0000000..d695a4e --- /dev/null +++ b/src/collection/count.ts @@ -0,0 +1,8 @@ +export function count(value: T[], predicate?: (it: T) => boolean): number { + if (!predicate) return value.length; + let count = 0; + for (const it of value) { + if (predicate(it)) count++; + } + return count; +} diff --git a/src/collection/distinct.ts b/src/collection/distinct.ts new file mode 100644 index 0000000..638fd45 --- /dev/null +++ b/src/collection/distinct.ts @@ -0,0 +1,10 @@ +export function distinct(value: T[]): T[] { + const result: T[] = []; + const set: Set = new Set(); + for (const it of value) { + if (set.has(it)) continue; + result.push(it); + set.add(it); + } + return result; +} diff --git a/src/collection/distinctBy.ts b/src/collection/distinctBy.ts new file mode 100644 index 0000000..be6d9c0 --- /dev/null +++ b/src/collection/distinctBy.ts @@ -0,0 +1,11 @@ +export function distinctBy(value: T[], transform: (it: T) => K): T[] { + const result: T[] = []; + const set: Set = new Set(); + for (const it of value) { + const x = transform(it); + if (set.has(x)) continue; + result.push(it); + set.add(x); + } + return result; +} diff --git a/src/collection/drop.ts b/src/collection/drop.ts new file mode 100644 index 0000000..6abbb53 --- /dev/null +++ b/src/collection/drop.ts @@ -0,0 +1,10 @@ +import {error} from "../util/error"; + +export function drop(value: T[], count: number): T[] { + if (count < 0) error(); + const result: T[] = []; + for (let i = count; i < value.length; i++) { + result.push(value[i]); + } + return result; +} diff --git a/src/collection/dropUntil.ts b/src/collection/dropUntil.ts new file mode 100644 index 0000000..1fd307a --- /dev/null +++ b/src/collection/dropUntil.ts @@ -0,0 +1,11 @@ +export function dropUntil(value: T[], predicate: (it: T) => boolean): T[] { + let index = 0; + while (index < value.length && !predicate(value[index])) { + index++; + } + const result: T[] = []; + for (let i = index; i < value.length; i++) { + result.push(value[i]); + } + return result; +} diff --git a/src/collection/dropWhile.ts b/src/collection/dropWhile.ts new file mode 100644 index 0000000..cd54078 --- /dev/null +++ b/src/collection/dropWhile.ts @@ -0,0 +1,11 @@ +export function dropWhile(value: T[], predicate: (it: T) => boolean): T[] { + let index = 0; + while (index < value.length && predicate(value[index])) { + index++; + } + const result: T[] = []; + for (let i = index; i < value.length; i++) { + result.push(value[i]); + } + return result; +} diff --git a/src/collection/filter.ts b/src/collection/filter.ts new file mode 100644 index 0000000..3f8d483 --- /dev/null +++ b/src/collection/filter.ts @@ -0,0 +1,7 @@ +export function filter(value: T[], predicate: (it: T) => boolean): T[] { + const result: T[] = []; + for (const it of value) { + if (predicate(it)) result.push(it); + } + return result; +} diff --git a/src/collection/first.ts b/src/collection/first.ts new file mode 100644 index 0000000..9198150 --- /dev/null +++ b/src/collection/first.ts @@ -0,0 +1,10 @@ +import {error} from "../util/error"; + +export function first(value: T[], predicate?: (it: T) => boolean): T { + if (value.length === 0) error(); + if (!predicate) return value[0]; + for (const it of value) { + if (predicate(it)) return it; + } + error(); +} diff --git a/src/collection/firstOrNull.ts b/src/collection/firstOrNull.ts new file mode 100644 index 0000000..51026a7 --- /dev/null +++ b/src/collection/firstOrNull.ts @@ -0,0 +1,8 @@ +export function firstOrNull(value: T[], predicate?: (it: T) => boolean): T | null { + if (value.length === 0) return null; + if (!predicate) return value[0]; + for (const it of value) { + if (predicate(it)) return it; + } + return null; +} diff --git a/src/collection/flatMap.ts b/src/collection/flatMap.ts new file mode 100644 index 0000000..4c0a667 --- /dev/null +++ b/src/collection/flatMap.ts @@ -0,0 +1,7 @@ +export function flatMap(value: T[], transform: (it: T) => Iterable): R[] { + const result: R[] = []; + for (const it of value) { + result.push(...transform(it)); + } + return result; +} diff --git a/src/collection/flatten.ts b/src/collection/flatten.ts new file mode 100644 index 0000000..7fea90a --- /dev/null +++ b/src/collection/flatten.ts @@ -0,0 +1,7 @@ +export function flatten(value: R[][]): R[] { + const result: R[] = []; + for (const it of value) { + result.push(...it); + } + return result; +} diff --git a/src/collection/fold.ts b/src/collection/fold.ts new file mode 100644 index 0000000..ecd5af0 --- /dev/null +++ b/src/collection/fold.ts @@ -0,0 +1,7 @@ +export function fold(value: T[], initial: R, accumulate: (acc: R, it: T) => R): R { + let acc = initial; + for (const it of value) { + acc = accumulate(acc, it); + } + return acc; +} diff --git a/src/collection/forEach.ts b/src/collection/forEach.ts new file mode 100644 index 0000000..9682f06 --- /dev/null +++ b/src/collection/forEach.ts @@ -0,0 +1,5 @@ +export function forEach(value: T[], action: (it: T) => void) { + for (const it of value) { + action(it); + } +} diff --git a/src/collection/get.ts b/src/collection/get.ts new file mode 100644 index 0000000..53cb48d --- /dev/null +++ b/src/collection/get.ts @@ -0,0 +1,6 @@ +import {error} from "../util/error"; + +export function get(value: T[], index: number): T { + if (index < 0 || index >= value.length) error(); + return value[index]; +} diff --git a/src/collection/getOrNull.ts b/src/collection/getOrNull.ts new file mode 100644 index 0000000..14b67cd --- /dev/null +++ b/src/collection/getOrNull.ts @@ -0,0 +1,4 @@ +export function getOrNull(value: T[], index: number): T | null { + if (index < 0 || index >= value.length) return null; + return value[index]; +} diff --git a/src/collection/groupBy.ts b/src/collection/groupBy.ts new file mode 100644 index 0000000..4de851e --- /dev/null +++ b/src/collection/groupBy.ts @@ -0,0 +1,9 @@ +export function groupBy(value: T[], transform: (it: T) => K): [K, T[]][] { + const map: Map = new Map(); + for (const it of value) { + const key = transform(it); + if (!map.has(key)) map.set(key, []); + map.get(key)!.push(it); + } + return [...map]; +} diff --git a/src/collection/indices.ts b/src/collection/indices.ts new file mode 100644 index 0000000..e2aab55 --- /dev/null +++ b/src/collection/indices.ts @@ -0,0 +1,7 @@ +export function indices(value: T[]): number[] { + const result: number[] = []; + for (let index = 0; index < value.length; index++) { + result.push(index); + } + return result; +} diff --git a/src/collection/intersect.ts b/src/collection/intersect.ts new file mode 100644 index 0000000..5567e03 --- /dev/null +++ b/src/collection/intersect.ts @@ -0,0 +1,8 @@ +export function intersect(value: T[], items: Iterable): T[] { + const result: T[] = []; + const set = new Set(items); + for (const it of value) { + if (set.has(it)) result.push(it); + } + return result; +} diff --git a/src/collection/isEmpty.ts b/src/collection/isEmpty.ts new file mode 100644 index 0000000..350bcf1 --- /dev/null +++ b/src/collection/isEmpty.ts @@ -0,0 +1,3 @@ +export function isEmpty(value: T[]): boolean { + return value.length === 0; +} diff --git a/src/collection/isNotEmpty.ts b/src/collection/isNotEmpty.ts new file mode 100644 index 0000000..7e1c7c2 --- /dev/null +++ b/src/collection/isNotEmpty.ts @@ -0,0 +1,3 @@ +export function isNotEmpty(value: T[]): boolean { + return value.length !== 0; +} diff --git a/src/collection/joinToString.ts b/src/collection/joinToString.ts new file mode 100644 index 0000000..cf9d390 --- /dev/null +++ b/src/collection/joinToString.ts @@ -0,0 +1,7 @@ +export function joinToString(value: T[], separator: string, transform?: (it: T) => string): string { + const transformed: string[] = []; + for (const it of value) { + transformed.push(transform ? transform(it) : String(it)); + } + return transformed.join(separator); +} diff --git a/src/collection/last.ts b/src/collection/last.ts new file mode 100644 index 0000000..cd2ead1 --- /dev/null +++ b/src/collection/last.ts @@ -0,0 +1,12 @@ +import {error} from "../util/error"; + +export function last(value: T[], predicate?: (it: T) => boolean): T { + if (value.length === 0) error(); + if (!predicate) return value[value.length - 1]; + let last: T | undefined; + for (const it of value) { + if (predicate(it)) last = it; + } + if (last === undefined) error(); + return last; +} diff --git a/src/collection/lastOrNull.ts b/src/collection/lastOrNull.ts new file mode 100644 index 0000000..9b6cad3 --- /dev/null +++ b/src/collection/lastOrNull.ts @@ -0,0 +1,10 @@ +export function lastOrNull(value: T[], predicate?: (it: T) => boolean): T | null { + if (value.length === 0) return null; + if (!predicate) return value[value.length - 1]; + let last: T | undefined; + for (const it of value) { + if (predicate(it)) last = it; + } + if (last === undefined) return null; + return last; +} diff --git a/src/collection/map.ts b/src/collection/map.ts new file mode 100644 index 0000000..0c82dae --- /dev/null +++ b/src/collection/map.ts @@ -0,0 +1,7 @@ +export function map(value: T[], transform: (it: T) => R): R[] { + const result: R[] = []; + for (const it of value) { + result.push(transform(it)); + } + return result; +} diff --git a/src/collection/mapNotNull.ts b/src/collection/mapNotNull.ts new file mode 100644 index 0000000..78de149 --- /dev/null +++ b/src/collection/mapNotNull.ts @@ -0,0 +1,8 @@ +export function mapNotNull(value: T[], transform: (it: T) => R | null): R[] { + const result: R[] = []; + for (const it of value) { + const transformed = transform(it); + if (transformed !== null) result.push(transformed); + } + return result; +} diff --git a/src/collection/max.ts b/src/collection/max.ts new file mode 100644 index 0000000..8652d2e --- /dev/null +++ b/src/collection/max.ts @@ -0,0 +1,11 @@ +import {error} from "../util/error"; + +export function max(value: number[]): number { + if (value.length === 0) error(); + let max = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (it > max) max = it; + } + return max; +} diff --git a/src/collection/maxBy.ts b/src/collection/maxBy.ts new file mode 100644 index 0000000..a985ad8 --- /dev/null +++ b/src/collection/maxBy.ts @@ -0,0 +1,16 @@ +import {error} from "../util/error"; + +export function maxBy(value: T[], transform: (it: T) => number): T { + if (value.length === 0) error(); + let max = value[0]; + let maxVal = transform(max); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val > maxVal) { + max = it; + maxVal = val; + } + } + return max; +} diff --git a/src/collection/maxByOrNull.ts b/src/collection/maxByOrNull.ts new file mode 100644 index 0000000..da163ca --- /dev/null +++ b/src/collection/maxByOrNull.ts @@ -0,0 +1,14 @@ +export function maxByOrNull(value: T[], transform: (it: T) => number): T | null { + if (value.length === 0) return null; + let max = value[0]; + let maxVal = transform(max); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val > maxVal) { + max = it; + maxVal = val; + } + } + return max; +} diff --git a/src/collection/maxOf.ts b/src/collection/maxOf.ts new file mode 100644 index 0000000..d795483 --- /dev/null +++ b/src/collection/maxOf.ts @@ -0,0 +1,12 @@ +import {error} from "../util/error"; + +export function maxOf(value: T[], transform: (it: T) => number): number { + if (value.length === 0) error(); + let max = transform(value[0]); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val > max) max = val; + } + return max; +} diff --git a/src/collection/maxOfOrNull.ts b/src/collection/maxOfOrNull.ts new file mode 100644 index 0000000..ddcbbc5 --- /dev/null +++ b/src/collection/maxOfOrNull.ts @@ -0,0 +1,10 @@ +export function maxOfOrNull(value: T[], transform: (it: T) => number): number | null { + if (value.length === 0) return null; + let max = transform(value[0]); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val > max) max = val; + } + return max; +} diff --git a/src/collection/maxOrNull.ts b/src/collection/maxOrNull.ts new file mode 100644 index 0000000..b3bb627 --- /dev/null +++ b/src/collection/maxOrNull.ts @@ -0,0 +1,9 @@ +export function maxOrNull(value: number[]): number | null { + if (value.length === 0) return null; + let max = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (it > max) max = it; + } + return max; +} diff --git a/src/collection/maxWith.ts b/src/collection/maxWith.ts new file mode 100644 index 0000000..afeba58 --- /dev/null +++ b/src/collection/maxWith.ts @@ -0,0 +1,11 @@ +import {error} from "../util/error"; + +export function maxWith(value: T[], compare: (a: T, b: T) => number): T { + if (value.length === 0) error(); + let max = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (compare(it, max) > 0) max = it; + } + return max; +} diff --git a/src/collection/maxWithOrNull.ts b/src/collection/maxWithOrNull.ts new file mode 100644 index 0000000..035ef68 --- /dev/null +++ b/src/collection/maxWithOrNull.ts @@ -0,0 +1,9 @@ +export function maxWithOrNull(value: T[], compare: (a: T, b: T) => number): T | null { + if (value.length === 0) return null; + let max = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (compare(it, max) > 0) max = it; + } + return max; +} diff --git a/src/collection/min.ts b/src/collection/min.ts new file mode 100644 index 0000000..3f2da60 --- /dev/null +++ b/src/collection/min.ts @@ -0,0 +1,11 @@ +import {error} from "../util/error"; + +export function min(value: number[]): number { + if (value.length === 0) error(); + let min = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (it < min) min = it; + } + return min; +} diff --git a/src/collection/minBy.ts b/src/collection/minBy.ts new file mode 100644 index 0000000..9b2c9ef --- /dev/null +++ b/src/collection/minBy.ts @@ -0,0 +1,16 @@ +import {error} from "../util/error"; + +export function minBy(value: T[], transform: (it: T) => number): T { + if (value.length === 0) error(); + let min = value[0]; + let minVal = transform(min); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val < minVal) { + min = it; + minVal = val; + } + } + return min; +} diff --git a/src/collection/minByOrNull.ts b/src/collection/minByOrNull.ts new file mode 100644 index 0000000..e35dbfb --- /dev/null +++ b/src/collection/minByOrNull.ts @@ -0,0 +1,14 @@ +export function minByOrNull(value: T[], transform: (it: T) => number): T | null { + if (value.length === 0) return null; + let min = value[0]; + let minVal = transform(min); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val < minVal) { + min = it; + minVal = val; + } + } + return min; +} diff --git a/src/collection/minOf.ts b/src/collection/minOf.ts new file mode 100644 index 0000000..2d14a81 --- /dev/null +++ b/src/collection/minOf.ts @@ -0,0 +1,12 @@ +import {error} from "../util/error"; + +export function minOf(value: T[], transform: (it: T) => number): number { + if (value.length === 0) error(); + let min = transform(value[0]); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val < min) min = val; + } + return min; +} diff --git a/src/collection/minOfOrNull.ts b/src/collection/minOfOrNull.ts new file mode 100644 index 0000000..ed218d0 --- /dev/null +++ b/src/collection/minOfOrNull.ts @@ -0,0 +1,10 @@ +export function minOfOrNull(value: T[], transform: (it: T) => number): number | null { + if (value.length === 0) return null; + let min = transform(value[0]); + for (let index = 1; index < value.length; index++) { + const it = value[index]; + const val = transform(it); + if (val < min) min = val; + } + return min; +} diff --git a/src/collection/minOrNull.ts b/src/collection/minOrNull.ts new file mode 100644 index 0000000..862e233 --- /dev/null +++ b/src/collection/minOrNull.ts @@ -0,0 +1,9 @@ +export function minOrNull(value: number[]): number | null { + if (value.length === 0) return null; + let min = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (it < min) min = it; + } + return min; +} diff --git a/src/collection/minWith.ts b/src/collection/minWith.ts new file mode 100644 index 0000000..22a3463 --- /dev/null +++ b/src/collection/minWith.ts @@ -0,0 +1,11 @@ +import {error} from "../util/error"; + +export function minWith(value: T[], compare: (a: T, b: T) => number): T { + if (value.length === 0) error(); + let min = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (compare(it, min) < 0) min = it; + } + return min; +} diff --git a/src/collection/minWithOrNull.ts b/src/collection/minWithOrNull.ts new file mode 100644 index 0000000..ebd0b38 --- /dev/null +++ b/src/collection/minWithOrNull.ts @@ -0,0 +1,9 @@ +export function minWithOrNull(value: T[], compare: (a: T, b: T) => number): T | null { + if (value.length === 0) return null; + let min = value[0]; + for (let index = 1; index < value.length; index++) { + const it = value[index]; + if (compare(it, min) < 0) min = it; + } + return min; +} diff --git a/src/collection/minus.ts b/src/collection/minus.ts new file mode 100644 index 0000000..74c3738 --- /dev/null +++ b/src/collection/minus.ts @@ -0,0 +1,8 @@ +export function minus(value: T[], items: Iterable): T[] { + const result: T[] = []; + const set = new Set(items); + for (const it of value) { + if (!set.has(it)) result.push(it); + } + return result; +} diff --git a/src/collection/none.ts b/src/collection/none.ts new file mode 100644 index 0000000..08f12d8 --- /dev/null +++ b/src/collection/none.ts @@ -0,0 +1,6 @@ +export function none(value: T[], predicate: (it: T) => boolean): boolean { + for (const it of value) { + if (predicate(it)) return false; + } + return true; +} diff --git a/src/collection/onEach.ts b/src/collection/onEach.ts new file mode 100644 index 0000000..8e85571 --- /dev/null +++ b/src/collection/onEach.ts @@ -0,0 +1,6 @@ +export function onEach(value: T[], action: (it: T) => void): T[] { + for (const it of value) { + action(it); + } + return value; +} diff --git a/src/collection/partition.ts b/src/collection/partition.ts new file mode 100644 index 0000000..3ac1533 --- /dev/null +++ b/src/collection/partition.ts @@ -0,0 +1,9 @@ +export function partition(value: T[], predicate: (it: T) => boolean): [T[], T[]] { + const t: T[] = []; + const f: T[] = []; + for (const it of value) { + if (predicate(it)) t.push(it); + else f.push(it); + } + return [t, f]; +} diff --git a/src/collection/plus.ts b/src/collection/plus.ts new file mode 100644 index 0000000..0e0aef9 --- /dev/null +++ b/src/collection/plus.ts @@ -0,0 +1,10 @@ +export function plus(value: T[], items: Iterable): T[] { + const result: T[] = []; + for (const it of value) { + result.push(it); + } + for (const it of items) { + result.push(it); + } + return result; +} diff --git a/src/collection/reduce.ts b/src/collection/reduce.ts new file mode 100644 index 0000000..2f0c2c7 --- /dev/null +++ b/src/collection/reduce.ts @@ -0,0 +1,10 @@ +import {error} from "../util/error"; + +export function reduce(value: T[], accumulate: (acc: T, it: T) => T): T { + if (value.length === 0) error(); + let acc = value[0]; + for (let index = 1; index < value.length; index++) { + acc = accumulate(acc, value[index]); + } + return acc; +} diff --git a/src/collection/reduceOrNull.ts b/src/collection/reduceOrNull.ts new file mode 100644 index 0000000..d4230dc --- /dev/null +++ b/src/collection/reduceOrNull.ts @@ -0,0 +1,8 @@ +export function reduceOrNull(value: T[], accumulate: (acc: T, it: T) => T): T | null { + if (value.length === 0) return null; + let acc = value[0]; + for (let index = 1; index < value.length; index++) { + acc = accumulate(acc, value[index]); + } + return acc; +} diff --git a/src/collection/reversed.ts b/src/collection/reversed.ts new file mode 100644 index 0000000..dbc42fc --- /dev/null +++ b/src/collection/reversed.ts @@ -0,0 +1,3 @@ +export function reversed(value: T[]): T[] { + return [...value].reverse(); +} diff --git a/src/collection/scan.ts b/src/collection/scan.ts new file mode 100644 index 0000000..33ecf56 --- /dev/null +++ b/src/collection/scan.ts @@ -0,0 +1,9 @@ +export function scan(value: T[], initial: R, accumulate: (acc: R, it: T) => R): R[] { + const result: R[] = [initial]; + let acc = initial; + for (const it of value) { + acc = accumulate(acc, it); + result.push(acc); + } + return result; +} diff --git a/src/collection/single.ts b/src/collection/single.ts new file mode 100644 index 0000000..78dfeae --- /dev/null +++ b/src/collection/single.ts @@ -0,0 +1,18 @@ +import {error} from "../util/error"; + +export function single(value: T[], predicate?: (it: T) => boolean): T { + if (value.length === 0) error(); + if (!predicate) { + if (value.length !== 1) error(); + return value[0]; + } + let last: T | undefined; + for (const it of value) { + if (predicate(it)) { + if (last !== undefined) error(); + last = it; + } + } + if (last === undefined) error(); + return last; +} diff --git a/src/collection/singleOrNull.ts b/src/collection/singleOrNull.ts new file mode 100644 index 0000000..7290681 --- /dev/null +++ b/src/collection/singleOrNull.ts @@ -0,0 +1,16 @@ +export function singleOrNull(value: T[], predicate?: (it: T) => boolean): T | null { + if (value.length === 0) return null; + if (!predicate) { + if (value.length !== 1) return null; + return value[0]; + } + let last: T | undefined; + for (const it of value) { + if (predicate(it)) { + if (last !== undefined) return null; + last = it; + } + } + if (last === undefined) return null; + return last; +} diff --git a/src/collection/slice.ts b/src/collection/slice.ts new file mode 100644 index 0000000..c1cf0a3 --- /dev/null +++ b/src/collection/slice.ts @@ -0,0 +1,15 @@ +import {error} from "../util/error"; + +export function slice(value: T[], start: number, end?: number): T[] { + if (start < 0) error(); + if (end !== undefined) { + if (end < 0 || end > value.length) error(); + if (start > end) error(); + } + const result: T[] = []; + const limit = end ?? value.length; + for (let index = start; index < limit; index++) { + result.push(value[index]); + } + return result; +} diff --git a/src/collection/sorted.ts b/src/collection/sorted.ts new file mode 100644 index 0000000..aadd57f --- /dev/null +++ b/src/collection/sorted.ts @@ -0,0 +1,3 @@ +export function sorted(value: number[]): number[] { + return [...value].sort((a, b) => a - b); +} diff --git a/src/collection/sortedBy.ts b/src/collection/sortedBy.ts new file mode 100644 index 0000000..92c32a9 --- /dev/null +++ b/src/collection/sortedBy.ts @@ -0,0 +1,3 @@ +export function sortedBy(value: T[], transform: (it: T) => number): T[] { + return [...value].sort((a, b) => transform(a) - transform(b)); +} diff --git a/src/collection/sortedWith.ts b/src/collection/sortedWith.ts new file mode 100644 index 0000000..0b1ed18 --- /dev/null +++ b/src/collection/sortedWith.ts @@ -0,0 +1,3 @@ +export function sortedWith(value: T[], compare: (a: T, b: T) => number): T[] { + return [...value].sort(compare); +} diff --git a/src/collection/sum.ts b/src/collection/sum.ts new file mode 100644 index 0000000..f1606a1 --- /dev/null +++ b/src/collection/sum.ts @@ -0,0 +1,7 @@ +export function sum(value: number[]): number { + let sum = 0; + for (const it of value) { + sum += it; + } + return sum; +} diff --git a/src/collection/sumBy.ts b/src/collection/sumBy.ts new file mode 100644 index 0000000..1fcc6b2 --- /dev/null +++ b/src/collection/sumBy.ts @@ -0,0 +1,7 @@ +export function sumBy(value: T[], transform: (it: T) => number): number { + let sum = 0; + for (const it of value) { + sum += transform(it); + } + return sum; +} diff --git a/src/collection/take.ts b/src/collection/take.ts new file mode 100644 index 0000000..08a61ad --- /dev/null +++ b/src/collection/take.ts @@ -0,0 +1,11 @@ +import {error} from "../util/error"; + +export function take(value: T[], count: number): T[] { + if (count < 0) error(); + const result: T[] = []; + const limit = Math.min(count, value.length); + for (let index = 0; index < limit; index++) { + result.push(value[index]); + } + return result; +} diff --git a/src/collection/takeUntil.ts b/src/collection/takeUntil.ts new file mode 100644 index 0000000..f8c028b --- /dev/null +++ b/src/collection/takeUntil.ts @@ -0,0 +1,8 @@ +export function takeUntil(value: T[], predicate: (it: T) => boolean): T[] { + const result: T[] = []; + for (const it of value) { + if (predicate(it)) break; + result.push(it); + } + return result; +} diff --git a/src/collection/takeWhile.ts b/src/collection/takeWhile.ts new file mode 100644 index 0000000..3dff419 --- /dev/null +++ b/src/collection/takeWhile.ts @@ -0,0 +1,8 @@ +export function takeWhile(value: Iterable, predicate: (it: T) => boolean): T[] { + const result: T[] = []; + for (const it of value) { + if (!predicate(it)) break; + result.push(it); + } + return result; +} diff --git a/src/collection/toArray.ts b/src/collection/toArray.ts new file mode 100644 index 0000000..e977b29 --- /dev/null +++ b/src/collection/toArray.ts @@ -0,0 +1,3 @@ +export function toArray(value: T[]): T[] { + return [...value]; +} diff --git a/src/collection/toMap.ts b/src/collection/toMap.ts new file mode 100644 index 0000000..c6162be --- /dev/null +++ b/src/collection/toMap.ts @@ -0,0 +1,3 @@ +export function toMap(value: [K, V][]): Map { + return new Map(value); +} diff --git a/src/collection/toSet.ts b/src/collection/toSet.ts new file mode 100644 index 0000000..3c76ff1 --- /dev/null +++ b/src/collection/toSet.ts @@ -0,0 +1,3 @@ +export function toSet(value: T[]): Set { + return new Set(value); +} diff --git a/src/collection/unzip.ts b/src/collection/unzip.ts new file mode 100644 index 0000000..f5d04f4 --- /dev/null +++ b/src/collection/unzip.ts @@ -0,0 +1,9 @@ +export function unzip(value: [A, B][]): [A[], B[]] { + const resultA: A[] = []; + const resultB: B[] = []; + for (const [a, b] of value) { + resultA.push(a); + resultB.push(b); + } + return [resultA, resultB]; +} diff --git a/src/collection/windowed.ts b/src/collection/windowed.ts new file mode 100644 index 0000000..d0b2b41 --- /dev/null +++ b/src/collection/windowed.ts @@ -0,0 +1,22 @@ +import {error} from "../util/error"; + +export function windowed(value: T[], size: number, step: number = 1, partial: boolean = false): T[][] { + if (size < 1) error(); + if (step < 1) error(); + const result: T[][] = []; + const buffer: T[] = []; + for (const it of value) { + buffer.push(it); + if (buffer.length === size) { + result.push([...buffer]); + buffer.splice(0, step); + } + } + if (partial) { + while (buffer.length !== 0) { + result.push([...buffer]); + buffer.splice(0, step); + } + } + return result; +} diff --git a/src/collection/withIndex.ts b/src/collection/withIndex.ts new file mode 100644 index 0000000..26e57dd --- /dev/null +++ b/src/collection/withIndex.ts @@ -0,0 +1,7 @@ +export function withIndex(value: T[]): [number, T][] { + const result: [number, T][] = []; + for (let index = 0; index < value.length; index++) { + result.push([index, value[index]]); + } + return result; +} diff --git a/src/collection/zip.ts b/src/collection/zip.ts new file mode 100644 index 0000000..9a7a5fd --- /dev/null +++ b/src/collection/zip.ts @@ -0,0 +1,13 @@ +export function zip(value: T[], items: Iterable): [T, B][] { + const result: [T, B][] = []; + const iteratorA = value[Symbol.iterator](); + const iteratorB = items[Symbol.iterator](); + let a = iteratorA.next(); + let b = iteratorB.next(); + while (!a.done && !b.done) { + result.push([a.value, b.value]); + a = iteratorA.next(); + b = iteratorB.next(); + } + return result; +} diff --git a/src/index.ts b/src/index.ts index e69de29..78d3644 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1 @@ +export {type Collection, collection} from "./collection"; diff --git a/src/util/error.ts b/src/util/error.ts new file mode 100644 index 0000000..fc4dbc5 --- /dev/null +++ b/src/util/error.ts @@ -0,0 +1,3 @@ +export function error(message?: string): never { + throw Error(message); +} diff --git a/tsconfig.json b/tsconfig.json index 8c1d21f..1ff6193 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,10 @@ { "compilerOptions": { - "moduleResolution": "NodeNext", - "module": "NodeNext", - "target": "ESNext", "rootDir": "./src/", + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", "outDir": "./dist/", - "declaration": true, - "strict": true + "declaration": true } }