├── .gitignore
├── .travis.yml
├── APIDOC.md
├── LICENSE
├── README.md
├── package.json
├── preprocessor.js
├── sequency.gif
├── sequency.png
├── src
├── Comparator.ts
├── ComparatorFactory.ts
├── GeneratorIterator.ts
├── GeneratorSeedIterator.ts
├── IndexedValue.ts
├── Sequence.ts
├── all.ts
├── any.ts
├── asIterable.ts
├── associate.ts
├── associateBy.ts
├── average.ts
├── chunk.ts
├── contains.ts
├── count.ts
├── createComparatorFactory.ts
├── distinct.ts
├── distinctBy.ts
├── drop.ts
├── dropWhile.ts
├── elementAt.ts
├── elementAtOrElse.ts
├── elementAtOrNull.ts
├── filter.ts
├── filterIndexed.ts
├── filterNot.ts
├── filterNotNull.ts
├── first.ts
├── firstOrNull.ts
├── flatMap.ts
├── flatten.ts
├── fold.ts
├── foldIndexed.ts
├── forEach.ts
├── forEachIndexed.ts
├── groupBy.ts
├── indexOf.ts
├── indexOfFirst.ts
├── indexOfLast.ts
├── joinToString.ts
├── last.ts
├── lastOrNull.ts
├── map.ts
├── mapIndexed.ts
├── mapNotNull.ts
├── max.ts
├── maxBy.ts
├── maxWith.ts
├── merge.ts
├── min.ts
├── minBy.ts
├── minWith.ts
├── minus.ts
├── none.ts
├── onEach.ts
├── partition.ts
├── plus.ts
├── reduce.ts
├── reduceIndexed.ts
├── reverse.ts
├── single.ts
├── singleOrNull.ts
├── sorted.ts
├── sortedBy.ts
├── sortedByDescending.ts
├── sortedDescending.ts
├── sortedWith.ts
├── sum.ts
├── sumBy.ts
├── take.ts
├── takeWhile.ts
├── toArray.ts
├── toMap.ts
├── toSet.ts
├── unzip.ts
├── withIndex.ts
└── zip.ts
├── test
├── all.test.ts
├── any.test.ts
├── asIterable.test.ts
├── asSequence.test.ts
├── associate.test.ts
├── associateBy.test.ts
├── average.test.ts
├── browsertest-lib.html
├── chunk.test.ts
├── contains.test.ts
├── count.test.ts
├── distinct.test.ts
├── distinctBy.test.ts
├── drop.test.ts
├── dropWhile.test.ts
├── elementAt.test.ts
├── elementAtOrElse.test.ts
├── elementAtOrNull.test.ts
├── emptySequence.test.ts
├── examples.test.ts
├── extendSequence.test.ts
├── filter.test.ts
├── filterIndexed.test.ts
├── filterNot.test.ts
├── filterNotNull.test.ts
├── find.test.ts
├── findLast.test.ts
├── first.test.ts
├── firstOrNull.test.ts
├── flatMap.test.ts
├── flatten.test.ts
├── fold.test.ts
├── foldIndexed.test.ts
├── forEach.test.ts
├── forEachIndexed.test.ts
├── generateSequence.test.ts
├── groupBy.test.ts
├── indexOf.test.ts
├── indexOfFirst.test.ts
├── indexOfLast.test.ts
├── joinTo.test.ts
├── joinToString.test.ts
├── last.test.ts
├── lastOrNull.test.ts
├── map.test.ts
├── mapIndexed.test.ts
├── mapNotNull.test.ts
├── max.test.ts
├── maxBy.test.ts
├── maxWith.test.ts
├── merge.test.ts
├── min.test.ts
├── minBy.test.ts
├── minWith.test.ts
├── minus.test.ts
├── none.test.ts
├── onEach.test.ts
├── partition.test.ts
├── plus.test.ts
├── range.test.ts
├── reduce.test.ts
├── reduceIndexed.test.ts
├── reverse.test.ts
├── sequenceOf.test.ts
├── single.test.ts
├── singleOrNull.test.ts
├── sorted.test.ts
├── sortedBy.test.ts
├── sortedByDescending.test.ts
├── sortedDescending.test.ts
├── sortedWith.test.ts
├── sum.test.ts
├── sumBy.test.ts
├── take.test.ts
├── takeWhile.test.ts
├── toArray.test.ts
├── toList.test.ts
├── toMap.test.ts
├── toSet.test.ts
├── undefinedAndNull.test.ts
├── unzip.test.ts
└── zip.test.ts
├── tsconfig.json
├── tslint.json
├── webpack.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | *.iml
4 |
5 | lib
6 | lib-umd
7 | docs
8 | coverage
9 | node_modules
10 | package-lock.json
11 | *.log
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | branches:
3 | only:
4 | - master
5 | before_install:
6 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.9.2
7 | - export PATH=$HOME/.yarn/bin:$PATH
8 | cache:
9 | yarn: true
10 | directories:
11 | - node_modules
12 | notifications:
13 | email: false
14 | node_js:
15 | - '8.11'
16 | script:
17 | - yarn travis
--------------------------------------------------------------------------------
/APIDOC.md:
--------------------------------------------------------------------------------
1 | # API Documentation
2 |
3 | Sequency is a type-safe functional programming library for processing iterable data such as arrays, sets and maps. It's written in TypeScript, compiles to ES5-compatible JavaScript and works in all current browsers and Node applications.
4 |
5 | Download the [latest release](https://github.com/winterbe/sequency/releases) from [GitHub](https://github.com/winterbe/sequency) or install Sequency from [NPM](https://github.com/winterbe/sequency):
6 |
7 | ```bash
8 | npm install sequency
9 | ```
10 | or
11 | ```bash
12 | yarn add sequency
13 | ```
14 |
15 | ### How Sequency works
16 |
17 | Sequency is centered around the interface [Sequence](https://winterbe.github.io/sequency/interfaces/Sequence.html) to process any kind of iterable data such as arrays, maps and sets.
18 |
19 | The interface `Sequence` provides a fluent functional API consisting of intermediate and terminal operations. Intermediate functions return a new sequence, thus enabling method chaining while terminal functions return an arbitrary result. You can explore all available `Sequence` operations by navigating to the [Sequence](https://winterbe.github.io/sequency/interfaces/Sequence.html) interface.
20 |
21 | ### Creating Sequences from your data
22 |
23 | Sequences can be created with one of the following functions:
24 |
25 | ```js
26 | import {sequenceOf, asSequence, emptySequence, generateSequence} from "sequency";
27 | ```
28 |
29 | - `sequenceOf` accepts one or many values and returns a new sequence.
30 | - `asSequence` accepts an iterable (e.g. an array, set or map) and returns a new sequence.
31 | - `emptySequence` returns a new empty sequence.
32 | - `generateSequence` creates a sequence from the results of the given generator function.
33 |
34 | ### Code sample
35 |
36 | ```js
37 | import {asSequence} from "sequency";
38 |
39 | const numbers = [1, 2, 3, 4, 5];
40 |
41 | const result = asSequence(numbers)
42 | .filter(num => num > 2)
43 | .reverse()
44 | .toArray()
45 |
46 | // result: [5, 4, 3]
47 | ```
48 |
49 | ### License
50 |
51 | MIT © [winterbe](https://twitter.com/winterbe_)
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Benjamin Winterberg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #  [](https://travis-ci.org/winterbe/sequency)
2 |
3 | > Type-safe functional sequences for processing iterable data in TypeScript and JavaScript.
4 |
5 | 
6 |
7 | ---
8 |
9 |
10 | ★★★ Like this project? Leave a star and follow on Twitter! Thanks. ★★★
11 |
12 |
13 | ## About Sequency
14 |
15 | Sequency is a lightweight (**5 KB minified**), intensely tested (**200+ tests, 99% coverage**), type-safe functional programming library for processing iterable data such as arrays, sets and maps. It's written in TypeScript, compiles to ES5-compatible JavaScript and works in all current browsers and Node applications. The API is inspired by [Sequences](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/) from the programming language [Kotlin](https://kotlinlang.org/).
16 |
17 | > [Try Sequency](https://npm.runkit.com/sequency) right in your browser.
18 |
19 | ## Getting started
20 |
21 | Download the [latest release](https://github.com/winterbe/sequency/releases) from GitHub or install Sequency from [NPM](https://www.npmjs.com/package/sequency):
22 |
23 | ```bash
24 | npm install --save sequency
25 | ```
26 |
27 | Alternatively use Sequency from [CDN](https://unpkg.com/sequency/) by adding this to your HTML:
28 |
29 | ```html
30 |
31 | ```
32 |
33 | ## How Sequency works
34 |
35 | Sequency is centered around a single class called `Sequence` to process any kind of iterable data such as arrays, sets or maps. The API is inspired by [Kotlin](https://kotlinlang.org/) [Sequences](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/index.html).
36 |
37 | Sequences can be created by utilizing one of the following functions:
38 |
39 | ```js
40 | import {
41 | asSequence,
42 | sequenceOf,
43 | emptySequence,
44 | range,
45 | generateSequence,
46 | extendSequence
47 | } from 'sequency';
48 | ```
49 |
50 | - `sequenceOf` accepts one or many values and returns a new sequence.
51 | - `asSequence` accepts an iterable (e.g. an array, set or map) and returns a new sequence.
52 | - `emptySequence` returns a new empty sequence.
53 | - `range` returns as number sequence consisting of all numbers between `startInclusive` and `endExclusive`.
54 | - `generateSequence` returns a sequence generated from the given generator function.
55 | - `extendSequence` allows extending sequences with user-defined operations (see [example](https://github.com/winterbe/sequency/blob/ac3dbb0f212bb08783d970472c7a76dc921b60ba/test/extendSequence.test.ts)).
56 |
57 | Each `Sequence` provides a fluent functional API consisting of intermediate and terminal operations. Intermediate functions (e.g. `filter`, `map`, `sorted`) return a new sequence, thus enabling method chaining. Terminal functions (e.g. `toArray`, `groupBy`, `findLast`) return an arbitrary result. Detailed descriptions of all operations are available in the [API docs](https://winterbe.github.io/sequency/).
58 |
59 | Sequences are **lazily evaluated** to avoid examining all of the input data when it's not necessary. Sequences always perform the minimal amount of operations to gain results. E.g. in a `filter - map - find` sequence both `map` and `find` are executed just one time before returning the single result.
60 |
61 | ## [API documentation](https://winterbe.github.io/sequency/)
62 |
63 | Sequency is fully documented via inline JSDoc comments. [The docs are also available online](https://winterbe.github.io/sequency/). When using an IDE like Intellij IDEA or Webstorm the docs are available inline right inside your editor.
64 |
65 | ## Why Sequency?
66 |
67 | I've built Sequency because I'm using Kotlin for server-side code but for some reasons still use TypeScript and JavaScript for client-side browser code. I find that using the same APIs for collection processing both on client and server is a huge gain in productivity for me.
68 |
69 | ## License
70 |
71 | MIT © [winterbe](https://twitter.com/winterbe_)
72 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sequency",
3 | "version": "0.20.0",
4 | "description": "Functional sequences for processing iterable data in JavaScript",
5 | "main": "lib/Sequence.js",
6 | "umd:main": "lib-umd/sequency.js",
7 | "typings": "lib/Sequence.d.ts",
8 | "scripts": {
9 | "test": "jest",
10 | "watch": "jest --watch --notify",
11 | "coverage": "rimraf coverage && jest --coverage",
12 | "travis": "yarn lint && yarn test",
13 | "lint": "node_modules/.bin/tslint -c tslint.json 'src/**/*.ts' 'test/**/*.ts'",
14 | "docs": "rimraf docs && typedoc --name Sequency --readme APIDOC.md -out docs --hideGenerator src/Sequence.ts",
15 | "docs-publish": "yarn docs && touch docs/.nojekyll && gh-pages -d docs -t",
16 | "bundle": "webpack --mode production && size-limit",
17 | "bundle-browsertest": "open ./test/browsertest-lib.html",
18 | "clean": "rimraf lib && rimraf lib-umd && rimraf docs && rimraf coverage",
19 | "compile": "tsc",
20 | "build": "yarn clean && yarn lint && yarn compile && yarn test && yarn bundle",
21 | "prepublishOnly": "yarn build"
22 | },
23 | "author": "winterbe",
24 | "homepage": "https://winterbe.com",
25 | "license": "MIT",
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/winterbe/sequency.git"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/winterbe/sequency/issues"
32 | },
33 | "files": [
34 | "lib",
35 | "lib-umd",
36 | "LICENSE"
37 | ],
38 | "engines": {
39 | "node": ">=6.0.0"
40 | },
41 | "devDependencies": {
42 | "@size-limit/preset-small-lib": "^7.0.8",
43 | "@types/jest": "^27.4.1",
44 | "gh-pages": "^3.2.3",
45 | "jest": "^27.5.1",
46 | "rimraf": "^3.0.2",
47 | "size-limit": "^7.0.8",
48 | "ts-loader": "^8.2.0",
49 | "tslint": "^5.11.0",
50 | "typedoc": "^0.22.13",
51 | "typescript": "^4.6.2",
52 | "uglifyjs-webpack-plugin": "^2.2.0",
53 | "webpack": "^4.46.0",
54 | "webpack-cli": "^3.3.12"
55 | },
56 | "jest": {
57 | "moduleFileExtensions": [
58 | "ts",
59 | "js"
60 | ],
61 | "transform": {
62 | "^.+\\.ts$": "/preprocessor.js"
63 | },
64 | "testMatch": [
65 | "**/test/*.ts"
66 | ],
67 | "testURL": "http://localhost/"
68 | },
69 | "size-limit": [
70 | {
71 | "path": "lib-umd/sequency.min.js",
72 | "limit": "10 KB"
73 | }
74 | ],
75 | "keywords": [
76 | "functional",
77 | "sequence",
78 | "processing",
79 | "lazy",
80 | "iterable",
81 | "iterator",
82 | "array",
83 | "map",
84 | "set"
85 | ]
86 | }
87 |
--------------------------------------------------------------------------------
/preprocessor.js:
--------------------------------------------------------------------------------
1 | const tsc = require('typescript');
2 | const tsConfig = require('./tsconfig.json');
3 |
4 | module.exports = {
5 | process(src, path) {
6 | if (path.endsWith('.ts')) {
7 | return tsc.transpile(src, tsConfig.compilerOptions, path, []);
8 | }
9 | return src;
10 | },
11 | };
--------------------------------------------------------------------------------
/sequency.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winterbe/sequency/948a7876229bba951ad2ac1c2c26ffdf91d7e41d/sequency.gif
--------------------------------------------------------------------------------
/sequency.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winterbe/sequency/948a7876229bba951ad2ac1c2c26ffdf91d7e41d/sequency.png
--------------------------------------------------------------------------------
/src/Comparator.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A Comparator defines a compare function enriched with methods to compose multiple
3 | * comparators in order to form complex comparison behavior. A compare function returns
4 | * negative numbers if the first value is lower than the second value, positive numbers
5 | * if the first value is larger than the second value and zero if both values are equal.
6 | */
7 | interface Comparator {
8 | (a: T, b: T): number;
9 |
10 | /**
11 | * Reverses the order of the current comparator.
12 | *
13 | * @returns {Comparator}
14 | */
15 | reversed(): Comparator;
16 |
17 | /**
18 | * Composes the current comparator with the given comparison function such
19 | * that the latter is applied for every equal values of the former comparator.
20 | *
21 | * @param {(a: T, b: T) => number} comparison
22 | * @returns {Comparator}
23 | */
24 | then(comparison: (a: T, b: T) => number): Comparator;
25 |
26 | /**
27 | * Composes the current comparator with the given comparison function such
28 | * that the latter is applied for every equal values of the current comparator
29 | * in reverse (descending) order.
30 | *
31 | * @param {(a: T, b: T) => number} comparison
32 | * @returns {Comparator}
33 | */
34 | thenDescending(comparison: (a: T, b: T) => number): Comparator;
35 |
36 | /**
37 | * Composes the current comparator with a comparator which compares the properties
38 | * selected by the given `selector` function for every equal values of the current
39 | * comparator.
40 | *
41 | * @param {(value: T) => any} selector
42 | * @returns {Comparator}
43 | */
44 | thenBy(selector: (value: T) => any): Comparator;
45 |
46 | /**
47 | * Composes the current comparator with a comparator which compares the values
48 | * of the given `key` for every equal values of the current comparator.
49 | *
50 | * @param {keyof T} key
51 | * @returns {Comparator}
52 | */
53 | thenBy(key: keyof T): Comparator;
54 |
55 | /**
56 | * Composes the current comparator with a comparator which compares the properties
57 | * selected by the given `selector` function for every equal values of the current
58 | * comparator in reverse (descending) order.
59 | *
60 | * @param {(value: T) => any} selector
61 | * @returns {Comparator}
62 | */
63 | thenByDescending(selector: (value: T) => any): Comparator;
64 |
65 | /**
66 | * Composes the current comparator with a comparator which compares the values
67 | * of the given `key` for every equal values of the current comparator
68 | * in reverse (descending) order.
69 | *
70 | * @param {keyof T} key
71 | * @returns {Comparator}
72 | */
73 | thenByDescending(key: keyof T): Comparator;
74 | }
75 |
76 | export default Comparator;
--------------------------------------------------------------------------------
/src/ComparatorFactory.ts:
--------------------------------------------------------------------------------
1 | import Comparator from "./Comparator";
2 |
3 | /**
4 | * Defines various methods for constructing comparators.
5 | */
6 | interface ComparatorFactory {
7 |
8 | /**
9 | * Constructs a new comparator where values are ordered by the given
10 | * comparison function.
11 | *
12 | * @param {(a: T, b: T) => number} comparison
13 | * @returns {Comparator}
14 | */
15 | compare(comparison: (a: T, b: T) => number): Comparator;
16 |
17 | /**
18 | * Constructs a new comparator where values are ordered by the natural ascending order
19 | * of the property selected by the given `selector` function.
20 | *
21 | * @param {(value: T) => any} selector
22 | * @returns {Comparator}
23 | */
24 | compareBy(selector: (value: T) => any): Comparator;
25 |
26 | /**
27 | * Constructs a new comparator where values are ordered by the natural ascending order
28 | * of values for the given `key`.
29 | *
30 | * @param {keyof T} key
31 | * @returns {Comparator}
32 | */
33 | compareBy(key: keyof T): Comparator;
34 |
35 | /**
36 | * Constructs a new comparator where values are ordered by the natural descending order
37 | * of the property selected by the given `selector` function.
38 | *
39 | * @param {(value: T) => any} selector
40 | * @returns {Comparator}
41 | */
42 | compareByDescending(selector: (value: T) => any): Comparator;
43 |
44 | /**
45 | * Constructs a new comparator where values are ordered by the natural descending order
46 | * of values for the given `key`.
47 | *
48 | * @param {keyof T} key
49 | * @returns {Comparator}
50 | */
51 | compareByDescending(key: keyof T): Comparator;
52 |
53 | /**
54 | * Constructs a new comparator where values are ordered naturally.
55 | *
56 | * @returns {Comparator}
57 | */
58 | naturalOrder(): Comparator;
59 |
60 | /**
61 | * Constructs a new comparator where values are ordered in reverse natural order.
62 | *
63 | * @returns {Comparator}
64 | */
65 | reverseOrder(): Comparator;
66 |
67 | /**
68 | * Constructs a new comparator where null values are ordered at the beginning.
69 | *
70 | * @returns {Comparator}
71 | */
72 | nullsFirst(): Comparator;
73 |
74 | /**
75 | * Constructs a new comparator where null values are ordered at the end.
76 | *
77 | * @returns {Comparator}
78 | */
79 | nullsLast(): Comparator;
80 | }
81 |
82 | export default ComparatorFactory;
--------------------------------------------------------------------------------
/src/GeneratorIterator.ts:
--------------------------------------------------------------------------------
1 | export default class GeneratorIterator implements Iterator {
2 | constructor(private readonly nextFunction: () => T | null | undefined) {
3 | }
4 |
5 | next(value?: any): IteratorResult {
6 | const nextItem = this.nextFunction();
7 | return {
8 | done: nextItem == null,
9 | value: nextItem!
10 | };
11 | }
12 | }
--------------------------------------------------------------------------------
/src/GeneratorSeedIterator.ts:
--------------------------------------------------------------------------------
1 | export default class GeneratorSeedIterator implements Iterator {
2 | private prevItem: T;
3 |
4 | constructor(private readonly seed: T,
5 | private readonly nextFunction: (value: T) => T | null | undefined) {
6 | }
7 |
8 | next(value?: any): IteratorResult {
9 | if (this.prevItem == null) {
10 | this.prevItem = this.seed;
11 | return {done: false, value: this.seed};
12 | }
13 | const nextItem = this.nextFunction(this.prevItem);
14 | if (nextItem == null) {
15 | return {done: true, value: undefined as any};
16 | }
17 | this.prevItem = nextItem;
18 | return {
19 | done: false,
20 | value: nextItem!
21 | };
22 | }
23 | }
--------------------------------------------------------------------------------
/src/IndexedValue.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Defines a `value` with a zero-based `index`.
3 | */
4 | interface IndexedValue {
5 | index: number;
6 | value: T;
7 | }
8 | export default IndexedValue;
--------------------------------------------------------------------------------
/src/Sequence.ts:
--------------------------------------------------------------------------------
1 | import {All} from "./all";
2 | import {Any} from "./any";
3 | import {AsIterable} from "./asIterable";
4 | import {Associate} from "./associate";
5 | import {AssociateBy} from "./associateBy";
6 | import {Average} from "./average";
7 | import {Chunk} from "./chunk";
8 | import {Contains} from "./contains";
9 | import {Count} from "./count";
10 | import {Distinct} from "./distinct";
11 | import {DistinctBy} from "./distinctBy";
12 | import {Drop} from "./drop";
13 | import {DropWhile} from "./dropWhile";
14 | import {ElementAt} from "./elementAt";
15 | import {ElementAtOrElse} from "./elementAtOrElse";
16 | import {ElementAtOrNull} from "./elementAtOrNull";
17 | import {Filter} from "./filter";
18 | import {FilterIndexed} from "./filterIndexed";
19 | import {FilterNot} from "./filterNot";
20 | import {FilterNotNull} from "./filterNotNull";
21 | import {First} from "./first";
22 | import {FirstOrNull} from "./firstOrNull";
23 | import {FlatMap} from "./flatMap";
24 | import {Flatten} from "./flatten";
25 | import {Fold} from "./fold";
26 | import {FoldIndexed} from "./foldIndexed";
27 | import {ForEach} from "./forEach";
28 | import {ForEachIndexed} from "./forEachIndexed";
29 | import {GroupBy} from "./groupBy";
30 | import {IndexOf} from "./indexOf";
31 | import {IndexOfFirst} from "./indexOfFirst";
32 | import {IndexOfLast} from "./indexOfLast";
33 | import {JoinToString} from "./joinToString";
34 | import {Last} from "./last";
35 | import {LastOrNull} from "./lastOrNull";
36 | import {Map} from "./map";
37 | import {MapIndexed} from "./mapIndexed";
38 | import {MapNotNull} from "./mapNotNull";
39 | import {Max} from "./max";
40 | import {MaxBy} from "./maxBy";
41 | import {MaxWith} from "./maxWith";
42 | import {Merge} from "./merge";
43 | import {Min} from "./min";
44 | import {MinBy} from "./minBy";
45 | import {Minus} from "./minus";
46 | import {MinWith} from "./minWith";
47 | import {None} from "./none";
48 | import {OnEach} from "./onEach";
49 | import {Partition} from "./partition";
50 | import {Plus} from "./plus";
51 | import {Reduce} from "./reduce";
52 | import {ReduceIndexed} from "./reduceIndexed";
53 | import {Reverse} from "./reverse";
54 | import {Single} from "./single";
55 | import {SingleOrNull} from "./singleOrNull";
56 | import {Sorted} from "./sorted";
57 | import {SortedBy} from "./sortedBy";
58 | import {SortedByDescending} from "./sortedByDescending";
59 | import {SortedDescending} from "./sortedDescending";
60 | import {SortedWith} from "./sortedWith";
61 | import {Sum} from "./sum";
62 | import {SumBy} from "./sumBy";
63 | import {Take} from "./take";
64 | import {TakeWhile} from "./takeWhile";
65 | import {ToArray} from "./toArray";
66 | import {ToMap} from "./toMap";
67 | import {ToSet} from "./toSet";
68 | import {Unzip} from "./unzip";
69 | import {WithIndex} from "./withIndex";
70 | import {Zip} from "./zip";
71 | import GeneratorIterator from "./GeneratorIterator";
72 | import GeneratorSeedIterator from "./GeneratorSeedIterator";
73 |
74 | /**
75 | * A Sequence provides a fluent functional API consisting
76 | * of various intermediate and terminal operations for processing the iterated data.
77 | * The operations are evaluated lazily to avoid examining all the input data
78 | * when it's not necessary. Sequences can be iterated only once.
79 | */
80 | export interface Sequence extends SequenceOperators {
81 | readonly iterator: Iterator;
82 | }
83 |
84 | export default Sequence;
85 |
86 | /**
87 | * @hidden
88 | */
89 | export interface SequenceOperators extends All, Any, AsIterable, Associate, AssociateBy, Average, Chunk, Contains, Count, Distinct, DistinctBy, Drop,
90 | DropWhile, ElementAt, ElementAtOrElse, ElementAtOrNull, Filter, FilterIndexed, FilterNot, FilterNotNull, First, FirstOrNull, FlatMap, Flatten, Fold, FoldIndexed,
91 | ForEach, ForEachIndexed, GroupBy, IndexOf, IndexOfFirst, IndexOfLast, JoinToString, Last, LastOrNull, Map, MapIndexed, MapNotNull, Max, MaxBy, MaxWith, Merge, Min, MinBy,
92 | Minus, MinWith, None, OnEach, Partition, Plus, Reduce, ReduceIndexed, Reverse, Single, SingleOrNull, Sorted, SortedBy, SortedByDescending, SortedDescending, SortedWith,
93 | Sum, SumBy, Take, TakeWhile, ToArray, ToMap, ToSet, Unzip, WithIndex, Zip {
94 | }
95 |
96 | class SequenceImpl {
97 | constructor(readonly iterator: Iterator) {
98 | }
99 | }
100 |
101 | applyMixins(SequenceImpl, [All, Any, AsIterable, Associate, AssociateBy, Average, Chunk, Contains, Count, Distinct, DistinctBy, Drop,
102 | DropWhile, ElementAt, ElementAtOrElse, ElementAtOrNull, Filter, FilterIndexed, FilterNot, FilterNotNull, First, FirstOrNull, FlatMap, Flatten, Fold, FoldIndexed,
103 | ForEach, ForEachIndexed, GroupBy, IndexOf, IndexOfFirst, IndexOfLast, JoinToString, Last, LastOrNull, Map, MapIndexed, MapNotNull, Max, MaxBy, MaxWith, Merge, Min, MinBy,
104 | Minus, MinWith, None, OnEach, Partition, Plus, Reduce, ReduceIndexed, Reverse, Single, SingleOrNull, Sorted, SortedBy, SortedByDescending, SortedDescending, SortedWith,
105 | Sum, SumBy, Take, TakeWhile, ToArray, ToMap, ToSet, Unzip, WithIndex, Zip]);
106 |
107 | function applyMixins(derivedCtor: any, baseCtors: any[]) {
108 | baseCtors.forEach(baseCtor => {
109 | Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
110 | derivedCtor.prototype[name] = baseCtor.prototype[name];
111 | });
112 | });
113 | }
114 |
115 | export function sequenceOf(...args: Array): Sequence {
116 | return asSequence(args);
117 | }
118 |
119 | export function emptySequence(): Sequence {
120 | return asSequence([]);
121 | }
122 |
123 | export function asSequence(iterable: Iterable): Sequence {
124 | if (iterable === null) {
125 | throw new Error("Cannot create sequence for input: null");
126 | }
127 | if (iterable === undefined) {
128 | throw new Error("Cannot create sequence for input: undefined");
129 | }
130 | if (iterable[Symbol.iterator] == null) {
131 | throw new Error("Cannot create sequence for non-iterable input: " + iterable);
132 | }
133 | const iterator = iterable[Symbol.iterator]();
134 | return createSequence(iterator);
135 | }
136 |
137 | export function createSequence(iterator: Iterator): Sequence {
138 | return new SequenceImpl(iterator) as any;
139 | }
140 |
141 | export function isSequence(object: any): object is Sequence {
142 | return object instanceof SequenceImpl;
143 | }
144 |
145 | export function extendSequence(mixin: { new(): any }) {
146 | applyMixins(SequenceImpl, [mixin]);
147 | }
148 |
149 | export function generateSequence(nextFunction: () => T | null | undefined): Sequence;
150 | export function generateSequence(seedFunction: () => T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence;
151 | export function generateSequence(seed: T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence;
152 | export function generateSequence(a: any, b?: any): Sequence {
153 | if (typeof a === "function" && b == null) {
154 | return createSequence(new GeneratorIterator(a));
155 | }
156 | const seed = typeof a === "function" ? a() : a;
157 | return seed != null
158 | ? createSequence(new GeneratorSeedIterator(seed, b))
159 | : emptySequence();
160 | }
161 |
162 | export function range(start: number, endInclusive: number, step: number = 1): Sequence {
163 | if (start > endInclusive) {
164 | throw new Error(`start [${start}] must be lower then endInclusive [${endInclusive}]`);
165 | }
166 | let current = start;
167 | return generateSequence(() => {
168 | try {
169 | return current <= endInclusive
170 | ? current
171 | : undefined;
172 | } finally {
173 | current += step;
174 | }
175 | });
176 | }
177 |
--------------------------------------------------------------------------------
/src/all.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class All {
4 |
5 | /**
6 | * Returns `true` if all elements match the given `predicate`.
7 | *
8 | * @param {(T) => boolean} predicate
9 | * @returns {boolean}
10 | */
11 | all(this: Sequence, predicate: (item: T) => boolean): boolean {
12 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
13 | if (!predicate(item.value)) {
14 | return false;
15 | }
16 | }
17 | return true;
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/src/any.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Any {
4 |
5 | /**
6 | * Returns `true` if at least one element match the given `predicate`.
7 | *
8 | * @param {(T) => boolean} predicate
9 | * @returns {boolean}
10 | */
11 | any(this: Sequence, predicate?: (item: T) => boolean): boolean {
12 | if (predicate == null) {
13 | return !this.iterator.next().done;
14 | }
15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
16 | if (predicate(item.value)) {
17 | return true;
18 | }
19 | }
20 | return false;
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/src/asIterable.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class AsIterable {
4 |
5 | /**
6 | * Returns an iterable representation of the sequence.
7 | *
8 | * @returns {Iterable}
9 | */
10 | asIterable(this: Sequence): Iterable {
11 | const iterator = this.iterator;
12 | return {
13 | [Symbol.iterator](): Iterator {
14 | return iterator;
15 | }
16 | };
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/src/associate.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Associate {
4 |
5 | /**
6 | * Transforms each element into a key-value pair and returns the results as map. In case of
7 | * duplicate keys the last key-value pair overrides the other.
8 | *
9 | * @param {(value: T) => [K , V]} transform
10 | * @returns {Map}
11 | */
12 | associate(this: Sequence, transform: (value: T) => [K, V]): Map {
13 | const result = new Map();
14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
15 | const pair = transform(item.value);
16 | result.set(pair[0], pair[1]);
17 | }
18 | return result;
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/src/associateBy.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class AssociateBy {
4 |
5 | /**
6 | * Returns a map consisting of the elements mapped by the given `keySelector`.
7 | *
8 | * @param {(value: T) => K} keySelector
9 | * @returns {Map}
10 | */
11 | associateBy(keySelector: (value: T) => K): Map;
12 |
13 | /**
14 | * Returns a map consisting of the elements indexed by the given `key`.
15 | *
16 | * @param {K} key
17 | * @returns {Map}
18 | */
19 | associateBy(key: K): Map;
20 |
21 | /**
22 | * Returns a map consisting of the elements mapped by the given `keySelector`. The value
23 | * is transformed into another value by the `valueTransformer`.
24 | *
25 | * @param {(value: T) => K} keySelector
26 | * @param {(value: T) => V} valueTransformer
27 | * @returns {Map}
28 | */
29 | associateBy(keySelector: (value: T) => K, valueTransformer: (value: T) => V): Map;
30 |
31 | /**
32 | * Returns a map consisting of the elements indexed by the given `key`. The value
33 | * is transformed into another value by the `valueTransformer`.
34 | *
35 | * @param {K} key
36 | * @param {(value: T) => V} valueTransformer
37 | * @returns {Map}
38 | */
39 | associateBy(key: K, valueTransformer: (value: T) => V): Map;
40 |
41 | associateBy(this: Sequence,
42 | keyOrSelector: any,
43 | valueTransform?: (value: T) => V): Map {
44 | const selector = typeof keyOrSelector === "function"
45 | ? keyOrSelector
46 | : (value: T) => value[keyOrSelector as keyof T];
47 | const result = new Map();
48 | const transform = valueTransform != null
49 | ? valueTransform
50 | : (value: T) => value;
51 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
52 | const key = selector(item.value);
53 | const value = transform(item.value);
54 | result.set(key, value);
55 | }
56 | return result;
57 | }
58 |
59 | }
--------------------------------------------------------------------------------
/src/average.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Average {
4 |
5 | /**
6 | * Returns the average of all numbers of the sequence or `NaN` if the sequence is empty.
7 | *
8 | * @returns {number}
9 | */
10 | average(this: Sequence): number {
11 | let sum = 0;
12 | let count = 0;
13 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
14 | sum += item.value;
15 | count++;
16 | }
17 | return count === 0
18 | ? Number.NaN
19 | : sum / count;
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/src/chunk.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Chunk {
4 |
5 | /**
6 | * Splits the elements of the sequence into arrays which length is determined by
7 | * the given `chunkSize` and returns all chunks as array.
8 | *
9 | * @param {number} chunkSize
10 | * @returns {Array>}
11 | */
12 | chunk(this: Sequence, chunkSize: number): Array> {
13 | if (chunkSize < 1) {
14 | throw new Error("chunkSize must be > 0 but is " + chunkSize);
15 | }
16 | const result: Array> = [];
17 | let index = 0;
18 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
19 | const chunkIndex = Math.floor(index / chunkSize);
20 | if (result[chunkIndex] == null) {
21 | result[chunkIndex] = [item.value];
22 | } else {
23 | result[chunkIndex].push(item.value);
24 | }
25 | index++;
26 | }
27 | return result;
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/src/contains.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Contains {
4 |
5 | /**
6 | * Returns `true` if the sequence contains the given `element`.
7 | *
8 | * @param {T} element
9 | * @returns {boolean}
10 | */
11 | contains(this: Sequence, element: T): boolean {
12 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
13 | if (element === item.value) {
14 | return true;
15 | }
16 | }
17 | return false;
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/src/count.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Count {
4 |
5 | /**
6 | * Returns the number of elements of this sequence. If `predicate` is present, returns
7 | * the number of elements matching the given `predicate`.
8 | *
9 | * @param {(T) => boolean} predicate
10 | * @returns {number}
11 | */
12 | count(this: Sequence, predicate?: (item: T) => boolean): number {
13 | let num = 0;
14 | if (predicate == null) {
15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
16 | num++;
17 | }
18 | } else {
19 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
20 | if (predicate(item.value)) {
21 | num++;
22 | }
23 | }
24 | }
25 | return num;
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/src/createComparatorFactory.ts:
--------------------------------------------------------------------------------
1 | import ComparatorFactory from "./ComparatorFactory";
2 | import Comparator from "./Comparator";
3 |
4 | function compare(comparison: (a: T, b: T) => number): Comparator {
5 | return Object.assign(comparison, {
6 | reversed(): Comparator {
7 | return compare(
8 | (a: T, b: T) => comparison(a, b) * -1
9 | );
10 | },
11 | then(nextComparison: (a: T, b: T) => number): Comparator {
12 | return compare(
13 | (a: T, b: T) => {
14 | const result = comparison(a, b);
15 | return result !== 0
16 | ? result
17 | : nextComparison(a, b);
18 | }
19 | );
20 | },
21 | thenDescending(nextComparison: (a: T, b: T) => number): Comparator {
22 | return this.then(
23 | compare(nextComparison)
24 | .reversed()
25 | );
26 | },
27 | thenBy(keyOrSelector: any): Comparator {
28 | const selector = asSelector(keyOrSelector);
29 | return this.then(
30 | (a: T, b: T) => naturalCompare(selector(a), selector(b))
31 | );
32 | },
33 | thenByDescending(keyOrSelector: any): Comparator {
34 | const selector = asSelector(keyOrSelector);
35 | return this.then(
36 | compare(
37 | (a: T, b: T) => naturalCompare(selector(a), selector(b))
38 | ).reversed()
39 | );
40 | }
41 | });
42 | }
43 |
44 | function compareBy(keyOrSelector: any): Comparator {
45 | const selector = asSelector(keyOrSelector);
46 | return compare(
47 | (a: T, b: T) => naturalCompare(selector(a), selector(b))
48 | );
49 | }
50 |
51 | function compareByDescending(keyOrSelector: any): Comparator {
52 | const selector = asSelector(keyOrSelector);
53 | return compare(
54 | (a: T, b: T) => naturalCompare(selector(b), selector(a))
55 | );
56 | }
57 |
58 | function asSelector(keyOrSelector: (item: T) => any | string): (item: T) => any {
59 | return typeof keyOrSelector === "function"
60 | ? keyOrSelector
61 | : (item: T) => (item as any)[keyOrSelector as string];
62 | }
63 |
64 | function naturalCompare(a: T, b: T): number {
65 | return a < b ? -1 : a > b ? 1 : 0;
66 | }
67 |
68 | function naturalOrder(): Comparator {
69 | return compare(naturalCompare);
70 | }
71 |
72 | function reverseOrder(): Comparator {
73 | return compare(naturalCompare).reversed();
74 | }
75 |
76 | function nullsLast(): Comparator {
77 | return compare(
78 | (a: T, b: T) => a === null ? 1 : b === null ? -1 : 0
79 | );
80 | }
81 |
82 | function nullsFirst(): Comparator {
83 | return compare(
84 | (a: T, b: T) => a === null ? -1 : b === null ? 1 : 0
85 | );
86 | }
87 |
88 | function createComparatorFactory(): ComparatorFactory {
89 | return {
90 | compare,
91 | compareBy,
92 | compareByDescending,
93 | naturalOrder,
94 | reverseOrder,
95 | nullsFirst,
96 | nullsLast
97 | };
98 | }
99 |
100 | export default createComparatorFactory;
--------------------------------------------------------------------------------
/src/distinct.ts:
--------------------------------------------------------------------------------
1 | import Sequence, {createSequence} from "./Sequence";
2 |
3 | class DistinctIterator implements Iterator {
4 | private set: Set = new Set();
5 |
6 | constructor(private readonly iterator: Iterator) {
7 | }
8 |
9 | next(value?: any): IteratorResult {
10 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
11 | const sizeBeforeAdd = this.set.size;
12 | this.set.add(item.value);
13 | if (this.set.size > sizeBeforeAdd) {
14 | return {done: false, value: item.value};
15 | }
16 | }
17 | this.set.clear();
18 | return {done: true, value: undefined as any};
19 | }
20 | }
21 |
22 | export class Distinct {
23 |
24 | /**
25 | * Returns a new sequence which discards all duplicate elements.
26 | *
27 | * @returns {Sequence}
28 | */
29 | distinct(this: Sequence): Sequence {
30 | return createSequence(new DistinctIterator(this.iterator));
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/distinctBy.ts:
--------------------------------------------------------------------------------
1 | import Sequence, {createSequence} from "./Sequence";
2 |
3 | class DistinctByIterator implements Iterator {
4 | private set: Set = new Set();
5 |
6 | constructor(private readonly iterator: Iterator,
7 | private readonly selector: (item: T) => K) {
8 | }
9 |
10 | next(value?: any): IteratorResult {
11 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
12 | const key = this.selector(item.value);
13 | const sizeBeforeAdd = this.set.size;
14 | this.set.add(key);
15 | if (this.set.size > sizeBeforeAdd) {
16 | return {done: false, value: item.value};
17 | }
18 | }
19 | this.set.clear();
20 | return {done: true, value: undefined as any};
21 | }
22 | }
23 |
24 | export class DistinctBy {
25 |
26 | /**
27 | * Returns a new sequence which discards all elements with duplicate items determined
28 | * by the given `selector`.
29 | *
30 | * @param {(item: T) => K} selector
31 | * @returns {Sequence}
32 | */
33 | distinctBy(this: Sequence, selector: (item: T) => K): Sequence {
34 | return createSequence(new DistinctByIterator(this.iterator, selector));
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/drop.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Drop {
4 |
5 | /**
6 | * Returns a new sequence which discards the first `n` elements;
7 | *
8 | * @param {number} n
9 | * @returns {Sequence}
10 | */
11 | drop(this: Sequence, n: number): Sequence {
12 | return this.withIndex()
13 | .dropWhile(it => it.index < n)
14 | .map(it => it.value);
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/dropWhile.ts:
--------------------------------------------------------------------------------
1 | import Sequence, {createSequence} from "./Sequence";
2 |
3 | class DropWhileIterator implements Iterator {
4 | private dropping = true;
5 |
6 | constructor(private readonly iterator: Iterator,
7 | private readonly predicate: (item: T) => boolean) {
8 | }
9 |
10 | next(value?: any): IteratorResult {
11 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
12 | if (!this.dropping) {
13 | return {done: false, value: item.value};
14 | }
15 | const result = this.predicate(item.value);
16 | if (!result) {
17 | this.dropping = false;
18 | return {done: false, value: item.value};
19 | }
20 | }
21 | return {done: true, value: undefined as any};
22 | }
23 | }
24 |
25 | export class DropWhile {
26 |
27 | /**
28 | * Drops all elements of the sequence as long as the given `predicate` evaluates to true.
29 | *
30 | * @param {(item: T) => boolean} predicate
31 | * @returns {Sequence}
32 | */
33 | dropWhile(this: Sequence, predicate: (item: T) => boolean): Sequence {
34 | return createSequence(new DropWhileIterator(this.iterator, predicate));
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/elementAt.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class ElementAt {
4 |
5 | /**
6 | * Returns the element at position `index` (zero-based) or throws an error if `index`
7 | * is out of bounds.
8 | *
9 | * @param {number} index
10 | * @returns {T}
11 | */
12 | elementAt(this: Sequence, index: number): T {
13 | let i = 0;
14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
15 | if (i === index) {
16 | return item.value;
17 | }
18 | i++;
19 | }
20 | throw new Error("Index out of bounds: " + index);
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/src/elementAtOrElse.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class ElementAtOrElse {
4 |
5 | /**
6 | * Returns the element at position `index` (zero-based). If `index` is out of bounds returns
7 | * the result of the given `defaultValue` function.
8 | *
9 | * @param {number} index
10 | * @param defaultValue
11 | * @returns {T}
12 | */
13 | elementAtOrElse(this: Sequence, index: number, defaultValue: (index: number) => T): T {
14 | let i = 0;
15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
16 | if (i === index) {
17 | return item.value;
18 | }
19 | i++;
20 | }
21 | return defaultValue(index);
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/src/elementAtOrNull.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class ElementAtOrNull {
4 |
5 | /**
6 | * Returns the element at position `index` (zero-based) or `null` if `index`
7 | * is out of bounds.
8 | *
9 | * @param {number} index
10 | * @returns {T}
11 | */
12 | elementAtOrNull(this: Sequence, index: number): T | null {
13 | let i = 0;
14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
15 | if (i === index) {
16 | return item.value;
17 | }
18 | i++;
19 | }
20 | return null;
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/src/filter.ts:
--------------------------------------------------------------------------------
1 | import Sequence, {createSequence} from "./Sequence";
2 |
3 | class FilterIterator implements Iterator {
4 | constructor(private readonly predicate: (item: T) => boolean,
5 | private readonly iterator: Iterator) {
6 | }
7 |
8 | next(value?: any): IteratorResult {
9 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
10 | if (this.predicate(item.value)) {
11 | return {done: false, value: item.value};
12 | }
13 | }
14 | return {done: true, value: undefined as any};
15 | }
16 | }
17 |
18 | export class Filter {
19 |
20 | /**
21 | * Returns a new sequence consisting of all elements that match the given `predicate`.
22 | *
23 | * @param {(T) => boolean} predicate
24 | * @returns {Sequence}
25 | */
26 | filter(this: Sequence, predicate: (item: T) => boolean): Sequence {
27 | return createSequence(new FilterIterator(predicate, this.iterator));
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/src/filterIndexed.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class FilterIndexed {
4 |
5 | /**
6 | * Returns a new sequence consisting of all elements that match the given `predicate`.
7 | *
8 | * @param {(index: number, value: T) => boolean} predicate
9 | * @returns {Sequence}
10 | */
11 | filterIndexed(this: Sequence, predicate: (index: number, value: T) => boolean): Sequence {
12 | return this.withIndex()
13 | .filter(it => predicate(it.index, it.value))
14 | .map(it => it.value);
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/src/filterNot.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class FilterNot {
4 |
5 | /**
6 | * Returns a new sequence consisting of all elements that don't match the given `predicate`.
7 | *
8 | * @param {(value: T) => boolean} predicate
9 | * @returns {Sequence}
10 | */
11 | filterNot(this: Sequence, predicate: (value: T) => boolean): Sequence {
12 | return this.filter((value: T) => !predicate(value));
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/src/filterNotNull.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class FilterNotNull {
4 |
5 | /**
6 | * Returns a new sequence consisting of all non-null elements.
7 | *
8 | * @returns {Sequence}
9 | */
10 | filterNotNull(this: Sequence): Sequence {
11 | return this.filter(it => it !== null) as Sequence;
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/first.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class First {
4 |
5 | /**
6 | * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise throws
7 | * an error.
8 | *
9 | * @param {(T) => boolean} predicate
10 | * @returns {T}
11 | */
12 | first(this: Sequence, predicate?: (item: T) => boolean): T {
13 | if (predicate != null) {
14 | return this.filter(predicate).first();
15 | }
16 | const item = this.iterator.next();
17 | if (item.done) {
18 | throw new Error("No such element");
19 | }
20 | return item.value;
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/src/firstOrNull.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class FirstOrNull {
4 |
5 | /**
6 | * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise returns `null`.
7 | *
8 | * @param {(T) => boolean} predicate
9 | * @returns {T}
10 | */
11 | firstOrNull(this: Sequence, predicate?: (item: T) => boolean): T | null {
12 | if (predicate != null) {
13 | return this.filter(predicate).firstOrNull();
14 | }
15 | const item = this.iterator.next();
16 | return item.done
17 | ? null
18 | : item.value;
19 | }
20 |
21 | /**
22 | * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise returns `null`.
23 | *
24 | * @param {(T) => boolean} predicate
25 | * @returns {T}
26 | */
27 | find(this: Sequence, predicate?: (item: T) => boolean): T | null {
28 | return this.firstOrNull(predicate);
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/flatMap.ts:
--------------------------------------------------------------------------------
1 | import Sequence, {createSequence} from "./Sequence";
2 |
3 | class FlatMapIterator implements Iterator {
4 | private current: Iterator | undefined;
5 |
6 | constructor(private readonly transform: (item: S) => Sequence,
7 | private readonly iterator: Iterator) {
8 | }
9 |
10 | next(value?: any): IteratorResult {
11 | if (this.current != null) {
12 | const item = this.current.next();
13 | if (!item.done) {
14 | return item;
15 | }
16 | }
17 | const next = this.iterator.next();
18 | if (!next.done) {
19 | const sequence = this.transform(next.value);
20 | this.current = sequence.iterator;
21 | return this.next();
22 | }
23 | return {done: true, value: undefined as any};
24 | }
25 | }
26 |
27 | export class FlatMap {
28 |
29 | /**
30 | * Transforms each element into a sequence of items and returns a flat single sequence of all those items.
31 | *
32 | * @param {(value: S) => Sequence} transform
33 | * @returns {Sequence}
34 | */
35 | flatMap(this: Sequence, transform: (value: S) => Sequence): Sequence {
36 | return createSequence(new FlatMapIterator(transform, this.iterator));
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/src/flatten.ts:
--------------------------------------------------------------------------------
1 | import Sequence, {asSequence, isSequence} from "./Sequence";
2 |
3 | export class Flatten {
4 |
5 | /**
6 | * Returns a single flat sequence of all the items from all sequences or iterables.
7 | *
8 | * @returns {Sequence}
9 | */
10 | flatten(this: Sequence | Iterable>): Sequence {
11 | return this.flatMap(it => {
12 | if (isSequence(it)) {
13 | return it;
14 | } else {
15 | return asSequence(it);
16 | }
17 | });
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/src/fold.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class Fold {
4 |
5 | /**
6 | * Accumulates all elements of the sequence into a single result by applying the given `operation` starting with
7 | * the `initial` value. The result of the last operation will be passed as accumulated value to the getNext invocation
8 | * of the operation until all elements of the sequence are processed.
9 | *
10 | * @param {R} initial
11 | * @param {(acc: R, element: T) => R} operation
12 | * @returns {R}
13 | */
14 | fold(this: Sequence, initial: R, operation: (acc: R, element: T) => R): R {
15 | let result = initial;
16 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
17 | result = operation(result, item.value);
18 | }
19 | return result;
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/src/foldIndexed.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class FoldIndexed {
4 |
5 | /**
6 | * Accumulates all elements of the sequence into a single result by applying the given `operation` starting with
7 | * the `initial` value. The result of the last operation will be passed as accumulated value to the getNext invocation
8 | * of the operation as well as the `index` of the current element (zero-based) until all elements of the sequence
9 | * are processed.
10 | *
11 | * @param {R} initial
12 | * @param {(index: number, acc: R, element: T) => R} operation
13 | * @returns {R}
14 | */
15 | foldIndexed(this: Sequence, initial: R, operation: (index: number, acc: R, element: T) => R): R {
16 | let result = initial;
17 | let index = 0;
18 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
19 | result = operation(index, result, item.value);
20 | index++;
21 | }
22 | return result;
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/src/forEach.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class ForEach {
4 |
5 | /**
6 | * Performs the given `action` (side-effect) for each element of the sequence.
7 | *
8 | * @param {(T) => void} action
9 | */
10 | forEach(this: Sequence, action: (item: T) => void) {
11 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
12 | action(item.value);
13 | }
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/src/forEachIndexed.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class ForEachIndexed {
4 |
5 | /**
6 | * Performs the given `action` (side-effect) for each element of the sequence and passes the `index` of the current
7 | * element (zero-based).
8 | *
9 | * @param {(index: number, value: T) => void} action
10 | */
11 | forEachIndexed(this: Sequence, action: (index: number, value: T) => void) {
12 | this.withIndex()
13 | .forEach(it => action(it.index, it.value));
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/src/groupBy.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class GroupBy {
4 |
5 | /**
6 | * Groups all elements of the sequence into a map. Keys are determined by the given `keySelector` function.
7 | *
8 | * @param {(value: T) => K} keySelector
9 | * @returns {Map>}
10 | */
11 | groupBy(this: Sequence, keySelector: (value: T) => K): Map> {
12 | const result = new Map>();
13 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
14 | const key = keySelector(item.value);
15 | const array = result.get(key);
16 | if (array == null) {
17 | result.set(key, [item.value]);
18 | } else {
19 | array.push(item.value);
20 | }
21 | }
22 | return result;
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/src/indexOf.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class IndexOf {
4 |
5 | /**
6 | * Returns the zero-based index of the given `element` or -1 if the sequence does not contain the element.
7 | *
8 | * @param {T} element
9 | * @returns {number}
10 | */
11 | indexOf(this: Sequence, element: T): number {
12 | let index = 0;
13 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
14 | if (element === item.value) {
15 | return index;
16 | }
17 | index++;
18 | }
19 | return -1;
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/src/indexOfFirst.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class IndexOfFirst {
4 |
5 | /**
6 | * Returns the zero-based index of the first element matching the given `predicate` or -1 if no element matches
7 | * the predicate.
8 | *
9 | * @param {(value: T) => boolean} predicate
10 | * @returns {number}
11 | */
12 | indexOfFirst(this: Sequence, predicate: (value: T) => boolean): number {
13 | let index = 0;
14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
15 | if (predicate(item.value)) {
16 | return index;
17 | }
18 | index++;
19 | }
20 | return -1;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/indexOfLast.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export class IndexOfLast {
4 |
5 | /**
6 | * Returns the zero-based index of the last element matching the given `predicate` or -1 if no element matches
7 | * the predicate.
8 | *
9 | * @param {(value: T) => boolean} predicate
10 | * @returns {number}
11 | */
12 | indexOfLast(this: Sequence, predicate: (value: T) => boolean): number {
13 | let index = 0;
14 | let result = -1;
15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
16 | if (predicate(item.value)) {
17 | result = index;
18 | }
19 | index++;
20 | }
21 | return result;
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/src/joinToString.ts:
--------------------------------------------------------------------------------
1 | import Sequence from "./Sequence";
2 |
3 | export interface JoinConfig {
4 | value?: string;
5 | separator?: string;
6 | prefix?: string;
7 | postfix?: string;
8 | limit?: number;
9 | truncated?: string;
10 | transform?: (value: T) => string;
11 | }
12 |
13 | const defaults = {
14 | value: "",
15 | separator: ", ",
16 | prefix: "",
17 | postfix: "",
18 | limit: -1,
19 | truncated: "...",
20 | transform: undefined
21 | };
22 |
23 | export class JoinToString {
24 |
25 | /**
26 | * Joins all elements of the sequence into a string with the given configuration.
27 | *
28 | * @param {JoinConfig} config
29 | * @returns {string}
30 | */
31 | joinToString(this: Sequence, config: JoinConfig = defaults): string {
32 | const {
33 | value = defaults.value,
34 | separator = defaults.separator,
35 | prefix = defaults.prefix,
36 | postfix = defaults.postfix,
37 | limit = defaults.limit,
38 | truncated = defaults.truncated,
39 | transform = defaults.transform
40 | } = config;
41 |
42 | let result = `${value}${prefix}`;
43 | let count = 0;
44 |
45 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) {
46 | count++;
47 | if (count > 1) {
48 | result += separator;
49 | }
50 | if (limit < 0 || count <= limit) {
51 | result += transform != null
52 | ? transform(item.value)
53 | : String(item.value);
54 | } else {
55 | break;
56 | }
57 | }
58 |
59 | if (limit >= 0 && count > limit) {
60 | result += truncated;
61 | }
62 |
63 | result += postfix;
64 | return result;
65 | }
66 |
67 | /**
68 | * Joins all elements of the sequence into a string with the given configuration.
69 | *
70 | * @param {JoinConfig} config
71 | * @returns {string}
72 | */
73 | joinTo