├── .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 | # ![Sequencey](/sequency.png) [![Travic CI](https://travis-ci.org/winterbe/sequency.svg?branch=master)](https://travis-ci.org/winterbe/sequency) 2 | 3 | > Type-safe functional sequences for processing iterable data in TypeScript and JavaScript. 4 | 5 | ![Sequencey](/sequency.gif) 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(this: Sequence, config: JoinConfig = defaults): string { 74 | return this.joinToString(config); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/last.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Last { 4 | 5 | /** 6 | * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise throws 7 | * an error. 8 | * 9 | * @param {(value: T) => boolean} predicate 10 | * @returns {T} 11 | */ 12 | last(this: Sequence, predicate?: (value: T) => boolean): T { 13 | if (predicate != null) { 14 | return this.filter(predicate).last(); 15 | } 16 | let result: T; 17 | let empty = true; 18 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 19 | result = item.value; 20 | empty = false; 21 | } 22 | if (empty) { 23 | throw new Error("No such element"); 24 | } 25 | return result!; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/lastOrNull.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class LastOrNull { 4 | 5 | /** 6 | * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise returns `null`. 7 | * 8 | * @param {(value: T) => boolean} predicate 9 | * @returns {T} 10 | */ 11 | lastOrNull(this: Sequence, predicate?: (value: T) => boolean): T | null { 12 | if (predicate != null) { 13 | return this.filter(predicate).lastOrNull(); 14 | } 15 | let result: T | null = null; 16 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 17 | result = item.value; 18 | } 19 | return result; 20 | } 21 | 22 | /** 23 | * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise returns `null`. 24 | * 25 | * @param {(value: T) => boolean} predicate 26 | * @returns {T} 27 | */ 28 | findLast(this: Sequence, predicate?: (value: T) => boolean): T | null { 29 | return this.lastOrNull(predicate); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/map.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {createSequence} from "./Sequence"; 2 | 3 | class MapIterator implements Iterator { 4 | constructor(private readonly transform: (item: S) => T, 5 | private readonly iterator: Iterator) { 6 | } 7 | 8 | next(value?: any): IteratorResult { 9 | const item = this.iterator.next(); 10 | return item.done 11 | ? {done: true, value: undefined as any} 12 | : {done: false, value: this.transform(item.value)}; 13 | } 14 | } 15 | 16 | export class Map { 17 | 18 | /** 19 | * Transforms each element into another value by applying the given `transform` function and returns a new sequence. 20 | * 21 | * @param {(S) => T} transform 22 | * @returns {Sequence} 23 | */ 24 | map(this: Sequence, transform: (element: S) => T): Sequence { 25 | return createSequence(new MapIterator(transform, this.iterator)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/mapIndexed.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class MapIndexed { 4 | 5 | /** 6 | * Transforms each element into another value by applying the given `transform` function and returns a new sequence. 7 | * 8 | * @param {(index: number, value: T) => R} transform 9 | * @returns {Sequence} 10 | */ 11 | mapIndexed(this: Sequence, transform: (index: number, value: T) => R): Sequence { 12 | return this.withIndex() 13 | .map(it => transform(it.index, it.value)); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/mapNotNull.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {emptySequence, sequenceOf} from "./Sequence"; 2 | 3 | export class MapNotNull { 4 | 5 | /** 6 | * Transforms each element into another value by applying the given `transform` function and returns a new sequence. 7 | * Transformations into `null` values are discarded. 8 | * 9 | * @param {(value: T) => R} transform 10 | * @returns {Sequence} 11 | */ 12 | mapNotNull(this: Sequence, transform: (value: T) => R | null): Sequence { 13 | return this.flatMap((value: T) => { 14 | const item = transform(value); 15 | return item !== null 16 | ? sequenceOf(item) 17 | : emptySequence(); 18 | }); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/max.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Max { 4 | 5 | /** 6 | * Returns the maximum element of the sequence or `null` if sequence is empty. 7 | * 8 | * @returns {T} 9 | */ 10 | max(this: Sequence): T | null { 11 | let result: T | null = null; 12 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 13 | if (result == null || item.value > result) { 14 | result = item.value; 15 | } 16 | } 17 | return result; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/maxBy.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class MaxBy { 4 | 5 | /** 6 | * Returns the maximum element by comparing the results of the given `selector` function 7 | * for each element of the sequence or `null` if the sequence is empty. 8 | * 9 | * @param {(value: T) => R} selector 10 | * @returns {T} 11 | */ 12 | maxBy(this: Sequence, selector: (value: T) => R): T | null { 13 | let max: T | null = null; 14 | let maxSelected: R | null = null; 15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 16 | const value = selector(item.value); 17 | if (maxSelected == null || value > maxSelected) { 18 | maxSelected = value; 19 | max = item.value; 20 | } 21 | } 22 | return max; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/maxWith.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class MaxWith { 4 | 5 | /** 6 | * Returns the maximum element of the sequence by evaluating the given `compare` 7 | * function or `null` if sequence is empty. 8 | * 9 | * @returns {T} 10 | */ 11 | maxWith(this: Sequence, compare: (a: T, b: T) => number): T | null { 12 | let max: T | null = null; 13 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 14 | if (max == null || compare(item.value, max) > 0) { 15 | max = item.value; 16 | } 17 | } 18 | return max; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/merge.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {asSequence, isSequence} from "./Sequence"; 2 | 3 | export class Merge { 4 | 5 | /** 6 | * Merges the elements of both sequences into a new sequence. Each element of this sequence is eventually replaced with 7 | * an element of the other sequence by comparing results of the given `selector` function. If no value is found in the other 8 | * sequence the element is retained. New elements of the other sequence are appended to the end of the new sequence or 9 | * prepended to the start of the new sequence, if `prependNewValues` is set to `true`. This operation is not lazy evaluated. 10 | * 11 | * @param {Sequence} other 12 | * @param {(value: T) => S} selector 13 | * @param prependNewValues 14 | * @returns {Sequence} 15 | */ 16 | merge(this: Sequence, other: Sequence | Iterable, selector: (value: T) => S, prependNewValues: boolean = false): Sequence { 17 | let mergeValues = isSequence(other) 18 | ? other.toArray() 19 | : asSequence(other).toArray(); 20 | const leftValues = this.toArray(); 21 | const result = leftValues.map(left => { 22 | const selected = selector(left); 23 | const right = asSequence(mergeValues) 24 | .find(it => selector(it) === selected); 25 | if (right != null) { 26 | mergeValues = mergeValues.filter(it => it !== right); 27 | return right; 28 | } else { 29 | return left; 30 | } 31 | }); 32 | if (prependNewValues) { 33 | return asSequence([...mergeValues, ...result]); 34 | } else { 35 | return asSequence([...result, ...mergeValues]); 36 | } 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/min.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Min { 4 | 5 | /** 6 | * Returns the minimum element of the sequence or `null` if sequence is empty. 7 | * 8 | * @returns {T} 9 | */ 10 | min(this: Sequence): T | null { 11 | let result: T | null = null; 12 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 13 | if (result == null || item.value < result) { 14 | result = item.value; 15 | } 16 | } 17 | return result; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/minBy.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class MinBy { 4 | 5 | /** 6 | * Returns the minimum element by comparing the results of the given `selector` function 7 | * for each element of the sequence or `null` if the sequence is empty. 8 | * 9 | * @param {(value: T) => R} selector 10 | * @returns {T} 11 | */ 12 | minBy(this: Sequence, selector: (value: T) => R): T | null { 13 | let min: T | null = null; 14 | let minSelected: R | null = null; 15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 16 | const value = selector(item.value); 17 | if (minSelected == null || value < minSelected) { 18 | minSelected = value; 19 | min = item.value; 20 | } 21 | } 22 | return min; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/minWith.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class MinWith { 4 | 5 | /** 6 | * Returns the minimum element of the sequence by evaluating the given `compare` 7 | * function or `null` if sequence is empty. 8 | * 9 | * @returns {T} 10 | */ 11 | minWith(this: Sequence, compare: (a: T, b: T) => number): T | null { 12 | let min: T | null = null; 13 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 14 | if (min == null || compare(item.value, min) < 0) { 15 | min = item.value; 16 | } 17 | } 18 | return min; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/minus.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {isSequence} from "./Sequence"; 2 | 3 | export class Minus { 4 | 5 | /** 6 | * Removes the given `data` and returns a new sequence. Data can either be a single element, an array of elements 7 | * or a sequence of elements. 8 | * 9 | * @param {Sequence | Array | T} data 10 | * @returns {Sequence} 11 | */ 12 | minus(this: Sequence, data: T | Sequence | Array): Sequence { 13 | if (isSequence(data)) { 14 | const array: Array = data.toArray(); 15 | return this.filter(it => array.indexOf(it) < 0); 16 | } else if (data instanceof Array) { 17 | return this.filter(it => data.indexOf(it) < 0); 18 | } else { 19 | return this.filter(it => it !== data); 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/none.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class None { 4 | 5 | /** 6 | * Returns `true` if no element match the given `predicate` or if the sequence is empty 7 | * if no predicate is present. 8 | * 9 | * @param {(value: T) => boolean} predicate 10 | * @returns {boolean} 11 | */ 12 | none(this: Sequence, predicate?: (value: T) => boolean): boolean { 13 | if (predicate == null) { 14 | return this.iterator.next()?.done ?? false; 15 | } 16 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 17 | if (predicate(item.value)) { 18 | return false; 19 | } 20 | } 21 | return true; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/onEach.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class OnEach { 4 | 5 | /** 6 | * Performs the given `action` for each element and returns the sequence. 7 | * 8 | * @param {(value: T) => void} action 9 | * @returns {Sequence} 10 | */ 11 | onEach(this: Sequence, action: (value: T) => void): Sequence { 12 | return this.map(it => { 13 | action(it); 14 | return it; 15 | }); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/partition.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Partition { 4 | 5 | /** 6 | * Evaluates the given `predicate` for each element of the sequence and assorts each element into one of two lists 7 | * according to the result of the predicate. Returns both lists as an object. 8 | * 9 | * @param {(value: T) => boolean} predicate 10 | * @returns {{true: Array; false: Array}} 11 | */ 12 | partition(this: Sequence, predicate: (value: T) => boolean): { "true": Array, "false": Array } { 13 | const arrayTrue: Array = []; 14 | const arrayFalse: Array = []; 15 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 16 | if (predicate(item.value)) { 17 | arrayTrue.push(item.value); 18 | } else { 19 | arrayFalse.push(item.value); 20 | } 21 | } 22 | return { "true": arrayTrue, "false": arrayFalse }; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/plus.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {createSequence, isSequence} from "./Sequence"; 2 | 3 | class AppendIterator implements Iterator { 4 | constructor(private readonly first: Iterator, 5 | private readonly second: Iterator) { 6 | } 7 | 8 | next(value?: any): IteratorResult { 9 | const item1 = this.first.next(); 10 | if (!item1.done) { 11 | return {done: false, value: item1.value}; 12 | } 13 | const item2 = this.second.next(); 14 | if (!item2.done) { 15 | return {done: false, value: item2.value}; 16 | } 17 | return {done: true, value: undefined as any}; 18 | } 19 | } 20 | 21 | export class Plus { 22 | 23 | /** 24 | * Appends the given `element` to the end of the sequence and returns a new sequence. 25 | * 26 | * @param {T} element 27 | * @returns {Sequence} 28 | */ 29 | plus(this: Sequence, element: T): Sequence; 30 | 31 | /** 32 | * Appends the given array to the end of the sequence and returns a new sequence. 33 | * 34 | * @param {Array} other 35 | * @returns {Sequence} 36 | */ 37 | plus(this: Sequence, other: Array): Sequence; 38 | 39 | /** 40 | * Appends the given sequence to the end of the sequence and returns a new sequence. 41 | * 42 | * @param {Sequence} other 43 | * @returns {Sequence} 44 | */ 45 | plus(this: Sequence, other: Sequence): Sequence; 46 | 47 | plus(this: Sequence, data: T | Sequence | Array): Sequence { 48 | if (isSequence(data)) { 49 | return createSequence(new AppendIterator(this.iterator, data.iterator)); 50 | } else if (data instanceof Array) { 51 | const iterator = data[Symbol.iterator](); 52 | return createSequence(new AppendIterator(this.iterator, iterator)); 53 | } else { 54 | const iterator = [data][Symbol.iterator](); 55 | return createSequence(new AppendIterator(this.iterator, iterator)); 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/reduce.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Reduce { 4 | 5 | /** 6 | * Reduces the whole sequence to a single value by invoking `operation` with each element 7 | * from left to right. For every invocation of the operation `acc` is the result of the last 8 | * invocation. For the first invocation of the operation `acc` is the first element of the 9 | * sequence. 10 | * 11 | * @param {(acc: S, value: T) => S} operation 12 | * @returns {S} 13 | */ 14 | reduce(this: Sequence, operation: (acc: S, value: T) => S): S { 15 | const first = this.iterator.next(); 16 | if (first.done) { 17 | throw new Error("Cannot reduce empty sequence"); 18 | } 19 | let result: S = first.value; 20 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 21 | result = operation(result, item.value); 22 | } 23 | return result; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/reduceIndexed.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class ReduceIndexed { 4 | 5 | /** 6 | * Reduces the whole sequence to a single value by invoking `operation` with each element 7 | * from left to right. For every invocation of the operation `acc` is the result of the last 8 | * invocation. For the first invocation of the operation `acc` is the first element of the 9 | * sequence. In addition the `index` of the current element is also passed to the operation. 10 | * 11 | * @param {(index: number, acc: S, element: T) => S} operation 12 | * @returns {S} 13 | */ 14 | reduceIndexed(this: Sequence, operation: (index: number, acc: S, element: T) => S): S { 15 | const first = this.iterator.next(); 16 | if (first.done) { 17 | throw new Error("Cannot reduce empty sequence"); 18 | } 19 | let index = 1; 20 | let result: S = first.value; 21 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 22 | result = operation(index, result, item.value); 23 | index++; 24 | } 25 | return result; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/reverse.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Reverse { 4 | 5 | /** 6 | * Returns a new sequence with all elements of the sequence in reverse order. 7 | * 8 | * @returns {Sequence} 9 | */ 10 | reverse(this: Sequence): Sequence { 11 | return this.withIndex() 12 | .sortedByDescending(it => it.index) 13 | .map(it => it.value); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/single.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Single { 4 | 5 | /** 6 | * Returns the single element of the sequence or throws error if the sequence has more than 7 | * one element or none at all. If a `predicate` is passed returns the single element matching 8 | * the predicate or throws an error if more or less than one element match the predicate. 9 | * 10 | * @param {(value: T) => boolean} predicate 11 | * @returns {T} 12 | */ 13 | single(this: Sequence, predicate?: (value: T) => boolean): T { 14 | if (predicate != null) { 15 | return this.filter(predicate).single(); 16 | } 17 | const first = this.iterator.next(); 18 | if (first.done) { 19 | throw new Error("No such element"); 20 | } 21 | if (!this.iterator.next().done) { 22 | throw new Error("Expect single element"); 23 | } 24 | return first.value; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/singleOrNull.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class SingleOrNull { 4 | 5 | /** 6 | * Returns the single element of the sequence or `null` if the sequence has more than 7 | * one element or none at all. If a `predicate` is passed returns the single element matching 8 | * the predicate or `null` if more or less than one element match the predicate. 9 | * 10 | * @param {(value: T) => boolean} predicate 11 | * @returns {T} 12 | */ 13 | singleOrNull(this: Sequence, predicate?: (value: T) => boolean): T | null { 14 | if (predicate != null) { 15 | return this.filter(predicate).singleOrNull(); 16 | } 17 | const first = this.iterator.next(); 18 | if (first.done) { 19 | return null; 20 | } 21 | if (!this.iterator.next().done) { 22 | return null; 23 | } 24 | return first.value; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/sorted.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {createSequence} from "./Sequence"; 2 | import ComparatorFactory from "./ComparatorFactory"; 3 | import Comparator from "./Comparator"; 4 | import createComparatorFactory from "./createComparatorFactory"; 5 | 6 | export class Sorted { 7 | 8 | /** 9 | * Returns a new sequence with all elements sorted by the comparator specified by the given `composeComparator` function 10 | * or in natural order if no arguments are given. 11 | * 12 | * @returns {Sequence} 13 | */ 14 | sorted(this: Sequence, composeComparator?: (factory: ComparatorFactory) => Comparator): Sequence { 15 | const result: Array = []; 16 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 17 | result.push(item.value); 18 | } 19 | if (composeComparator == null) { 20 | result.sort(); 21 | } else { 22 | const factory: ComparatorFactory = createComparatorFactory(); 23 | const comparator = composeComparator(factory); 24 | result.sort(comparator); 25 | } 26 | const iterator = result[Symbol.iterator](); 27 | return createSequence(iterator); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/sortedBy.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class SortedBy { 4 | 5 | /** 6 | * Returns a new sequence with all elements sorted ascending by the value specified 7 | * by the given `selector` function. 8 | * 9 | * @param {(value: T) => R} selector 10 | * @returns {Sequence} 11 | */ 12 | sortedBy(this: Sequence, selector: (value: T) => R): Sequence { 13 | return this.sorted(it => it.compareBy(selector)); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/sortedByDescending.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class SortedByDescending { 4 | 5 | /** 6 | * Returns a new sequence with all elements sorted descending by the value specified 7 | * by the given `selector` function. 8 | * 9 | * @param {(value: T) => R} selector 10 | * @returns {Sequence} 11 | */ 12 | sortedByDescending(this: Sequence, selector: (value: T) => R): Sequence { 13 | return this.sorted(it => it.compareByDescending(selector)); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/sortedDescending.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class SortedDescending { 4 | 5 | /** 6 | * Returns a new sequence with all elements sorted in reverse (descending) natural order. 7 | * 8 | * @returns {Sequence} 9 | */ 10 | sortedDescending(this: Sequence): Sequence { 11 | return this.sorted(it => it.reverseOrder()); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/sortedWith.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class SortedWith { 4 | 5 | /** 6 | * Returns a new sequence with all elements sorted be the given `compare` function. 7 | * 8 | * @param {(a: T, b: T) => number} comparison 9 | * @returns {Sequence} 10 | */ 11 | sortedWith(this: Sequence, comparison: (a: T, b: T) => number): Sequence { 12 | return this.sorted(it => it.compare(comparison)); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/sum.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Sum { 4 | 5 | /** 6 | * Returns the sum of all numbers. 7 | * 8 | * @returns {number} 9 | */ 10 | sum(this: Sequence): number { 11 | let result = 0; 12 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 13 | result += item.value; 14 | } 15 | return result; 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/sumBy.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class SumBy { 4 | 5 | /** 6 | * Returns the sum of all numbers specified by the given `selector` function. 7 | * 8 | * @param {(value: T) => number} selector 9 | * @returns {number} 10 | */ 11 | sumBy(this: Sequence, selector: (value: T) => number): number { 12 | let result = 0; 13 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 14 | result += selector(item.value); 15 | } 16 | return result; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/take.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Take { 4 | 5 | /** 6 | * Returns a new sequence consisting of the first `n` elements. All other elements 7 | * are discarded. 8 | * 9 | * @param {number} n 10 | * @returns {Sequence} 11 | */ 12 | take(this: Sequence, n: number): Sequence { 13 | return this.withIndex() 14 | .takeWhile(it => it.index < n) 15 | .map(it => it.value); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/takeWhile.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {createSequence} from "./Sequence"; 2 | 3 | class TakeWhileIterator implements Iterator { 4 | constructor(private readonly iterator: Iterator, 5 | private readonly predicate: (item: T) => boolean) { 6 | } 7 | 8 | next(value?: any): IteratorResult { 9 | const item = this.iterator.next(); 10 | if (!item.done) { 11 | const result = this.predicate(item.value); 12 | if (result) { 13 | return {done: false, value: item.value}; 14 | } 15 | } 16 | return {done: true, value: undefined as any}; 17 | } 18 | } 19 | 20 | export class TakeWhile { 21 | 22 | /** 23 | * Takes all elements of the sequence as long as the given `predicate` evaluates to true. 24 | * 25 | * @param {(item: T) => boolean} predicate 26 | * @returns {Sequence} 27 | */ 28 | takeWhile(this: Sequence, predicate: (item: T) => boolean): Sequence { 29 | return createSequence(new TakeWhileIterator(this.iterator, predicate)); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/toArray.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class ToArray { 4 | 5 | /** 6 | * Returns all elements of the sequence as array. If an `array` is passed 7 | * the elements are appended to the end of the array. 8 | * 9 | * @param {Array} array 10 | * @returns {Array} 11 | */ 12 | toArray(this: Sequence, array?: Array): Array { 13 | const result: Array = array || []; 14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 15 | result.push(item.value); 16 | } 17 | return result; 18 | } 19 | 20 | /** 21 | * Returns all elements of the sequence as array. If an `array` is passed 22 | * the elements are appended to the end of the array. 23 | * 24 | * @param {Array} array 25 | * @returns {Array} 26 | */ 27 | toList(this: Sequence, array?: Array): Array { 28 | return this.toArray(array); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/toMap.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class ToMap { 4 | 5 | /** 6 | * Returns a map consisting of each key-value pair. If a `map` is passed 7 | * the pairs are set on this map. Duplicate keys override each other. 8 | * 9 | * @param {Map} map 10 | * @returns {Map} 11 | */ 12 | toMap(this: Sequence<[K, V]>, map?: Map): Map { 13 | const result = map || new Map(); 14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 15 | const pair = item.value; 16 | const key = pair[0]; 17 | const value = pair[1]; 18 | result.set(key, value); 19 | } 20 | return result; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/toSet.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class ToSet { 4 | 5 | /** 6 | * Returns all elements of the sequence as set. If a `set` is passed 7 | * the elements are added to this set. 8 | * 9 | * @param {Set} set 10 | * @returns {Set} 11 | */ 12 | toSet(this: Sequence, set?: Set): Set { 13 | const result = set || new Set(); 14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 15 | result.add(item.value); 16 | } 17 | return result; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/unzip.ts: -------------------------------------------------------------------------------- 1 | import Sequence from "./Sequence"; 2 | 3 | export class Unzip { 4 | 5 | /** 6 | * Returns a pair of arrays where the first array contains all first values 7 | * and the second array all second values from each input pair of the sequence. 8 | * 9 | * @returns {[Array , Array]} 10 | */ 11 | unzip(this: Sequence<[T, S]>): [Array, Array] { 12 | const array1: Array = []; 13 | const array2: Array = []; 14 | for (let item = this.iterator.next(); !item.done; item = this.iterator.next()) { 15 | const [first, second] = item.value; 16 | array1.push(first); 17 | array2.push(second); 18 | } 19 | return [array1, array2]; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/withIndex.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {createSequence} from "./Sequence"; 2 | import IndexedValue from "./IndexedValue"; 3 | 4 | class IndexIterator implements Iterator> { 5 | private index = -1; 6 | 7 | constructor(private readonly iterator: Iterator) { 8 | } 9 | 10 | next(value?: any): IteratorResult> { 11 | const item = this.iterator.next(); 12 | if (item.done) { 13 | return {done: true, value: undefined as any}; 14 | } 15 | this.index++; 16 | return { 17 | done: false, 18 | value: { 19 | index: this.index, 20 | value: item.value 21 | } 22 | }; 23 | } 24 | } 25 | 26 | export class WithIndex { 27 | 28 | /** 29 | * Returns a new sequence consisting of indexed values for all original elements. 30 | * 31 | * @returns {Sequence>} 32 | */ 33 | withIndex(this: Sequence): Sequence> { 34 | return createSequence(new IndexIterator(this.iterator)); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/zip.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {createSequence} from "./Sequence"; 2 | 3 | class ZipIterator implements Iterator<[T, S]> { 4 | constructor(private readonly iterator1: Iterator, 5 | private readonly iterator2: Iterator) { 6 | } 7 | 8 | next(value?: any): IteratorResult<[T, S]> { 9 | const item1 = this.iterator1.next(); 10 | const item2 = this.iterator2.next(); 11 | if (item1.done || item2.done) { 12 | return {done: true, value: undefined as any}; 13 | } else { 14 | return {done: false, value: [item1.value, item2.value]}; 15 | } 16 | } 17 | 18 | } 19 | 20 | export class Zip { 21 | 22 | /** 23 | * Returns a new sequence consisting of pairs built the elements of both sequences 24 | * with the same index. The resulting sequence has the length of the shortest input 25 | * sequence. All other elements are discarded. 26 | * 27 | * @param {Sequence} other 28 | * @returns {Sequence<[T , S]>} 29 | */ 30 | zip(this: Sequence, other: Sequence): Sequence<[T, S]> { 31 | return createSequence(new ZipIterator(this.iterator, other.iterator)); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /test/all.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("all", () => { 4 | it("should return false", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .all(it => it > 1); 7 | expect(result).toBe(false); 8 | }); 9 | 10 | it("should return true", () => { 11 | const result = sequenceOf(1, 2, 3) 12 | .all(it => it > 0); 13 | expect(result).toBe(true); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/any.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("any", () => { 4 | it("should return false", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filter(it => it > 3) 7 | .any(); 8 | expect(result).toBe(false); 9 | }); 10 | 11 | it("should return true", () => { 12 | const result = sequenceOf(1, 2, 3) 13 | .filter(it => it > 1) 14 | .any(); 15 | expect(result).toBe(true); 16 | }); 17 | 18 | it("should evaluate predicate and return false", () => { 19 | const result = sequenceOf(1, 2, 3) 20 | .any(it => it > 3); 21 | expect(result).toBe(false); 22 | }); 23 | 24 | it("should evaluate predicate and return true", () => { 25 | const result = sequenceOf(1, 2, 3) 26 | .any(it => it > 2); 27 | expect(result).toBe(true); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/asIterable.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("asIterable", () => { 4 | it("should return an iterable object conforming to the iterator-protocol", () => { 5 | const iterable = sequenceOf(1, 2, 3, 4, 5) 6 | .filter(it => it % 2 === 1) 7 | .asIterable(); 8 | 9 | const iterator = iterable[Symbol.iterator](); 10 | expect(iterator.next().value).toBe(1); 11 | expect(iterator.next().value).toBe(3); 12 | expect(iterator.next().value).toBe(5); 13 | expect(iterator.next().done).toBe(true); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/asSequence.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence} from "../src/Sequence"; 2 | 3 | describe("asSequence", () => { 4 | it("should create sequence from array", () => { 5 | const array = asSequence([1, 2, 3]) 6 | .filter(it => it > 1) 7 | .map(it => `num ${it}`) 8 | .toArray(); 9 | 10 | expect(array.length).toBe(2); 11 | expect(array[0]).toBe("num 2"); 12 | expect(array[1]).toBe("num 3"); 13 | }); 14 | 15 | it("should create sequence from object keys", () => { 16 | const keys = (Object as any).keys({"a": 1, "b": 2, "c": 3}); 17 | const array = asSequence(keys) 18 | .toArray(); 19 | 20 | expect(array.length).toBe(3); 21 | expect(array[0]).toBe("a"); 22 | expect(array[1]).toBe("b"); 23 | expect(array[2]).toBe("c"); 24 | }); 25 | 26 | it("should create sequence from object values", () => { 27 | const values = (Object as any).values({"a": 1, "b": 2, "c": 3}); 28 | const array = asSequence(values) 29 | .toArray(); 30 | 31 | expect(array.length).toBe(3); 32 | expect(array[0]).toBe(1); 33 | expect(array[1]).toBe(2); 34 | expect(array[2]).toBe(3); 35 | }); 36 | 37 | it("should create sequence from set", () => { 38 | const array = asSequence(new Set([1, 2, 3])) 39 | .toArray(); 40 | 41 | expect(array.length).toBe(3); 42 | expect(array[0]).toBe(1); 43 | expect(array[1]).toBe(2); 44 | expect(array[2]).toBe(3); 45 | }); 46 | 47 | it("should create sequence from map", () => { 48 | const map = new Map(); 49 | map.set("a", 1); 50 | map.set("b", 2); 51 | map.set("c", 3); 52 | 53 | const array = asSequence(map) 54 | .toArray(); 55 | 56 | expect(array.length).toBe(3); 57 | expect(array[0]).toEqual(["a", 1]); 58 | expect(array[1]).toEqual(["b", 2]); 59 | expect(array[2]).toEqual(["c", 3]); 60 | }); 61 | 62 | it("should throw understandable error message if input is undefined", () => { 63 | expect( 64 | () => asSequence(undefined as Array).toArray() 65 | ).toThrowError("Cannot create sequence for input: undefined"); 66 | }); 67 | 68 | it("should throw understandable error message if input is null", () => { 69 | expect( 70 | () => asSequence(null as Array).toArray() 71 | ).toThrowError("Cannot create sequence for input: null"); 72 | }); 73 | 74 | it("should throw understandable error message if input is not iterable", () => { 75 | expect( 76 | () => asSequence({} as Array).toArray() 77 | ).toThrowError("Cannot create sequence for non-iterable input: [object Object]"); 78 | }); 79 | }); -------------------------------------------------------------------------------- /test/associate.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("associate", () => { 4 | it("should associate map", () => { 5 | const map = sequenceOf(1, 2, 3) 6 | .associate(it => ([`key_${it}`, it])); 7 | expect(map.size).toBe(3); 8 | expect(map.get("key_1")).toBe(1); 9 | expect(map.get("key_2")).toBe(2); 10 | expect(map.get("key_3")).toBe(3); 11 | }); 12 | 13 | it("latest entries should win in case of duplicates", () => { 14 | const map = sequenceOf({k: 1, v: 1}, {k: 1, v: 11}, {k: 1, v: 111}, {k: 2, v: 222}) 15 | .associate(it => ([it.k, it.v])); 16 | expect(map.size).toBe(2); 17 | expect(map.get(1)).toBe(111); 18 | expect(map.get(2)).toBe(222); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/associateBy.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("associateBy", () => { 4 | it("should associate map by keySelector", () => { 5 | const a = {k: 1, v: 11}; 6 | const b = {k: 2, v: 22}; 7 | const c = {k: 3, v: 33}; 8 | 9 | const map = sequenceOf(a, b, c) 10 | .associateBy(it => it.k); 11 | 12 | expect(map.size).toBe(3); 13 | expect(map.get(1)).toBe(a); 14 | expect(map.get(2)).toBe(b); 15 | expect(map.get(3)).toBe(c); 16 | }); 17 | 18 | it("should associate map by key", () => { 19 | const a = {k: 1, v: 11}; 20 | const b = {k: 2, v: 22}; 21 | const c = {k: 3, v: 33}; 22 | 23 | const map = sequenceOf(a, b, c) 24 | .associateBy("k"); 25 | 26 | expect(map.size).toBe(3); 27 | expect(map.get(1)).toBe(a); 28 | expect(map.get(2)).toBe(b); 29 | expect(map.get(3)).toBe(c); 30 | }); 31 | 32 | it("should associate map by keySelector and valueTransformer", () => { 33 | const a = {k: 1, v: 11}; 34 | const b = {k: 2, v: 22}; 35 | const c = {k: 3, v: 33}; 36 | 37 | const map = sequenceOf(a, b, c) 38 | .associateBy( 39 | it => it.k, 40 | it => it.v 41 | ); 42 | 43 | expect(map.size).toBe(3); 44 | expect(map.get(1)).toBe(11); 45 | expect(map.get(2)).toBe(22); 46 | expect(map.get(3)).toBe(33); 47 | }); 48 | 49 | it("should associate map by key and valueTransformer", () => { 50 | const a = {k: 1, v: 11}; 51 | const b = {k: 2, v: 22}; 52 | const c = {k: 3, v: 33}; 53 | 54 | const map = sequenceOf(a, b, c) 55 | .associateBy( 56 | "k", 57 | it => it.v 58 | ); 59 | 60 | expect(map.size).toBe(3); 61 | expect(map.get(1)).toBe(11); 62 | expect(map.get(2)).toBe(22); 63 | expect(map.get(3)).toBe(33); 64 | }); 65 | 66 | it("latest entries should win in case of duplicates", () => { 67 | const a = {k: 1, v: 11}; 68 | const b = {k: 2, v: 22}; 69 | const c = {k: 3, v: 33}; 70 | const d = {k: 2, v: 222}; 71 | 72 | const map = sequenceOf(a, b, c, d) 73 | .associateBy( 74 | it => it.k, 75 | it => it.v 76 | ); 77 | 78 | expect(map.size).toBe(3); 79 | expect(map.get(1)).toBe(11); 80 | expect(map.get(2)).toBe(222); 81 | expect(map.get(3)).toBe(33); 82 | }); 83 | }); -------------------------------------------------------------------------------- /test/average.test.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("average", () => { 4 | it("should calculate average", () => { 5 | const avg = sequenceOf(1, 2, 3, 4) 6 | .average(); 7 | expect(avg).toBe(2.5); 8 | }); 9 | 10 | it("should return NaN on empty sequence", () => { 11 | const sequence = emptySequence() as Sequence; 12 | const avg = sequence.average(); 13 | expect(avg).toBeNaN(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/browsertest-lib.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | browsertest: lib-umd 6 | 7 | 8 | 9 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/chunk.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("chunk", () => { 4 | it("should return list of chunks", () => { 5 | const chunks = sequenceOf(1, 2, 3, 4, 5) 6 | .chunk(2); 7 | expect(chunks.length).toBe(3); 8 | 9 | expect(chunks[0].length).toBe(2); 10 | expect(chunks[0][0]).toBe(1); 11 | expect(chunks[0][1]).toBe(2); 12 | 13 | expect(chunks[1].length).toBe(2); 14 | expect(chunks[1][0]).toBe(3); 15 | expect(chunks[1][1]).toBe(4); 16 | 17 | expect(chunks[2].length).toBe(1); 18 | expect(chunks[2][0]).toBe(5); 19 | }); 20 | 21 | it("should return single chunk", () => { 22 | const chunks = sequenceOf(1, 2, 3) 23 | .chunk(5); 24 | expect(chunks.length).toBe(1); 25 | expect(chunks[0].length).toBe(3); 26 | expect(chunks[0][0]).toBe(1); 27 | expect(chunks[0][1]).toBe(2); 28 | expect(chunks[0][2]).toBe(3); 29 | }); 30 | 31 | it("should return one-size chunks", () => { 32 | const chunks = sequenceOf(1, 2, 3) 33 | .chunk(1); 34 | expect(chunks.length).toBe(3); 35 | expect(chunks[0].length).toBe(1); 36 | expect(chunks[0][0]).toBe(1); 37 | expect(chunks[1].length).toBe(1); 38 | expect(chunks[1][0]).toBe(2); 39 | expect(chunks[2].length).toBe(1); 40 | expect(chunks[2][0]).toBe(3); 41 | }); 42 | 43 | it("should throw", () => { 44 | expect( 45 | () => sequenceOf(1, 2, 3).chunk(0) 46 | ).toThrow(); 47 | expect( 48 | () => sequenceOf(1, 2, 3).chunk(-1) 49 | ).toThrow(); 50 | }); 51 | }); -------------------------------------------------------------------------------- /test/contains.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("contains", () => { 4 | it("should contain element", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .contains(3); 7 | expect(result).toBe(true); 8 | }); 9 | 10 | it("should not contain element", () => { 11 | const result = sequenceOf(1, 2, 3) 12 | .contains(4); 13 | expect(result).toBe(false); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/count.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("count", () => { 4 | 5 | it("should count results", () => { 6 | const num = sequenceOf(1, 2, 3).count(); 7 | expect(num).toBe(3); 8 | }); 9 | 10 | it("should evaluate predicate and count results", () => { 11 | const num = sequenceOf(1, 2, 3) 12 | .count(it => it > 1); 13 | expect(num).toBe(2); 14 | }); 15 | 16 | }); -------------------------------------------------------------------------------- /test/distinct.test.ts: -------------------------------------------------------------------------------- 1 | import {range, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("distinct", () => { 4 | it("should dismiss duplicate items", () => { 5 | const result = sequenceOf(1, 1, 2, 3) 6 | .distinct() 7 | .toArray(); 8 | expect(result.length).toBe(3); 9 | expect(result[0]).toBe(1); 10 | expect(result[1]).toBe(2); 11 | expect(result[2]).toBe(3); 12 | }); 13 | 14 | it.skip("distinct performance test", () => { 15 | const t0 = Date.now(); 16 | const result = range(1, 1_000_000) 17 | .distinct() 18 | .toArray(); 19 | expect(result.length).toBe(1_000_000); 20 | const took = Date.now() - t0; 21 | console.log("Took %s ms", took); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/distinctBy.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("distinctBy", () => { 4 | it("should dismiss items with duplicate selections", () => { 5 | const result = sequenceOf({a: 1}, {a: 2}, {a: 1}, {a: 3}) 6 | .distinctBy(it => it.a) 7 | .toArray(); 8 | expect(result.length).toBe(3); 9 | expect(result[0].a).toBe(1); 10 | expect(result[1].a).toBe(2); 11 | expect(result[2].a).toBe(3); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/drop.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("drop", () => { 4 | it("should drop 2 items", () => { 5 | const result = sequenceOf(1, 2, 3, 4) 6 | .drop(2) 7 | .toArray(); 8 | expect(result.length).toBe(2); 9 | expect(result[0]).toBe(3); 10 | expect(result[1]).toBe(4); 11 | }); 12 | 13 | it("should drop all items", () => { 14 | const result = sequenceOf(1, 2, 3, 4) 15 | .drop(4) 16 | .toArray(); 17 | expect(result.length).toBe(0); 18 | }); 19 | 20 | it("should drop all items even if overflow", () => { 21 | const result = sequenceOf(1, 2, 3, 4) 22 | .drop(10) 23 | .toArray(); 24 | expect(result.length).toBe(0); 25 | }); 26 | 27 | it("should drop nothing for n = 0", () => { 28 | const result = sequenceOf(1, 2, 3) 29 | .drop(0) 30 | .toArray(); 31 | expect(result.length).toBe(3); 32 | expect(result[0]).toBe(1); 33 | expect(result[1]).toBe(2); 34 | expect(result[2]).toBe(3); 35 | }); 36 | 37 | it("should drop nothing for n < 0", () => { 38 | const result = sequenceOf(1, 2, 3) 39 | .drop(-10) 40 | .toArray(); 41 | expect(result.length).toBe(3); 42 | expect(result[0]).toBe(1); 43 | expect(result[1]).toBe(2); 44 | expect(result[2]).toBe(3); 45 | }); 46 | }); -------------------------------------------------------------------------------- /test/dropWhile.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("dropWhile", () => { 4 | it("should drop elements until predicate evaluates to false", () => { 5 | const result = sequenceOf(1, 2, 3, 2, 1) 6 | .dropWhile(it => it < 3) 7 | .toArray(); 8 | expect(result.length).toBe(3); 9 | expect(result[0]).toBe(3); 10 | expect(result[1]).toBe(2); 11 | expect(result[2]).toBe(1); 12 | }); 13 | 14 | it("should drop no elements", () => { 15 | const result = sequenceOf(1, 2, 3) 16 | .dropWhile(it => it > 3) 17 | .toArray(); 18 | expect(result.length).toBe(3); 19 | expect(result[0]).toBe(1); 20 | expect(result[1]).toBe(2); 21 | expect(result[2]).toBe(3); 22 | }); 23 | 24 | it("should drop all elements", () => { 25 | const result = sequenceOf(1, 2, 3) 26 | .dropWhile(it => it > 0) 27 | .toArray(); 28 | expect(result.length).toBe(0); 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/elementAt.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("elementAt", () => { 4 | it("should return element at first index", () => { 5 | const item = sequenceOf(1, 2, 3) 6 | .elementAt(0); 7 | expect(item).toBe(1); 8 | }); 9 | 10 | it("should return element at middle index", () => { 11 | const item = sequenceOf(1, 2, 3) 12 | .elementAt(1); 13 | expect(item).toBe(2); 14 | }); 15 | 16 | it("should return element at last index", () => { 17 | const item = sequenceOf(1, 2, 3) 18 | .elementAt(2); 19 | expect(item).toBe(3); 20 | }); 21 | 22 | it("should throw error when index out of bounds", () => { 23 | expect( 24 | () => sequenceOf(1, 2, 3).elementAt(3) 25 | ).toThrow(); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/elementAtOrElse.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("elementAtOrElse", () => { 4 | it("should return element at first index", () => { 5 | const item = sequenceOf(1, 2, 3) 6 | .elementAtOrElse(0, () => -1); 7 | expect(item).toBe(1); 8 | }); 9 | 10 | it("should return element at middle index", () => { 11 | const item = sequenceOf(1, 2, 3) 12 | .elementAtOrElse(1, () => -1); 13 | expect(item).toBe(2); 14 | }); 15 | 16 | it("should return element at last index", () => { 17 | const item = sequenceOf(1, 2, 3) 18 | .elementAtOrElse(2, () => -1); 19 | expect(item).toBe(3); 20 | }); 21 | 22 | it("should return default value when index out of bounds", () => { 23 | const item = sequenceOf(1, 2, 3) 24 | .elementAtOrElse(3, () => 1234); 25 | expect(item).toBe(1234); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/elementAtOrNull.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("elementAtOrNull", () => { 4 | it("should return element at first index", () => { 5 | const item = sequenceOf(1, 2, 3) 6 | .elementAtOrNull(0); 7 | expect(item).toBe(1); 8 | }); 9 | 10 | it("should return element at middle index", () => { 11 | const item = sequenceOf(1, 2, 3) 12 | .elementAtOrNull(1); 13 | expect(item).toBe(2); 14 | }); 15 | 16 | it("should return element at last index", () => { 17 | const item = sequenceOf(1, 2, 3) 18 | .elementAtOrNull(2); 19 | expect(item).toBe(3); 20 | }); 21 | 22 | it("should return null when index out of bounds", () => { 23 | const item = sequenceOf(1, 2, 3) 24 | .elementAtOrNull(3); 25 | expect(item).toBeNull(); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/emptySequence.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence} from "../src/Sequence"; 2 | 3 | describe("emptySequence", () => { 4 | it("should return empty array", () => { 5 | const result = emptySequence().toArray(); 6 | expect(result.length).toBe(0); 7 | }); 8 | }); -------------------------------------------------------------------------------- /test/examples.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence, generateSequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("examples", () => { 4 | 5 | it("should be beer-o-clock", () => { 6 | const result = sequenceOf("🍻", "🍻") 7 | .flatMap(it => sequenceOf("🍺", "🍺")) 8 | .toArray(); 9 | expect(result).toEqual(["🍺", "🍺", "🍺", "🍺"]); 10 | }); 11 | 12 | it("should generate sequence of fibonacci numbers", () => { 13 | const nums = 14 | generateSequence([0, 1], ([a, b]) => [b, a + b]) 15 | .map(([a, _]) => a) 16 | .take(10) 17 | .toArray(); 18 | expect(nums).toEqual([0, 1, 1, 2, 3, 5, 8, 13, 21, 34]); 19 | }); 20 | 21 | it("should iterate over chars of the given string", () => { 22 | const result = asSequence("abc") 23 | .toArray(); 24 | expect(result).toEqual(["a", "b", "c"]); 25 | }); 26 | 27 | it("should create sequences from es6 generators", () => { 28 | function* generator() { 29 | let i = 0; 30 | while (true) { 31 | yield i++; 32 | } 33 | } 34 | 35 | const result = asSequence(generator()) 36 | .take(3) 37 | .toArray(); 38 | expect(result).toEqual([0, 1, 2]); 39 | }); 40 | 41 | }); -------------------------------------------------------------------------------- /test/extendSequence.test.ts: -------------------------------------------------------------------------------- 1 | import Sequence, {extendSequence, sequenceOf} from "../src/Sequence"; 2 | 3 | class GreetAll { 4 | greetAll(this: Sequence): string { 5 | const names = this.joinToString({ separator: ", " }); 6 | return "Hello " + names + " !"; 7 | } 8 | } 9 | 10 | declare module "../src/Sequence" { 11 | export default interface Sequence extends GreetAll { 12 | } 13 | } 14 | 15 | describe("extendSequence", () => { 16 | it("should extend Sequence implementation prototype with provided mixin", () => { 17 | extendSequence(GreetAll); 18 | const names = sequenceOf("John", "Bob", "Steve"); 19 | const greetings = names.greetAll(); 20 | expect(greetings).toBe("Hello John, Bob, Steve !"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/filter.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("filter", () => { 4 | it("should filter elements", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .filter(it => it > 1) 7 | .toArray(); 8 | 9 | expect(array.length).toBe(2); 10 | expect(array[0]).toBe(2); 11 | expect(array[1]).toBe(3); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/filterIndexed.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("filterIndexed", () => { 4 | it("should filter elements by index", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .filterIndexed((index, value) => index < 2) 7 | .toArray(); 8 | 9 | expect(array.length).toBe(2); 10 | expect(array[0]).toBe(1); 11 | expect(array[1]).toBe(2); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/filterNot.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("filterNot", () => { 4 | it("should filter elements", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filterNot(it => it > 2) 7 | .toArray(); 8 | expect(result.length).toBe(2); 9 | expect(result[0]).toBe(1); 10 | expect(result[1]).toBe(2); 11 | }); 12 | }); -------------------------------------------------------------------------------- /test/filterNotNull.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("filterNotNull", () => { 4 | it("should skip null elements", () => { 5 | const array = sequenceOf(1, null, 2, null, 3) 6 | .filterNotNull() 7 | .toArray(); 8 | 9 | expect(array.length).toBe(3); 10 | expect(array[0]).toBe(1); 11 | expect(array[1]).toBe(2); 12 | expect(array[2]).toBe(3); 13 | }); 14 | }); -------------------------------------------------------------------------------- /test/find.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("find", () => { 4 | it("should return first element of sequence", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filter(it => it > 2) 7 | .find(); 8 | expect(result).toBe(3); 9 | }); 10 | 11 | it("should return null on empty sequence", () => { 12 | const result = sequenceOf(1, 2, 3) 13 | .filter(it => it > 3) 14 | .find(); 15 | expect(result).toBeNull(); 16 | }); 17 | 18 | it("should return first element matching predicate", () => { 19 | const result = sequenceOf(1, 2, 3) 20 | .find(it => it > 2); 21 | expect(result).toBe(3); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/findLast.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("findLast", () => { 4 | it("should return last element of sequence", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .findLast(); 7 | expect(result).toBe(3); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const result = sequenceOf(1, 2, 3) 12 | .filter(it => it > 3) 13 | .findLast(); 14 | expect(result).toBeNull(); 15 | }); 16 | 17 | it("should return last element matching predicate", () => { 18 | const result = sequenceOf(1, 2, 3) 19 | .findLast(it => it > 1); 20 | expect(result).toBe(3); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/first.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("first", () => { 4 | it("should return first element of sequence", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filter(it => it > 2) 7 | .first(); 8 | expect(result).toBe(3); 9 | }); 10 | 11 | it("should throw error on empty sequence", () => { 12 | expect( 13 | () => sequenceOf(1, 2, 3) 14 | .filter(it => it > 3) 15 | .first() 16 | ).toThrow("No such element"); 17 | }); 18 | 19 | it("should return first element matching predicate", () => { 20 | const result = sequenceOf(1, 2, 3) 21 | .first(it => it > 2); 22 | expect(result).toBe(3); 23 | }); 24 | 25 | it("should return null if the first element is null", () => { 26 | const result = sequenceOf(null) 27 | .first(); 28 | expect(result).toBeNull(); 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/firstOrNull.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("firstOrNull", () => { 4 | it("should return first element of sequence", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filter(it => it > 2) 7 | .firstOrNull(); 8 | expect(result).toBe(3); 9 | }); 10 | 11 | it("should return null on empty sequence", () => { 12 | const result = sequenceOf(1, 2, 3) 13 | .filter(it => it > 3) 14 | .firstOrNull(); 15 | expect(result).toBeNull(); 16 | }); 17 | 18 | it("should return first element matching predicate", () => { 19 | const result = sequenceOf(1, 2, 3) 20 | .firstOrNull(it => it > 2); 21 | expect(result).toBe(3); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/flatMap.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("flatMap", () => { 4 | it("should flatten element arrays", () => { 5 | const array = sequenceOf([1, 2], [3, 4], [5, 6]) 6 | .flatMap(it => asSequence(it)) 7 | .toArray(); 8 | 9 | expect(array.length).toBe(6); 10 | expect(array[0]).toBe(1); 11 | expect(array[1]).toBe(2); 12 | expect(array[2]).toBe(3); 13 | expect(array[3]).toBe(4); 14 | expect(array[4]).toBe(5); 15 | expect(array[5]).toBe(6); 16 | }); 17 | }); -------------------------------------------------------------------------------- /test/flatten.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("flatten", () => { 4 | it("should flatten sequence of sequences", () => { 5 | const array = sequenceOf(sequenceOf(1, 2), sequenceOf(3, 4), sequenceOf(5, 6)) 6 | .flatten() 7 | .toArray(); 8 | expect(array.length).toBe(6); 9 | expect(array[0]).toBe(1); 10 | expect(array[1]).toBe(2); 11 | expect(array[2]).toBe(3); 12 | expect(array[3]).toBe(4); 13 | expect(array[4]).toBe(5); 14 | expect(array[5]).toBe(6); 15 | }); 16 | 17 | it("should flatten sequence of arrays", () => { 18 | const array = sequenceOf([1, 2], [3, 4], [5, 6]) 19 | .flatten() 20 | .toArray(); 21 | expect(array.length).toBe(6); 22 | expect(array[0]).toBe(1); 23 | expect(array[1]).toBe(2); 24 | expect(array[2]).toBe(3); 25 | expect(array[3]).toBe(4); 26 | expect(array[4]).toBe(5); 27 | expect(array[5]).toBe(6); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/fold.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("fold", () => { 4 | it("should 23 + sum of all numbers", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .fold(23, (acc: number, value: number) => acc + value); 7 | expect(result).toBe(29); 8 | }); 9 | 10 | it("should return initial value on empty sequence", () => { 11 | const result = emptySequence() 12 | .fold(23, (acc: number, value: number) => acc + value); 13 | expect(result).toBe(23); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/foldIndexed.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("foldIndexed", () => { 4 | it("should 23 + sum of all numbers and indices", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .foldIndexed(23, (index: number, acc: number, element: number) => acc + element + index); 7 | expect(result).toBe(32); 8 | }); 9 | 10 | it("should return initial value on empty sequence", () => { 11 | const result = emptySequence() 12 | .foldIndexed(23, (index: number, acc: number, element: number) => acc + element + index); 13 | expect(result).toBe(23); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/forEach.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("forEach", () => { 4 | it("should call action for each element", () => { 5 | const array: Array = []; 6 | sequenceOf(1, 2, 3) 7 | .forEach(it => array.push(it)); 8 | expect(array[0]).toBe(1); 9 | expect(array[1]).toBe(2); 10 | expect(array[2]).toBe(3); 11 | }); 12 | }); -------------------------------------------------------------------------------- /test/forEachIndexed.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("forEach", () => { 4 | it("should call action for each element", () => { 5 | const array = []; 6 | sequenceOf(1, 2, 3) 7 | .forEachIndexed((index, value) => array.push(`${index}: ${value}`)); 8 | expect(array[0]).toBe("0: 1"); 9 | expect(array[1]).toBe("1: 2"); 10 | expect(array[2]).toBe("2: 3"); 11 | }); 12 | }); -------------------------------------------------------------------------------- /test/generateSequence.test.ts: -------------------------------------------------------------------------------- 1 | import {generateSequence} from "../src/Sequence"; 2 | 3 | describe("generateSequence", () => { 4 | it("should generate sequence", () => { 5 | let count = 0; 6 | const result = generateSequence(() => count++) 7 | .take(5) 8 | .toArray(); 9 | expect(result.length).toBe(5); 10 | expect(result[0]).toBe(0); 11 | expect(result[1]).toBe(1); 12 | expect(result[2]).toBe(2); 13 | expect(result[3]).toBe(3); 14 | expect(result[4]).toBe(4); 15 | }); 16 | 17 | it("should generate sequence with drop and take", () => { 18 | let count = 0; 19 | const result = generateSequence(() => count++) 20 | .drop(5) 21 | .take(5) 22 | .toArray(); 23 | expect(result.length).toBe(5); 24 | expect(result[0]).toBe(5); 25 | expect(result[1]).toBe(6); 26 | expect(result[2]).toBe(7); 27 | expect(result[3]).toBe(8); 28 | expect(result[4]).toBe(9); 29 | }); 30 | 31 | it("should generate sequence with seed", () => { 32 | const result = generateSequence(10, value => value + 1) 33 | .takeWhile(it => it < 15) 34 | .toArray(); 35 | expect(result.length).toBe(5); 36 | expect(result[0]).toBe(10); 37 | expect(result[1]).toBe(11); 38 | expect(result[2]).toBe(12); 39 | expect(result[3]).toBe(13); 40 | expect(result[4]).toBe(14); 41 | }); 42 | 43 | it("should generate empty sequence with seed of null", () => { 44 | const result = generateSequence(null as number, a => a) 45 | .count(); 46 | expect(result).toBe(0); 47 | }); 48 | 49 | it("should generate empty sequence with seed of undefined", () => { 50 | const result = generateSequence(undefined as number, a => a) 51 | .count(); 52 | expect(result).toBe(0); 53 | }); 54 | 55 | it("should generate sequence with seedFunction", () => { 56 | const result = generateSequence(() => 10, value => value + 1) 57 | .takeWhile(it => it < 15) 58 | .toArray(); 59 | expect(result.length).toBe(5); 60 | expect(result[0]).toBe(10); 61 | expect(result[1]).toBe(11); 62 | expect(result[2]).toBe(12); 63 | expect(result[3]).toBe(13); 64 | expect(result[4]).toBe(14); 65 | }); 66 | 67 | it("should generate empty sequence with seedFunction result of null", () => { 68 | const result = generateSequence(() => null as number, a => a) 69 | .count(); 70 | expect(result).toBe(0); 71 | }); 72 | 73 | it("should generate empty sequence with seedFunction result of undefined", () => { 74 | const result = generateSequence(() => undefined as number, a => a) 75 | .count(); 76 | expect(result).toBe(0); 77 | }); 78 | }); -------------------------------------------------------------------------------- /test/groupBy.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("groupBy", () => { 4 | it("should group by keySelector", () => { 5 | const a = {k: 1, v: 11}; 6 | const b = {k: 2, v: 22}; 7 | const c = {k: 3, v: 33}; 8 | const d = {k: 2, v: 222}; 9 | 10 | const map = sequenceOf(a, b, c, d) 11 | .groupBy(it => it.k); 12 | 13 | expect(map.size).toBe(3); 14 | expect(map.get(1)[0]).toBe(a); 15 | expect(map.get(2)[0]).toBe(b); 16 | expect(map.get(2)[1]).toBe(d); 17 | expect(map.get(3)[0]).toBe(c); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/indexOf.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("indexOf", () => { 4 | it("should return index of element", () => { 5 | const index = sequenceOf(1, 2, 3) 6 | .indexOf(3); 7 | expect(index).toBe(2); 8 | }); 9 | 10 | it("should return -1 if element not found", () => { 11 | const index = sequenceOf(1, 2, 3) 12 | .indexOf(4); 13 | expect(index).toBe(-1); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/indexOfFirst.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("indexOfFirst", () => { 4 | it("should return index of first element matching given predicate", () => { 5 | const index = sequenceOf(1, 2, 2, 3) 6 | .indexOfFirst(it => it > 1); 7 | expect(index).toBe(1); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/indexOfLast.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("indexOfLast", () => { 4 | it("should return index of last element matching given predicate", () => { 5 | const index = sequenceOf(1, 2, 2, 1) 6 | .indexOfLast(it => it > 1); 7 | expect(index).toBe(2); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/joinTo.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("joinTo", () => { 4 | it("should join to given string", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .joinTo({value: "List: ", prefix: "[ ", postfix: " ]"}); 7 | expect(result).toBe("List: [ 1, 2, 3 ]"); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/joinToString.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("joinToString", () => { 4 | it("should join to string using default config", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .joinToString(); 7 | expect(result).toBe("1, 2, 3"); 8 | }); 9 | 10 | it("should join to string using different separator", () => { 11 | const result = sequenceOf(1, 2, 3) 12 | .joinToString({separator: " | "}); 13 | expect(result).toBe("1 | 2 | 3"); 14 | }); 15 | 16 | it("should join to string using different prefix and postfix", () => { 17 | const result = sequenceOf(1, 2, 3) 18 | .joinToString({prefix: "[ ", postfix: " ]"}); 19 | expect(result).toBe("[ 1, 2, 3 ]"); 20 | }); 21 | 22 | it("should join to string using transform function", () => { 23 | const result = sequenceOf(1, 2, 3) 24 | .joinToString({transform: num => `a${num}`}); 25 | expect(result).toBe("a1, a2, a3"); 26 | }); 27 | 28 | it("should join to string limiting number of items joined", () => { 29 | const result = sequenceOf(1, 2, 3, 4, 5) 30 | .joinToString({limit: 3}); 31 | expect(result).toBe("1, 2, 3, ..."); 32 | }); 33 | }); -------------------------------------------------------------------------------- /test/last.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("last", () => { 4 | it("should return last element of sequence", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filter(it => it > 1) 7 | .last(); 8 | expect(result).toBe(3); 9 | }); 10 | 11 | it("should throw error on empty sequence", () => { 12 | expect( 13 | () => sequenceOf(1, 2, 3) 14 | .filter(it => it > 3) 15 | .last() 16 | ).toThrow("No such element"); 17 | }); 18 | 19 | it("should return last element matching predicate", () => { 20 | const result = sequenceOf(1, 2, 3) 21 | .last(it => it > 1); 22 | expect(result).toBe(3); 23 | }); 24 | 25 | it("should return null if the last element is null", () => { 26 | const result = sequenceOf(1, 2, null) 27 | .last(); 28 | expect(result).toBeNull(); 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/lastOrNull.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("lastOrNull", () => { 4 | it("should return last element of sequence", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .lastOrNull(); 7 | expect(result).toBe(3); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const result = sequenceOf(1, 2, 3) 12 | .filter(it => it > 3) 13 | .lastOrNull(); 14 | expect(result).toBeNull(); 15 | }); 16 | 17 | it("should return last element matching predicate", () => { 18 | const result = sequenceOf(1, 2, 3) 19 | .lastOrNull(it => it > 1); 20 | expect(result).toBe(3); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/map.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("map", () => { 4 | it("should map numbers to strings", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .map(it => `num ${it}`) 7 | .toArray(); 8 | 9 | expect(array.length).toBe(3); 10 | expect(array[0]).toBe("num 1"); 11 | expect(array[1]).toBe("num 2"); 12 | expect(array[2]).toBe("num 3"); 13 | }); 14 | }); -------------------------------------------------------------------------------- /test/mapIndexed.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("mapIndexed", () => { 4 | it("should map elements by index and value", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .mapIndexed((index, value) => `${index}: ${value}`) 7 | .toArray(); 8 | 9 | expect(array.length).toBe(3); 10 | expect(array[0]).toBe("0: 1"); 11 | expect(array[1]).toBe("1: 2"); 12 | expect(array[2]).toBe("2: 3"); 13 | }); 14 | }); -------------------------------------------------------------------------------- /test/mapNotNull.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("mapNotNull", () => { 4 | it("should map to non-null items", () => { 5 | const a1 = {a: 1}; 6 | const a2 = {a: null}; 7 | const a3 = {a: null}; 8 | const a4 = {a: 4}; 9 | 10 | const array = sequenceOf(a1, a2, a3, a4) 11 | .mapNotNull(it => it.a) 12 | .toArray(); 13 | 14 | expect(array.length).toBe(2); 15 | expect(array[0]).toBe(1); 16 | expect(array[1]).toBe(4); 17 | }); 18 | }); -------------------------------------------------------------------------------- /test/max.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("max", () => { 4 | it("should return max element", () => { 5 | const num = sequenceOf(1, 3, 2, 6, 3) 6 | .max(); 7 | expect(num).toBe(6); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const num = emptySequence() 12 | .max(); 13 | expect(num).toBeNull(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/maxBy.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("maxBy", () => { 4 | it("should return max element by selector", () => { 5 | const num = sequenceOf({a: 1}, {a: 3}, {a: 2}) 6 | .maxBy(it => it.a); 7 | expect(num).toEqual({a: 3}); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const num = emptySequence() 12 | .maxBy(() => void(0)); 13 | expect(num).toBeNull(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/maxWith.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("maxWith", () => { 4 | it("should return max element by comparator", () => { 5 | const num = sequenceOf({a: 1}, {a: 3}, {a: 2}) 6 | .maxWith((o1, o2) => o1.a > o2.a ? 1 : (o1.a < o2.a ? -1 : 0)); 7 | expect(num).toEqual({a: 3}); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const num = emptySequence() 12 | .maxWith(() => void(0)); 13 | expect(num).toBeNull(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/merge.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("merge", () => { 4 | it("should merge both sequences", () => { 5 | const result = sequenceOf({id: 1, val: "a"}, {id: 2, val: "b"}, {id: 3, val: "c"}) 6 | .merge(sequenceOf({id: 2, val: "bb"}), it => it.id) 7 | .toArray(); 8 | expect(result).toEqual([{id: 1, val: "a"}, {id: 2, val: "bb"}, {id: 3, val: "c"}]); 9 | }); 10 | 11 | it("should merge given array", () => { 12 | const result = sequenceOf({id: 1, val: "a"}, {id: 2, val: "b"}, {id: 3, val: "c"}) 13 | .merge([{id: 2, val: "bb"}], it => it.id) 14 | .toArray(); 15 | expect(result).toEqual([{id: 1, val: "a"}, {id: 2, val: "bb"}, {id: 3, val: "c"}]); 16 | }); 17 | 18 | it("should merge both sequences and append new values", () => { 19 | const result = sequenceOf({id: 1, val: "a"}, {id: 2, val: "b"}, {id: 3, val: "c"}) 20 | .merge(sequenceOf({id: 2, val: "bb"}, {id: 4, val: "d"}), it => it.id) 21 | .toArray(); 22 | expect(result).toEqual([{id: 1, val: "a"}, {id: 2, val: "bb"}, {id: 3, val: "c"}, {id: 4, val: "d"}]); 23 | }); 24 | 25 | it("should merge both sequences and prepend new values", () => { 26 | const result = sequenceOf({id: 1, val: "a"}, {id: 2, val: "b"}, {id: 3, val: "c"}) 27 | .merge(sequenceOf({id: 2, val: "bb"}, {id: 4, val: "d"}), it => it.id, true) 28 | .toArray(); 29 | expect(result).toEqual([{id: 4, val: "d"}, {id: 1, val: "a"}, {id: 2, val: "bb"}, {id: 3, val: "c"}]); 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/min.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("min", () => { 4 | it("should return min element", () => { 5 | const num = sequenceOf(3, 1, 2, 6, 3) 6 | .min(); 7 | expect(num).toBe(1); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const num = emptySequence() 12 | .min(); 13 | expect(num).toBeNull(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/minBy.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("minBy", () => { 4 | it("should return min element by selector", () => { 5 | const num = sequenceOf({a: 1}, {a: 3}, {a: 2}) 6 | .minBy(it => it.a); 7 | expect(num).toEqual({a: 1}); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const num = emptySequence() 12 | .minBy(() => void(0)); 13 | expect(num).toBeNull(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/minWith.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("minWith", () => { 4 | it("should return min element by comparator", () => { 5 | const num = sequenceOf({a: 1}, {a: 3}, {a: 2}) 6 | .minWith((o1, o2) => o1.a > o2.a ? 1 : (o1.a < o2.a ? -1 : 0)); 7 | expect(num).toEqual({a: 1}); 8 | }); 9 | 10 | it("should return null on empty sequence", () => { 11 | const num = emptySequence() 12 | .maxWith(() => void(0)); 13 | expect(num).toBeNull(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/minus.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("minus", () => { 4 | it("should remove element", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .minus(1) 7 | .toArray(); 8 | expect(array.length).toBe(2); 9 | expect(array[0]).toBe(2); 10 | expect(array[1]).toBe(3); 11 | }); 12 | 13 | it("should remove array", () => { 14 | const array = sequenceOf(1, 2, 3, 4, 5) 15 | .minus([2, 4]) 16 | .toArray(); 17 | expect(array.length).toBe(3); 18 | expect(array[0]).toBe(1); 19 | expect(array[1]).toBe(3); 20 | expect(array[2]).toBe(5); 21 | }); 22 | 23 | it("should append sequence", () => { 24 | const array = sequenceOf(1, 2, 3) 25 | .minus(sequenceOf(1, 2)) 26 | .toArray(); 27 | expect(array.length).toBe(1); 28 | expect(array[0]).toBe(3); 29 | }); 30 | 31 | it("should append empty sequence", () => { 32 | const array = sequenceOf(1, 2, 3) 33 | .minus(emptySequence()) 34 | .toArray(); 35 | expect(array.length).toBe(3); 36 | expect(array[0]).toBe(1); 37 | expect(array[1]).toBe(2); 38 | expect(array[2]).toBe(3); 39 | }); 40 | }); -------------------------------------------------------------------------------- /test/none.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("none", () => { 4 | it("should return false", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .filter(it => it > 1) 7 | .none(); 8 | expect(result).toBe(false); 9 | }); 10 | 11 | it("should return true", () => { 12 | const result = sequenceOf(1, 2, 3) 13 | .filter(it => it > 3) 14 | .none(); 15 | expect(result).toBe(true); 16 | }); 17 | 18 | it("should evaluate predicate and return false", () => { 19 | const result = sequenceOf(1, 2, 3) 20 | .none(it => it > 1); 21 | expect(result).toBe(false); 22 | }); 23 | 24 | it("should evaluate predicate and return true", () => { 25 | const result = sequenceOf(1, 2, 3) 26 | .none(it => it > 3); 27 | expect(result).toBe(true); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/onEach.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("onEach", () => { 4 | it("should call action for each element", () => { 5 | const array = []; 6 | const result = sequenceOf(1, 2, 3) 7 | .onEach(it => array.push(it)) 8 | .toArray(); 9 | expect(array[0]).toBe(result[0]); 10 | expect(array[1]).toBe(result[1]); 11 | expect(array[2]).toBe(result[2]); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/partition.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("partition", () => { 4 | it("should partition based on the given predicate", () => { 5 | const result = sequenceOf(1, 2, 3, 4) 6 | .partition(it => it % 2 === 1); 7 | 8 | expect(result.true.length).toBe(2); 9 | expect(result.true[0]).toBe(1); 10 | expect(result.true[1]).toBe(3); 11 | 12 | expect(result.false.length).toBe(2); 13 | expect(result.false[0]).toBe(2); 14 | expect(result.false[1]).toBe(4); 15 | }); 16 | }); -------------------------------------------------------------------------------- /test/plus.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("plus", () => { 4 | it("should append element", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .plus(4) 7 | .toArray(); 8 | expect(array.length).toBe(4); 9 | expect(array[0]).toBe(1); 10 | expect(array[1]).toBe(2); 11 | expect(array[2]).toBe(3); 12 | expect(array[3]).toBe(4); 13 | }); 14 | 15 | it("should append array", () => { 16 | const array = sequenceOf(1, 2, 3) 17 | .plus([4, 5]) 18 | .toArray(); 19 | expect(array.length).toBe(5); 20 | expect(array[0]).toBe(1); 21 | expect(array[1]).toBe(2); 22 | expect(array[2]).toBe(3); 23 | expect(array[3]).toBe(4); 24 | expect(array[4]).toBe(5); 25 | }); 26 | 27 | it("should append sequence", () => { 28 | const array = sequenceOf(1, 2, 3) 29 | .plus(sequenceOf(4, 5)) 30 | .toArray(); 31 | expect(array.length).toBe(5); 32 | expect(array[0]).toBe(1); 33 | expect(array[1]).toBe(2); 34 | expect(array[2]).toBe(3); 35 | expect(array[3]).toBe(4); 36 | expect(array[4]).toBe(5); 37 | }); 38 | 39 | it("should append empty sequence", () => { 40 | const array = sequenceOf(1, 2, 3) 41 | .plus(emptySequence()) 42 | .toArray(); 43 | expect(array.length).toBe(3); 44 | expect(array[0]).toBe(1); 45 | expect(array[1]).toBe(2); 46 | expect(array[2]).toBe(3); 47 | }); 48 | }); -------------------------------------------------------------------------------- /test/range.test.ts: -------------------------------------------------------------------------------- 1 | import {range} from "../src/Sequence"; 2 | 3 | describe("range", () => { 4 | it("should create range of numbers with step = 1", () => { 5 | const numbers = range(0, 5).toArray(); 6 | expect(numbers).toEqual([0, 1, 2, 3, 4, 5]); 7 | }); 8 | 9 | it("should create range of numbers with step = .5", () => { 10 | const numbers = range(0, 4, .5).toArray(); 11 | expect(numbers).toEqual([0, .5, 1, 1.5, 2, 2.5, 3, 3.5, 4.0]); 12 | }); 13 | 14 | it("should include one element", () => { 15 | const numbers = range(0, 0).toArray(); 16 | expect(numbers).toEqual([0]); 17 | }); 18 | 19 | it("should include two element", () => { 20 | const numbers = range(0, 1).toArray(); 21 | expect(numbers).toEqual([0, 1]); 22 | }); 23 | 24 | it("should throw on invalid boundaries", () => { 25 | expect(() => range(1, 0)).toThrow(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/reduce.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("reduce", () => { 4 | it("should sum all numbers", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .reduce((acc: number, value: number) => acc + value); 7 | expect(result).toBe(6); 8 | }); 9 | 10 | it("should concat all strings", () => { 11 | const result = sequenceOf("a", "b", "c") 12 | .reduce((acc: string, value: string) => `${acc}, ${value}`); 13 | expect(result).toBe("a, b, c"); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/reduceIndexed.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("reduceIndexed", () => { 4 | it("should sum all numbers + indices", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .reduceIndexed((index: number, acc: number, value: number) => acc + value + index); 7 | expect(result).toBe(9); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/reverse.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("reverse", () => { 4 | it("should reverse order", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .reverse() 7 | .toArray(); 8 | expect(array.length).toBe(3); 9 | expect(array[0]).toBe(3); 10 | expect(array[1]).toBe(2); 11 | expect(array[2]).toBe(1); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/sequenceOf.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sequenceOf", () => { 4 | it("filter-map-toArray", () => { 5 | const array = sequenceOf(1, 2, 3) 6 | .filter(it => it > 1) 7 | .map(it => `num ${it}`) 8 | .toArray(); 9 | 10 | expect(array.length).toBe(2); 11 | expect(array[0]).toBe("num 2"); 12 | expect(array[1]).toBe("num 3"); 13 | }); 14 | }); -------------------------------------------------------------------------------- /test/single.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | import single from "../src/single"; 3 | 4 | describe("single", () => { 5 | it("should return single element", () => { 6 | const result = sequenceOf(23) 7 | .single(); 8 | expect(result).toBe(23); 9 | }); 10 | 11 | it("should throw with more than one element", () => { 12 | expect( 13 | () => sequenceOf(1, 2).single() 14 | ).toThrow(); 15 | }); 16 | 17 | it("should throw with zero elements", () => { 18 | expect( 19 | () => emptySequence().single() 20 | ).toThrow(); 21 | }); 22 | 23 | it("should evaluate predicate and return single element", () => { 24 | const result = sequenceOf(1, 2, 3) 25 | .single(it => it > 2); 26 | expect(result).toBe(3); 27 | }); 28 | 29 | it("should evaluate predicate and throw with more than one element", () => { 30 | expect( 31 | () => sequenceOf(1, 2) 32 | .single(it => it > 0) 33 | ).toThrow(); 34 | }); 35 | 36 | it("should evaluate predicate and throw with zero elements", () => { 37 | expect( 38 | () => sequenceOf(1, 2, 3) 39 | .single(it => it > 3) 40 | ).toThrow(); 41 | }); 42 | }); -------------------------------------------------------------------------------- /test/singleOrNull.test.ts: -------------------------------------------------------------------------------- 1 | import {emptySequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("singleOrNull", () => { 4 | it("should return single element", () => { 5 | const result = sequenceOf(23) 6 | .singleOrNull(); 7 | expect(result).toBe(23); 8 | }); 9 | 10 | it("should return null with more than one element", () => { 11 | const result = sequenceOf(1, 2) 12 | .singleOrNull(); 13 | expect(result).toBeNull(); 14 | }); 15 | 16 | it("should return null with zero elements", () => { 17 | const result = emptySequence() 18 | .singleOrNull(); 19 | expect(result).toBeNull(); 20 | }); 21 | 22 | it("should evaluate predicate and return single element", () => { 23 | const result = sequenceOf(1, 2, 3) 24 | .singleOrNull(it => it > 2); 25 | expect(result).toBe(3); 26 | }); 27 | 28 | it("should evaluate predicate and return null with more than one element", () => { 29 | const result = sequenceOf(1, 2, 3) 30 | .singleOrNull(it => it > 1); 31 | expect(result).toBeNull(); 32 | }); 33 | 34 | it("should evaluate predicate and return null with zero elements", () => { 35 | const result = sequenceOf(1, 2, 3) 36 | .singleOrNull(it => it > 3); 37 | expect(result).toBeNull(); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/sorted.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sorted", () => { 4 | it("should sort numbers ascending", () => { 5 | const array = sequenceOf(1, 4, 3, 5, 2) 6 | .sorted() 7 | .toArray(); 8 | expect(array.length).toBe(5); 9 | expect(array[0]).toBe(1); 10 | expect(array[1]).toBe(2); 11 | expect(array[2]).toBe(3); 12 | expect(array[3]).toBe(4); 13 | expect(array[4]).toBe(5); 14 | }); 15 | 16 | it("should sort strings ascending", () => { 17 | const array = sequenceOf("1", "4", "3", "5", "2") 18 | .sorted() 19 | .toArray(); 20 | expect(array.length).toBe(5); 21 | expect(array[0]).toBe("1"); 22 | expect(array[1]).toBe("2"); 23 | expect(array[2]).toBe("3"); 24 | expect(array[3]).toBe("4"); 25 | expect(array[4]).toBe("5"); 26 | }); 27 | 28 | it("should sort numbers by natural order", () => { 29 | const array = sequenceOf(1, 4, 3, 5, 2) 30 | .sorted(it => it.naturalOrder()) 31 | .toArray(); 32 | expect(array).toEqual([1, 2, 3, 4, 5]); 33 | }); 34 | 35 | it("should sort numbers by natural order reversed", () => { 36 | const array = sequenceOf(1, 4, 3, 5, 2) 37 | .sorted(it => it.naturalOrder() 38 | .reversed()) 39 | .toArray(); 40 | expect(array).toEqual([5, 4, 3, 2, 1]); 41 | }); 42 | 43 | it("should sort numbers by reverse order", () => { 44 | const array = sequenceOf(1, 4, 3, 5, 2) 45 | .sorted(it => it.reverseOrder()) 46 | .toArray(); 47 | expect(array).toEqual([5, 4, 3, 2, 1]); 48 | }); 49 | 50 | it("should sort by given compareFn", () => { 51 | const fn = (a, b) => a < b ? 1 : a > b ? -1 : 0; 52 | const array = sequenceOf(1, 4, 3, 5, 2) 53 | .sorted(it => it.compare(fn)) 54 | .toArray(); 55 | expect(array).toEqual([5, 4, 3, 2, 1]); 56 | }); 57 | 58 | it("should sort by comparing the given property", () => { 59 | const array = sequenceOf({x: 2}, {x: 1}, {x: 3}) 60 | .sorted(it => it.compareBy(it => it.x)) 61 | .toArray(); 62 | expect(array).toEqual([{x: 1}, {x: 2}, {x: 3}]); 63 | }); 64 | 65 | it("should sort by comparing the given key", () => { 66 | const array = sequenceOf({x: 2}, {x: 1}, {x: 3}) 67 | .sorted(it => it.compareBy("x")) 68 | .toArray(); 69 | expect(array).toEqual([{x: 1}, {x: 2}, {x: 3}]); 70 | }); 71 | 72 | it("should sort by comparing the given property in reversed order", () => { 73 | const array = sequenceOf({x: 2}, {x: 1}, {x: 3}) 74 | .sorted(it => it.compareBy(it => it.x) 75 | .reversed()) 76 | .toArray(); 77 | expect(array).toEqual([{x: 3}, {x: 2}, {x: 1}]); 78 | }); 79 | 80 | it("should sort by comparing the given property descending", () => { 81 | const array = sequenceOf({x: 2}, {x: 1}, {x: 3}) 82 | .sorted(it => it.compareByDescending(it => it.x)) 83 | .toArray(); 84 | expect(array).toEqual([{x: 3}, {x: 2}, {x: 1}]); 85 | }); 86 | 87 | it("should sort by comparing the given key descending", () => { 88 | const array = sequenceOf({x: 2}, {x: 1}, {x: 3}) 89 | .sorted(it => it.compareByDescending("x")) 90 | .toArray(); 91 | expect(array).toEqual([{x: 3}, {x: 2}, {x: 1}]); 92 | }); 93 | 94 | it("should sort by comparing the given property then other property", () => { 95 | const array = sequenceOf({x: 2, y: 2}, {x: 1, y: 2}, {x: 1, y: 1}) 96 | .sorted(it => it.compareBy(it => it.x) 97 | .thenBy(it => it.y)) 98 | .toArray(); 99 | expect(array.length).toBe(3); 100 | expect(array[0]).toEqual({x: 1, y: 1}); 101 | expect(array[1]).toEqual({x: 1, y: 2}); 102 | expect(array[2]).toEqual({x: 2, y: 2}); 103 | }); 104 | 105 | it("should sort by comparing the given key then other key", () => { 106 | const array = sequenceOf({x: 2, y: 2}, {x: 1, y: 2}, {x: 1, y: 1}) 107 | .sorted(it => it.compareBy("x") 108 | .thenBy("y")) 109 | .toArray(); 110 | expect(array.length).toBe(3); 111 | expect(array[0]).toEqual({x: 1, y: 1}); 112 | expect(array[1]).toEqual({x: 1, y: 2}); 113 | expect(array[2]).toEqual({x: 2, y: 2}); 114 | }); 115 | 116 | it("should order nulls last", () => { 117 | const array = sequenceOf({x: 2}, null, {x: 1}, {x: 3}, null) 118 | .sorted(it => it.nullsLast() 119 | .thenBy(it => it.x)) 120 | .toArray(); 121 | expect(array).toEqual([{x: 1}, {x: 2}, {x: 3}, null, null]); 122 | }); 123 | 124 | it("should order nulls first then by descending selected property", () => { 125 | const array = sequenceOf({x: 2}, null, {x: 1}, {x: 3}, null) 126 | .sorted(it => it.nullsFirst() 127 | .thenByDescending(it => it.x)) 128 | .toArray(); 129 | expect(array).toEqual([null, null, {x: 3}, {x: 2}, {x: 1}]); 130 | }); 131 | 132 | it("should order nulls first then by descending key", () => { 133 | const array = sequenceOf({x: 2}, null, {x: 1}, {x: 3}, null) 134 | .sorted(it => it.nullsFirst() 135 | .thenByDescending("x")) 136 | .toArray(); 137 | expect(array).toEqual([null, null, {x: 3}, {x: 2}, {x: 1}]); 138 | }); 139 | }); -------------------------------------------------------------------------------- /test/sortedBy.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sortedBy", () => { 4 | it("should sort by the given key ascending", () => { 5 | const a4 = {a: 4}; 6 | const a1 = {a: 1}; 7 | const a3 = {a: 3}; 8 | const a23 = {a: 23}; 9 | 10 | const array = sequenceOf(a4, a1, a3, a23) 11 | .sortedBy(it => it.a) 12 | .toArray(); 13 | 14 | expect(array.length).toBe(4); 15 | expect(array[0]).toBe(a1); 16 | expect(array[1]).toBe(a3); 17 | expect(array[2]).toBe(a4); 18 | expect(array[3]).toBe(a23); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/sortedByDescending.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sortedBy", () => { 4 | it("should sort by the given key descending", () => { 5 | const a4 = {a: 4}; 6 | const a1 = {a: 1}; 7 | const a3 = {a: 3}; 8 | const a23 = {a: 23}; 9 | 10 | const array = sequenceOf(a4, a1, a3, a23) 11 | .sortedByDescending(it => it.a) 12 | .toArray(); 13 | 14 | expect(array.length).toBe(4); 15 | expect(array[0]).toBe(a23); 16 | expect(array[1]).toBe(a4); 17 | expect(array[2]).toBe(a3); 18 | expect(array[3]).toBe(a1); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/sortedDescending.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sorted", () => { 4 | it("should sort numbers descending", () => { 5 | const array = sequenceOf(1, 4, 3, 5, 2) 6 | .sortedDescending() 7 | .toArray(); 8 | expect(array.length).toBe(5); 9 | expect(array[0]).toBe(5); 10 | expect(array[1]).toBe(4); 11 | expect(array[2]).toBe(3); 12 | expect(array[3]).toBe(2); 13 | expect(array[4]).toBe(1); 14 | }); 15 | 16 | it("should sort strings descending", () => { 17 | const array = sequenceOf("1", "4", "3", "5", "2") 18 | .sortedDescending() 19 | .toArray(); 20 | expect(array.length).toBe(5); 21 | expect(array[0]).toBe("5"); 22 | expect(array[1]).toBe("4"); 23 | expect(array[2]).toBe("3"); 24 | expect(array[3]).toBe("2"); 25 | expect(array[4]).toBe("1"); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/sortedWith.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sortedWith", () => { 4 | it("should sort numbers by given comparator", () => { 5 | const array = sequenceOf(1, 4, 3, 5, 2) 6 | .sortedWith((a, b) => { 7 | if (a < b) { 8 | return 1; 9 | } 10 | if (a > b) { 11 | return -1; 12 | } 13 | return 0; 14 | }) 15 | .toArray(); 16 | expect(array.length).toBe(5); 17 | expect(array[0]).toBe(5); 18 | expect(array[1]).toBe(4); 19 | expect(array[2]).toBe(3); 20 | expect(array[3]).toBe(2); 21 | expect(array[4]).toBe(1); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/sum.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sum", () => { 4 | it("should sum all numbers", () => { 5 | const result = sequenceOf(1, 2, 3) 6 | .sum(); 7 | expect(result).toBe(6); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/sumBy.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("sumBy", () => { 4 | it("should sum all selected numbers", () => { 5 | const result = sequenceOf({a: 2}, {a: 4}, {a: 6}) 6 | .sumBy(it => it.a); 7 | expect(result).toBe(12); 8 | }); 9 | }); -------------------------------------------------------------------------------- /test/take.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("take", () => { 4 | it("should take first 2 items", () => { 5 | const result = sequenceOf(1, 2, 3, 4) 6 | .take(2) 7 | .toArray(); 8 | expect(result.length).toBe(2); 9 | expect(result[0]).toBe(1); 10 | expect(result[1]).toBe(2); 11 | }); 12 | 13 | it("should take no items", () => { 14 | const result = sequenceOf(1, 2, 3, 4) 15 | .take(0) 16 | .toArray(); 17 | expect(result.length).toBe(0); 18 | }); 19 | 20 | it("should take all items even if overflow", () => { 21 | const result = sequenceOf(1, 2, 3, 4) 22 | .take(10) 23 | .toArray(); 24 | expect(result.length).toBe(4); 25 | expect(result[0]).toBe(1); 26 | expect(result[1]).toBe(2); 27 | expect(result[2]).toBe(3); 28 | expect(result[3]).toBe(4); 29 | }); 30 | 31 | it("should take nothing for n < 0", () => { 32 | const result = sequenceOf(1, 2, 3) 33 | .take(-10) 34 | .toArray(); 35 | expect(result.length).toBe(0); 36 | }); 37 | }); -------------------------------------------------------------------------------- /test/takeWhile.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("takeWhile", () => { 4 | it("should take elements until predicate evaluates to false", () => { 5 | const result = sequenceOf(1, 2, 3, 2, 1) 6 | .takeWhile(it => it < 3) 7 | .toArray(); 8 | expect(result.length).toBe(2); 9 | expect(result[0]).toBe(1); 10 | expect(result[1]).toBe(2); 11 | }); 12 | 13 | it("should take no elements", () => { 14 | const result = sequenceOf(1, 2, 3) 15 | .takeWhile(it => it > 3) 16 | .toArray(); 17 | expect(result.length).toBe(0); 18 | }); 19 | 20 | it("should take all elements", () => { 21 | const result = sequenceOf(1, 2, 3) 22 | .takeWhile(it => it > 0) 23 | .toArray(); 24 | expect(result.length).toBe(3); 25 | expect(result[0]).toBe(1); 26 | expect(result[1]).toBe(2); 27 | expect(result[2]).toBe(3); 28 | }); 29 | }); -------------------------------------------------------------------------------- /test/toArray.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("toArray", () => { 4 | it("should return new array", () => { 5 | const input = [1, 2, 3]; 6 | const array = asSequence(input) 7 | .toArray(); 8 | expect(input).not.toBe(array); 9 | expect(array.length).toBe(3); 10 | expect(array[0]).toBe(1); 11 | expect(array[1]).toBe(2); 12 | expect(array[2]).toBe(3); 13 | }); 14 | 15 | it("should append items to passed array", () => { 16 | const array = [1]; 17 | const result = sequenceOf(2, 3, 4) 18 | .toArray(array); 19 | expect(result).toBe(array); 20 | expect(array.length).toBe(4); 21 | expect(array[0]).toBe(1); 22 | expect(array[1]).toBe(2); 23 | expect(array[2]).toBe(3); 24 | expect(array[3]).toBe(4); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/toList.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("toList", () => { 4 | it("should return new array", () => { 5 | const input = [1, 2, 3]; 6 | const array = asSequence(input) 7 | .toList(); 8 | expect(input).not.toBe(array); 9 | expect(array.length).toBe(3); 10 | expect(array[0]).toBe(1); 11 | expect(array[1]).toBe(2); 12 | expect(array[2]).toBe(3); 13 | }); 14 | 15 | it("should append items to passed array", () => { 16 | const array = [1]; 17 | const result = sequenceOf(2, 3, 4) 18 | .toList(array); 19 | expect(result).toBe(array); 20 | expect(array.length).toBe(4); 21 | expect(array[0]).toBe(1); 22 | expect(array[1]).toBe(2); 23 | expect(array[2]).toBe(3); 24 | expect(array[3]).toBe(4); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/toMap.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence} from "../src/Sequence"; 2 | 3 | describe("toMap", () => { 4 | it("should return items as new map", () => { 5 | const key1 = {k: 1}; 6 | const key2 = {k: 2}; 7 | const key3 = {k: 3}; 8 | const array: Array<[object, string]> = [[key1, "a"], [key2, "b"], [key3, "c"]]; 9 | const map = asSequence(array) 10 | .toMap(); 11 | expect(map.size).toBe(3); 12 | expect(map.get(key1)).toBe("a"); 13 | expect(map.get(key2)).toBe("b"); 14 | expect(map.get(key3)).toBe("c"); 15 | }); 16 | 17 | it("should append items to passed map", () => { 18 | const key0 = {k: 0}; 19 | const key1 = {k: 1}; 20 | const key2 = {k: 2}; 21 | const key3 = {k: 3}; 22 | 23 | const existingMap = new Map(); 24 | existingMap.set(key0, "_"); 25 | 26 | const array: Array<[object, string]> = [[key1, "a"], [key2, "b"], [key3, "c"]]; 27 | const result = asSequence(array) 28 | .toMap(existingMap); 29 | 30 | expect(result.size).toBe(4); 31 | expect(result.get(key0)).toBe("_"); 32 | expect(result.get(key1)).toBe("a"); 33 | expect(result.get(key2)).toBe("b"); 34 | expect(result.get(key3)).toBe("c"); 35 | }); 36 | }); -------------------------------------------------------------------------------- /test/toSet.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("toSet", () => { 4 | it("should return new set of distinct items", () => { 5 | const result = sequenceOf(1, 2, 2, 3, 3, 3) 6 | .toSet(); 7 | expect(result.size).toBe(3); 8 | expect(result.has(1)).toBe(true); 9 | expect(result.has(2)).toBe(true); 10 | expect(result.has(3)).toBe(true); 11 | }); 12 | 13 | it("should add distinct items to existing set", () => { 14 | const existingSet = new Set([4]); 15 | const result = sequenceOf(1, 2, 2, 3, 3, 3) 16 | .toSet(existingSet); 17 | expect(result).toBe(existingSet); 18 | expect(result.size).toBe(4); 19 | expect(result.has(1)).toBe(true); 20 | expect(result.has(2)).toBe(true); 21 | expect(result.has(3)).toBe(true); 22 | expect(result.has(4)).toBe(true); 23 | }); 24 | }); -------------------------------------------------------------------------------- /test/undefinedAndNull.test.ts: -------------------------------------------------------------------------------- 1 | import {asSequence, sequenceOf} from "../src/Sequence"; 2 | 3 | describe("undefinedAndNull", () => { 4 | it("should pass null values", () => { 5 | const result = sequenceOf(1, 2, null, 3, null, null, null, 4) 6 | .toList(); 7 | expect(result).toEqual([1, 2, null, 3, null, null, null, 4]); 8 | }); 9 | 10 | it("should pass undefined values", () => { 11 | const result = sequenceOf(1, 2, undefined, 3, undefined, undefined, undefined, 4) 12 | .toList(); 13 | expect(result).toEqual([1, 2, undefined, 3, undefined, undefined, undefined, 4]); 14 | }); 15 | 16 | it("should pass null and undefined values", () => { 17 | const result = asSequence([1, 2, null, null, 3, undefined, undefined, 4]) 18 | .filter(it => it == null || it % 2 === 1) 19 | .map(it => String(it)) 20 | .toList(); 21 | expect(result).toEqual(["1", "null", "null", "3", "undefined", "undefined"]); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/unzip.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("unzip", () => { 4 | it("should unzip items", () => { 5 | const [first, second] = sequenceOf("a", "b", "c") 6 | .zip(sequenceOf(1, 2, 3)) 7 | .unzip(); 8 | 9 | expect(first.length).toBe(3); 10 | expect(first[0]).toBe("a"); 11 | expect(first[1]).toBe("b"); 12 | expect(first[2]).toBe("c"); 13 | 14 | expect(second.length).toBe(3); 15 | expect(second[0]).toBe(1); 16 | expect(second[1]).toBe(2); 17 | expect(second[2]).toBe(3); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/zip.test.ts: -------------------------------------------------------------------------------- 1 | import {sequenceOf} from "../src/Sequence"; 2 | 3 | describe("zip", () => { 4 | it("should combine items from both sequences into pairs", () => { 5 | const array = sequenceOf("a", "b", "c") 6 | .zip(sequenceOf(1, 2, 3)) 7 | .toArray(); 8 | expect(array.length).toBe(3); 9 | expect(array[0]).toEqual(["a", 1]); 10 | expect(array[1]).toEqual(["b", 2]); 11 | expect(array[2]).toEqual(["c", 3]); 12 | }); 13 | 14 | it("should discard elements if length of sequences is different", () => { 15 | const array = sequenceOf(1, 2, 3) 16 | .zip(sequenceOf(1, 2, 3, 4, 5, 6, 7)) 17 | .toArray(); 18 | expect(array.length).toBe(3); 19 | }); 20 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "lib": ["es2015"], 6 | "outDir": "lib", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "skipLibCheck": true, 10 | "removeComments": false, 11 | "strictNullChecks": true, 12 | "noImplicitReturns": true, 13 | "noImplicitUseStrict": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true 16 | }, 17 | "include": [ 18 | "src" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "test", 23 | "lib" 24 | ] 25 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "indent": true, 6 | "no-debugger": true, 7 | "no-duplicate-variable": true, 8 | "no-eval": true, 9 | "no-internal-module": true, 10 | "no-shadowed-variable": false, 11 | "no-switch-case-fall-through": true, 12 | "no-unused-expression": true, 13 | "no-use-before-declare": false, 14 | "no-var-keyword": true, 15 | "one-line": [ 16 | true, 17 | "check-open-brace", 18 | "check-whitespace", 19 | "check-catch" 20 | ], 21 | "quotemark": [ 22 | true, 23 | "double" 24 | ], 25 | "semicolon": true, 26 | "trailing-comma": [ 27 | true, 28 | { 29 | "multiline": "never", 30 | "singleline": "never" 31 | } 32 | ], 33 | "triple-equals": [ 34 | true, 35 | "allow-null-check" 36 | ], 37 | "variable-name": [ 38 | true, 39 | "ban-keywords" 40 | ] 41 | } 42 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: { 7 | 'sequency': './src/Sequence.ts', 8 | 'sequency.min': './src/Sequence.ts' 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, 'lib-umd'), 12 | filename: '[name].js', 13 | libraryTarget: 'umd', 14 | library: 'Sequency', 15 | umdNamedDefine: true 16 | }, 17 | resolve: { 18 | extensions: ['.ts'] 19 | }, 20 | devtool: 'source-map', 21 | module: { 22 | rules: [{ 23 | test: /\.ts$/, 24 | loader: 'ts-loader', 25 | exclude: /node_modules/ 26 | }] 27 | }, 28 | optimization: { 29 | minimize: true, 30 | minimizer: [new UglifyJsPlugin({ 31 | include: /\.min\.js$/ 32 | })] 33 | } 34 | }; 35 | --------------------------------------------------------------------------------