├── .gitignore ├── SECURITY.md ├── tsconfig.json ├── .editorconfig ├── .travis.yml ├── LICENSE ├── src ├── common.ts ├── index.spec.ts ├── index.ts ├── async.spec.ts └── async.ts ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | coverage/ 4 | node_modules/ 5 | npm-debug.log 6 | dist/ 7 | typings/ 8 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security contact information 4 | 5 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@borderless/ts-scripts/configs/tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "module": "CommonJS", 7 | "types": ["jest"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_size = 2 7 | indent_style = space 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | 4 | notifications: 5 | email: 6 | on_success: never 7 | on_failure: change 8 | 9 | node_js: 10 | - "10" 11 | - stable 12 | 13 | after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Blake Embrey (hello@blakeembrey.com) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Throw when iterator is `done`. 3 | */ 4 | export class StopIteration extends Error { 5 | constructor() { 6 | super("Iterator is already marked as done"); 7 | } 8 | } 9 | 10 | /** 11 | * Unique object for comparisons. 12 | */ 13 | export const SENTINEL = Symbol("SENTINEL"); 14 | 15 | /** 16 | * Identity function. Returns input as output. 17 | */ 18 | export const identity = (x: T): T => x; 19 | 20 | /** 21 | * Compare the two objects x and y and return an integer according to the 22 | * outcome. The return value is negative if `x < y`, positive if `x > y`, 23 | * otherwise zero. 24 | */ 25 | export function cmp(x: T, y: T) { 26 | return x > y ? 1 : x < y ? -1 : 0; 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iterative", 3 | "version": "1.9.2", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "Functions for working with iterators in JavaScript, with TypeScript", 8 | "license": "Apache-2.0", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/blakeembrey/iterative.git" 12 | }, 13 | "author": { 14 | "name": "Blake Embrey", 15 | "email": "hello@blakeembrey.com", 16 | "url": "http://blakeembrey.me" 17 | }, 18 | "homepage": "https://github.com/blakeembrey/iterative", 19 | "bugs": { 20 | "url": "https://github.com/blakeembrey/iterative/issues" 21 | }, 22 | "main": "dist/index.js", 23 | "scripts": { 24 | "format": "ts-scripts format", 25 | "lint": "ts-scripts lint", 26 | "prepare": "ts-scripts install && ts-scripts build", 27 | "specs": "ts-scripts specs", 28 | "test": "ts-scripts test" 29 | }, 30 | "files": [ 31 | "dist/" 32 | ], 33 | "keywords": [ 34 | "iter", 35 | "iterable", 36 | "itertools", 37 | "iteration", 38 | "iterator", 39 | "generator", 40 | "function", 41 | "util" 42 | ], 43 | "devDependencies": { 44 | "@borderless/ts-scripts": "^0.3.0", 45 | "@types/jest": "^24.0.11", 46 | "@types/node": "^13.1.2", 47 | "ts-expect": "^1.1.0", 48 | "typescript": "^4.2.4" 49 | }, 50 | "types": "dist/index.d.ts" 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iterative 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![NPM downloads][downloads-image]][downloads-url] 5 | [![Build status][travis-image]][travis-url] 6 | [![Test coverage][coveralls-image]][coveralls-url] 7 | 8 | > Functions for working with iterators in JavaScript, with TypeScript. 9 | 10 | _(Inspired by [itertools](https://docs.python.org/3/library/itertools.html#itertools-recipes))_ 11 | 12 | ## Installation 13 | 14 | ``` 15 | npm install iterative --save 16 | ``` 17 | 18 | ## Usage 19 | 20 | **🚨 The packages comes in two flavors, `Iterator` and `AsyncIterator`. Use `iterative/dist/async` for async iterators. 🚨** 21 | 22 | ### `range(start = 0, stop = Infinity, step = 1): Iterable` 23 | 24 | This is a versatile function to create lists containing arithmetic progressions. 25 | 26 | ```ts 27 | range(); //=> [0, 1, 2, 3, 4, 5, 6, 7, 8, ...] 28 | range(10, 20, 5); //=> [10, 15] 29 | ``` 30 | 31 | ### `cycle(iterable: Iterable): Iterable` 32 | 33 | Make an iterator returning elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely. 34 | 35 | ```ts 36 | cycle([1, 2, 3]); //=> [1, 2, 3, 1, 2, 3, 1, 2, ...] 37 | ``` 38 | 39 | ### `repeat(value: T, times?: number): Iterable` 40 | 41 | Make an iterator that repeats `value` over and over again. 42 | 43 | ```ts 44 | repeat(true); //=> [true, true, true, true, ...] 45 | ``` 46 | 47 | ### `flatten(iterable: Iterable>): Iterable` 48 | 49 | Return an iterator flattening one level of nesting in an iterable of iterables. 50 | 51 | ```ts 52 | flatten([ 53 | [1, 2, 3], 54 | [4, 5, 6], 55 | [7, 8, 9], 56 | ]); //=> [1, 2, 3, 4, 5, 6, 7, 8, 9] 57 | ``` 58 | 59 | ### `chain(...iterables: Array>): Iterable` 60 | 61 | Make an iterator that returns elements from the first iterable until it is exhausted, then proceeds to the next iterable, until all of the iterables are exhausted. Used for treating consecutive sequences as a single sequence. 62 | 63 | ```ts 64 | chain([1, 2, 3], [4, 5, 6], [7, 8, 9]); //=> [1, 2, 3, 4, 5, 6, 7, 8, 9] 65 | ``` 66 | 67 | ### `slice(iterable: Iterable, start = 0, stop = Infinity, step = 1): Iterable` 68 | 69 | Make an iterator that returns selected elements from the `iterable`. 70 | 71 | ```ts 72 | slice([1, 2, 3, 4, 5]); //=> [1, 2, 3, 4, 5] 73 | slice(range(), 2, 5); //=> [2, 3, 4] 74 | ``` 75 | 76 | ### `map(iterable: Iterable, func: (x: T) => U): Iterable` 77 | 78 | Apply function to every item of iterable and return an iterable of the results. 79 | 80 | ```ts 81 | map([1, 2, 3], (x) => x * x); //=> [1, 4, 9] 82 | ``` 83 | 84 | ### `spreadmap(iterable: Iterable, func: (...args: T) => U): Iterable` 85 | 86 | Make an iterator that computes the function using arguments obtained from the iterable. Used instead of `map()` when argument parameters are already grouped in tuples from a single iterable (the data has been "pre-zipped"). The difference between `map()` and `spreadmap()` parallels the distinction between `function(a, b)` and `function(...c)`. 87 | 88 | ```ts 89 | map( 90 | [ 91 | [1, 2], 92 | [3, 4], 93 | [5, 6], 94 | ], 95 | (a, b) => a + b 96 | ); //=> [3, 7, 11] 97 | ``` 98 | 99 | ### `filter(iterable: Iterable, func: Predicate = Boolean): Iterable` 100 | 101 | Construct an `iterator` from those elements of `iterable` for which `func` returns true. 102 | 103 | ```ts 104 | filter(range(0, 10), (x) => x % 2 === 0); //=> [0, 2, 4, 6, 8] 105 | ``` 106 | 107 | ### `reduce(iterable: Iterable, reducer: Reducer, initializer?: U): U` 108 | 109 | Apply function of two arguments cumulatively to the items of `iterable`, from left to right, so as to reduce the iterable to a single value. 110 | 111 | ```ts 112 | reduce([1, 2, 3], (sum, val) => sum + val); //=> 6 113 | ``` 114 | 115 | ### `accumulate(iterable: Iterable, func: Reducer): Iterable` 116 | 117 | Make an iterator that returns accumulated results of binary functions. 118 | 119 | ```ts 120 | accumulate([1, 2, 3], (sum, val) => sum + val); //=> [1, 3, 6] 121 | ``` 122 | 123 | ### `all(iterable: Iterable, predicate: Predicate = Boolean): boolean` 124 | 125 | Returns `true` when all values in iterable are truthy. 126 | 127 | ```ts 128 | all([1, 2, 3], (x) => x % 2 === 0); //=> false 129 | ``` 130 | 131 | ### `any(iterable: Iterable, predicate: Predicate = Boolean): boolean` 132 | 133 | Returns `true` when any value in iterable is truthy. 134 | 135 | ```ts 136 | any([1, 2, 3], (x) => x % 2 === 0); //=> true 137 | ``` 138 | 139 | ### `contains(iterable: Iterable, needle: T): boolean` 140 | 141 | Returns `true` when any value in iterable is equal to `needle`. 142 | 143 | ```ts 144 | contains("test", "t"); //=> true 145 | ``` 146 | 147 | ### `dropWhile(iterable: Iterable, predicate: Predicate): Iterable` 148 | 149 | Make an iterator that drops elements from the iterable as long as the predicate is true; afterwards, returns every element. 150 | 151 | ```ts 152 | dropWhile([1, 2, 3, 4, 5], (x) => x < 3); //=> [3, 4, 5] 153 | ``` 154 | 155 | ### `takeWhile(iterable: Iterable, predicate: Predicate): Iterable` 156 | 157 | Make an iterator that returns elements from the iterable as long as the predicate is true. 158 | 159 | ```ts 160 | takeWhile([1, 2, 3, 4, 5], (x) => x < 3); //=> [1, 2] 161 | ``` 162 | 163 | ### `groupBy(iterable: Iterable, func: (x: T) => U): Iterable<[U, Iterable]>` 164 | 165 | Make an iterator that returns consecutive keys and groups from the `iterable`. The `func` is a function computing a key value for each element. 166 | 167 | ```ts 168 | groupBy(range(0, 6), (x) => Math.floor(x / 2)); //=> [[0, [0, 1]], [1, [2, 3]], [2, [4, 5]]] 169 | ``` 170 | 171 | ### `enumerate(iterable: Iterable, offset = 0): Iterable<[number, T]>` 172 | 173 | Returns an iterable of enumeration pairs. 174 | 175 | ```ts 176 | enumerate("test"); //=> [[0, 't'], [1, 'e'], [2, 's'], [3, 't']] 177 | ``` 178 | 179 | ### `zip(...iterables: Iterable[]): Iterable` 180 | 181 | Returns an iterator of tuples, where the `i`-th tuple contains the `i`-th element from each of the argument sequences or iterables. The iterator stops when the shortest input iterable is exhausted. 182 | 183 | ```ts 184 | zip([1, 2, 3], ["a", "b", "c"]); //=> [[1, 'a'], [2, 'b'], [3, 'c']] 185 | ``` 186 | 187 | ### `zipLongest(...iterables: Iterable[]): Iterable<(T | undefined)[]>` 188 | 189 | Make an iterator that aggregates elements from each of the iterables. If the iterables are of uneven length, missing values are `undefined`. Iteration continues until the longest iterable is exhausted. 190 | 191 | ```ts 192 | zipLongest([1, 2], ["a", "b", "c", "d"]); //=> [[1, 'a'], [2, 'b'], [undefined, 'c'], [undefined, 'd']] 193 | ``` 194 | 195 | ### `zipWithValue(fillValue: U, iterables: Iterable[]): Iterable<(T | U)[]>` 196 | 197 | Make an iterator that aggregates elements from each of the iterables. If the iterables are of uneven length, missing values are `fillValue`. Iteration continues until the longest iterable is exhausted. 198 | 199 | ```ts 200 | zipWithValue("example", [1, 2], ["a", "b", "c", "d"]); //=> [[1, 'a'], [2, 'b'], ['example', 'c'], ['example', 'd']] 201 | ``` 202 | 203 | ### `tee(iterable: Iterable): [Iterable, Iterable]` 204 | 205 | Return two independent iterables from a single iterable. 206 | 207 | ```ts 208 | tee([1, 2, 3]); //=> [[1, 2, 3], [1, 2, 3]] 209 | ``` 210 | 211 | ### `chunk(iterable: Iterable, size: number): Iterable` 212 | 213 | Break iterable into lists of length `size`. 214 | 215 | ```ts 216 | chunk(range(0, 10), 2); //=> [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] 217 | ``` 218 | 219 | ### `pairwise(iterable: Iterable): Iterable<[T, T]>` 220 | 221 | Returns an iterator of paired items, overlapping, from the original. When the input iterable has a finite number of items `n`, the outputted iterable will have `n - 1` items. 222 | 223 | ```ts 224 | pairwise(range(0, 5)); //=> [[0, 1], [1, 2], [2, 3], [3, 4]] 225 | ``` 226 | 227 | ### `compress(iterable: Iterable, selectors: Iterable): Iterable` 228 | 229 | Make an iterator that filters elements from `iterable` returning only those that have a corresponding element in selectors that evaluates to `true`. 230 | 231 | ```ts 232 | compress([1, 2, 3, 4, 5], [true, false, true, false, true]); //=> [1, 3, 5] 233 | ``` 234 | 235 | ### `sorted(iterable: Iterable, key: (x: T) => U, cmp: (x: U, y: U) => number, reverse?: boolean): T[]` 236 | 237 | Return a sorted array from the items in iterable. 238 | 239 | ```ts 240 | sorted(slice(range(), 0, 10), (x) => x); 241 | ``` 242 | 243 | ### `list(iterable: Iterable): T[]` 244 | 245 | Creates an array from an iterable object. 246 | 247 | ```ts 248 | list(range(0, 5)); //=> [0, 1, 2, 3, 4] 249 | ``` 250 | 251 | ### `dict(iterable: Iterable<[K, V]>): Record` 252 | 253 | Return an object from an iterable, i.e. `Array.from` for objects. 254 | 255 | ```ts 256 | dict(zip(range(0, 5), repeat(true))); //=> { 0: true, 1: true, 2: true, 3: true, 4: true } 257 | ``` 258 | 259 | ### `len(iterable: Iterable): number` 260 | 261 | Return the length (the number of items) of an iterable. 262 | 263 | ```ts 264 | len(range(0, 5)); //=> 5 265 | ``` 266 | 267 | _Note:_ This method iterates over `iterable` to return the length. 268 | 269 | ### `min(iterable: Iterable, key?: (x: T) => number): T` 270 | 271 | Return the smallest item in an iterable. 272 | 273 | ```ts 274 | min([1, 2, 3, 4, 5]); //=> 1 275 | ``` 276 | 277 | ### `max(iterable: Iterable, key?: (x: T) => number): T` 278 | 279 | Return the largest item in an iterable. 280 | 281 | ```ts 282 | max([1, 2, 3, 4, 5]); //=> 5 283 | ``` 284 | 285 | ### `sum(iterable: Iterable, start?: number): number` 286 | 287 | Sums `start` and the items of an `iterable` from left to right and returns the total. 288 | 289 | ```ts 290 | sum([1, 2, 3, 4, 5]); //=> 15 291 | ``` 292 | 293 | ### `product(...iterables: Iterable[]): Iterable` 294 | 295 | Cartesian product of input iterables. 296 | 297 | ```ts 298 | product("ABCD", "xy"); //=> Ax Ay Bx By Cx Cy Dx Dy 299 | ``` 300 | 301 | ## Reference 302 | 303 | - [Itertools Recipes](https://docs.python.org/3/library/itertools.html#itertools-recipes) 304 | 305 | ## TypeScript 306 | 307 | This project uses [TypeScript](https://github.com/Microsoft/TypeScript) and publishes definitions on NPM. 308 | 309 | ## License 310 | 311 | Apache 2.0 312 | 313 | [npm-image]: https://img.shields.io/npm/v/iterative.svg?style=flat 314 | [npm-url]: https://npmjs.org/package/iterative 315 | [downloads-image]: https://img.shields.io/npm/dm/iterative.svg?style=flat 316 | [downloads-url]: https://npmjs.org/package/iterative 317 | [travis-image]: https://img.shields.io/travis/blakeembrey/iterative.svg?style=flat 318 | [travis-url]: https://travis-ci.org/blakeembrey/iterative 319 | [coveralls-image]: https://img.shields.io/coveralls/blakeembrey/iterative.svg?style=flat 320 | [coveralls-url]: https://coveralls.io/r/blakeembrey/iterative?branch=master 321 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as iter from "./index"; 2 | import { expectType, TypeEqual } from "ts-expect"; 3 | 4 | describe("iterative", () => { 5 | describe("all", () => { 6 | it("should return true when all match", () => { 7 | const result = iter.all([1, 2, 3], (x) => true); 8 | 9 | expect(result).toBe(true); 10 | }); 11 | 12 | it("should return false when a value does not match", () => { 13 | const result = iter.all([1, 2, 3], (x) => x % 2 === 1); 14 | 15 | expect(result).toBe(false); 16 | }); 17 | }); 18 | 19 | describe("any", () => { 20 | it("should return true when any match", () => { 21 | const result = iter.any([1, 2, 3], (x) => x === 3); 22 | 23 | expect(result).toBe(true); 24 | }); 25 | 26 | it("should return false when none match", () => { 27 | const result = iter.any([1, 2, 3], (x) => x === 5); 28 | 29 | expect(result).toBe(false); 30 | }); 31 | }); 32 | 33 | describe("contains", () => { 34 | it("should find value in iterator", () => { 35 | const result = iter.contains("test", "s"); 36 | 37 | expect(result).toBe(true); 38 | }); 39 | 40 | it("should return false when not found", () => { 41 | const result = iter.contains("test", "a"); 42 | 43 | expect(result).toBe(false); 44 | }); 45 | }); 46 | 47 | describe("accumulate", () => { 48 | it("should accumulate values from iterator", () => { 49 | const iterable = iter.accumulate([1, 2, 3], (x, y) => x + y); 50 | 51 | expect(iter.list(iterable)).toEqual([1, 3, 6]); 52 | }); 53 | }); 54 | 55 | describe("range", () => { 56 | it("should generate range from 0 until stop", () => { 57 | const iterable = iter.range(0, 5); 58 | 59 | expect(iter.list(iterable)).toEqual([0, 1, 2, 3, 4]); 60 | }); 61 | 62 | it("should generate range from start to stop", () => { 63 | const iterable = iter.range(5, 10); 64 | 65 | expect(iter.list(iterable)).toEqual([5, 6, 7, 8, 9]); 66 | }); 67 | 68 | it("should generate range from start to stop with step", () => { 69 | const iterable = iter.range(0, 30, 5); 70 | 71 | expect(iter.list(iterable)).toEqual([0, 5, 10, 15, 20, 25]); 72 | }); 73 | }); 74 | 75 | describe("flatten", () => { 76 | it("should flatten an iterable of iterables", () => { 77 | const iterable = iter.slice( 78 | iter.flatten(iter.map(iter.range(), (stop) => iter.range(0, stop))), 79 | 10, 80 | 20 81 | ); 82 | 83 | expect(iter.list(iterable)).toEqual([0, 1, 2, 3, 4, 0, 1, 2, 3, 4]); 84 | }); 85 | }); 86 | 87 | describe("chain", () => { 88 | it("should chain together iterables", () => { 89 | const iterable = iter.chain(iter.range(0, 5), iter.range(0, 5)); 90 | 91 | expect(iter.list(iterable)).toEqual([0, 1, 2, 3, 4, 0, 1, 2, 3, 4]); 92 | }); 93 | 94 | it("should allow flat map", () => { 95 | const iterable = iter.chain( 96 | ...iter.map(iter.range(0, 5), (stop) => iter.range(0, stop)) 97 | ); 98 | 99 | expect(iter.list(iterable)).toEqual([0, 0, 1, 0, 1, 2, 0, 1, 2, 3]); 100 | }); 101 | }); 102 | 103 | describe("slice", () => { 104 | it("should slice an iterable", () => { 105 | const iterable = iter.slice(iter.range(0, 1000), 0, 10); 106 | 107 | expect(iter.list(iterable)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 108 | }); 109 | 110 | it("should slice from non-zero offset", () => { 111 | const iterable = iter.slice(iter.range(), 2, 4); 112 | 113 | expect(iter.list(iterable)).toEqual([2, 3]); 114 | }); 115 | }); 116 | 117 | describe("dropWhile", () => { 118 | it("should drop values until predicate becomes falsy", () => { 119 | const iterable = iter.slice( 120 | iter.dropWhile(iter.range(), (x) => x < 10), 121 | 0, 122 | 3 123 | ); 124 | 125 | expect(iter.list(iterable)).toEqual([10, 11, 12]); 126 | }); 127 | 128 | it("should drop nothing if immediately returns false", () => { 129 | const iterable = iter.slice( 130 | iter.dropWhile(iter.range(), (x) => false), 131 | 0, 132 | 3 133 | ); 134 | 135 | expect(iter.list(iterable)).toEqual([0, 1, 2]); 136 | }); 137 | }); 138 | 139 | describe("takeWhile", () => { 140 | it("take while predicate is truthy", () => { 141 | const iterable = iter.takeWhile(iter.range(), (x) => x < 5); 142 | 143 | expect(iter.list(iterable)).toEqual([0, 1, 2, 3, 4]); 144 | }); 145 | }); 146 | 147 | describe("repeat", () => { 148 | it("should repeat a value indefinitely", () => { 149 | const iterable = iter.slice(iter.repeat(10), 0, 5); 150 | 151 | expect(iter.list(iterable)).toEqual([10, 10, 10, 10, 10]); 152 | }); 153 | 154 | it("should repeat a value up to times", () => { 155 | const iterable = iter.repeat(10, 5); 156 | 157 | expect(iter.list(iterable)).toEqual([10, 10, 10, 10, 10]); 158 | }); 159 | }); 160 | 161 | describe("cycle", () => { 162 | it("should cycle over an iterator producing an infinite iterator", () => { 163 | const iterable = iter.slice(iter.cycle("abc"), 0, 5); 164 | 165 | expect(iter.list(iterable)).toEqual(["a", "b", "c", "a", "b"]); 166 | }); 167 | }); 168 | 169 | describe("groupBy", () => { 170 | it("should group by sequentially", () => { 171 | const iterable = iter.groupBy([1, 2, 3, 4, 5], (x) => Math.floor(x / 2)); 172 | const result = iter.list(iterable, ([index, iterable]) => [ 173 | index, 174 | iter.list(iterable), 175 | ]); 176 | 177 | expect(result).toEqual([ 178 | [0, [1]], 179 | [1, [2, 3]], 180 | [2, [4, 5]], 181 | ]); 182 | }); 183 | 184 | it("should skip over groups when not consumed", () => { 185 | const iterable = iter.groupBy([1, 2, 3, 4, 5], (x) => Math.floor(x / 2)); 186 | const result = iter.list(iterable, ([index]) => index); 187 | 188 | expect(result).toEqual([0, 1, 2]); 189 | }); 190 | 191 | it("should consume partial groups", () => { 192 | const iterable = iter.groupBy([1, 2, 3, 4, 5], (x) => Math.floor(x / 2)); 193 | const result = iter.list(iterable, ([index, iterable]) => { 194 | return [index, iter.next(iterable)]; 195 | }); 196 | 197 | expect(result).toEqual([ 198 | [0, 1], 199 | [1, 2], 200 | [2, 4], 201 | ]); 202 | }); 203 | }); 204 | 205 | describe("slice", () => { 206 | it("should slice an iterable", () => { 207 | const iterable = iter.slice([1, 2, 3, 4, 5], 0, 2); 208 | 209 | expect(iter.list(iterable)).toEqual([1, 2]); 210 | }); 211 | 212 | it("should exhaust an iterable when range is too large", () => { 213 | const iterable = iter.slice([1, 2, 3, 4, 5], 2, 10); 214 | 215 | expect(iter.list(iterable)).toEqual([3, 4, 5]); 216 | }); 217 | 218 | it("should specify a custom step", () => { 219 | const iterable = iter.slice([1, 2, 3, 4, 5], 0, Infinity, 3); 220 | 221 | expect(iter.list(iterable)).toEqual([1, 4]); 222 | }); 223 | }); 224 | 225 | describe("reduce", () => { 226 | it("should reduce an iterator to a single value", () => { 227 | const result = iter.reduce(iter.range(0, 5), (x, y) => x + y); 228 | 229 | expect(result).toEqual(10); 230 | }); 231 | }); 232 | 233 | describe("map", () => { 234 | it("should map iterator values", () => { 235 | const iterable = iter.slice( 236 | iter.map(iter.range(), (x) => x * x), 237 | 0, 238 | 5 239 | ); 240 | 241 | expect(iter.list(iterable)).toEqual([0, 1, 4, 9, 16]); 242 | }); 243 | }); 244 | 245 | describe("spreadmap", () => { 246 | it("should spread map iterator values", () => { 247 | const iterable = iter.slice( 248 | iter.spreadmap(iter.zip(iter.range(), iter.range()), (a, b) => a + b), 249 | 0, 250 | 5 251 | ); 252 | 253 | expect(iter.list(iterable)).toEqual([0, 2, 4, 6, 8]); 254 | }); 255 | }); 256 | 257 | describe("filter", () => { 258 | it("should filter values from iterator", () => { 259 | const iterable = iter.slice( 260 | iter.filter(iter.range(), (x) => x % 2 === 0), 261 | 0, 262 | 5 263 | ); 264 | 265 | expect(iter.list(iterable)).toEqual([0, 2, 4, 6, 8]); 266 | }); 267 | 268 | it("should filter with correct output type", () => { 269 | const iterable = iter.filter( 270 | ["a", 1, "b", 2, "c", 3], 271 | (x): x is string => typeof x === "string" 272 | ); 273 | 274 | expect(iter.list(iterable)).toEqual(["a", "b", "c"]); 275 | }); 276 | }); 277 | 278 | describe("tee", () => { 279 | it("should return two independent iterables from one", () => { 280 | const iterable = iter.map([1, 2, 3], (x) => x * 2); 281 | const [a, b] = iter.tee(iterable); 282 | 283 | expect(iter.list(a)).toEqual([2, 4, 6]); 284 | expect(iter.list(b)).toEqual([2, 4, 6]); 285 | }); 286 | 287 | it("should read varying from cache to iterable", () => { 288 | const iterable = iter.range(0, 5); 289 | const [a, b] = iter.tee(iterable).map(iter.iter); 290 | 291 | expect([a.next().value, a.next().value]).toEqual([0, 1]); 292 | expect([b.next().value, b.next().value, b.next().value]).toEqual([ 293 | 0, 294 | 1, 295 | 2, 296 | ]); 297 | 298 | expect([a.next().value, a.next().value]).toEqual([2, 3]); 299 | expect([b.next().value, b.next().value]).toEqual([3, 4]); 300 | 301 | expect(a.next().value).toEqual(4); 302 | expect(b.next().value).toEqual(undefined); 303 | }); 304 | 305 | it("should tee empty iterable", () => { 306 | const iterable = iter.range(0, 0); 307 | const [a, b] = iter.tee(iterable); 308 | 309 | expect(iter.list(a)).toEqual([]); 310 | expect(iter.list(b)).toEqual([]); 311 | }); 312 | 313 | it("should call `next` the right number of times", () => { 314 | let i = 0; 315 | const next = jest.fn(() => ({ value: i++, done: i > 10 })); 316 | const iterable: Iterable = { 317 | [Symbol.iterator]: () => ({ next }), 318 | }; 319 | 320 | const [a, b] = iter.tee(iterable); 321 | 322 | // Exhaust both iterables. 323 | expect(iter.list(a)).toEqual(iter.list(iter.range(0, 10))); 324 | expect(iter.list(b)).toEqual(iter.list(iter.range(0, 10))); 325 | 326 | expect(next.mock.calls.length).toEqual(11); 327 | }); 328 | }); 329 | 330 | describe("chunk", () => { 331 | it("should chunk an iterable", () => { 332 | const iterable = iter.chunk([1, 2, 3, 4, 5, 6], 2); 333 | 334 | expect(iter.list(iterable)).toEqual([ 335 | [1, 2], 336 | [3, 4], 337 | [5, 6], 338 | ]); 339 | }); 340 | 341 | it("should yield last chunk when less than chunk size", () => { 342 | const iterable = iter.chunk([1, 2, 3, 4, 5], 3); 343 | 344 | expect(iter.list(iterable)).toEqual([ 345 | [1, 2, 3], 346 | [4, 5], 347 | ]); 348 | }); 349 | }); 350 | 351 | describe("pairwise", () => { 352 | it("should generate pairwise iterator", () => { 353 | const iterable = iter.pairwise([1, 2, 3, 4]); 354 | 355 | expect(iter.list(iterable)).toEqual([ 356 | [1, 2], 357 | [2, 3], 358 | [3, 4], 359 | ]); 360 | }); 361 | 362 | it("should not generate any values when iterator too small", () => { 363 | const iterable = iter.pairwise([1]); 364 | 365 | expect(iter.list(iterable)).toEqual([]); 366 | }); 367 | }); 368 | 369 | describe("zip", () => { 370 | it("should zip two iterables", () => { 371 | const iterable = iter.zip([1, 2, 3], ["a", "b", "c"]); 372 | 373 | expect(iter.list(iterable)).toEqual([ 374 | [1, "a"], 375 | [2, "b"], 376 | [3, "c"], 377 | ]); 378 | }); 379 | 380 | it("should stop when an iterable is done", () => { 381 | const iterable = iter.zip([1, 2, 3], [1, 2, 3, 4, 5]); 382 | 383 | expect(iter.list(iterable)).toEqual([ 384 | [1, 1], 385 | [2, 2], 386 | [3, 3], 387 | ]); 388 | }); 389 | 390 | it("should do nothing without iterables", () => { 391 | const iterable = iter.zip(); 392 | 393 | expect(iter.list(iterable)).toEqual([]); 394 | }); 395 | }); 396 | 397 | describe("zipLongest", () => { 398 | it("should zip until the longest value", () => { 399 | const iterable = iter.zipLongest(iter.range(0, 2), iter.range(0, 5)); 400 | 401 | expectType< 402 | TypeEqual< 403 | IterableIterator<[number | undefined, number | undefined]>, 404 | typeof iterable 405 | > 406 | >(true); 407 | 408 | expect(iter.list(iterable)).toEqual([ 409 | [0, 0], 410 | [1, 1], 411 | [undefined, 2], 412 | [undefined, 3], 413 | [undefined, 4], 414 | ]); 415 | }); 416 | 417 | it("should do nothing without iterables", () => { 418 | const iterable = iter.zipLongest(); 419 | 420 | expect(iter.list(iterable)).toEqual([]); 421 | }); 422 | }); 423 | 424 | describe("zipWithValue", () => { 425 | it("should zip until the longest value", () => { 426 | const iterable = iter.zipWithValue( 427 | "test", 428 | iter.range(0, 2), 429 | iter.range(0, 5) 430 | ); 431 | 432 | expectType< 433 | TypeEqual< 434 | IterableIterator<[number | string, number | string]>, 435 | typeof iterable 436 | > 437 | >(true); 438 | 439 | expect(iter.list(iterable)).toEqual([ 440 | [0, 0], 441 | [1, 1], 442 | ["test", 2], 443 | ["test", 3], 444 | ["test", 4], 445 | ]); 446 | }); 447 | }); 448 | 449 | describe("compress", () => { 450 | it("should compress an iterable based on boolean sequence", () => { 451 | const iterable = iter.compress(iter.range(), [true, false, true]); 452 | 453 | expect(iter.list(iterable)).toEqual([0, 2]); 454 | }); 455 | }); 456 | 457 | describe("sorted", () => { 458 | it("should return a sorted list", () => { 459 | const list = iter.sorted(iter.slice(iter.cycle([1, 2, 3]), 0, 10)); 460 | 461 | expect(list).toEqual([1, 1, 1, 1, 2, 2, 2, 3, 3, 3]); 462 | }); 463 | 464 | it("should return list in reverse order", () => { 465 | const list = iter.sorted( 466 | iter.slice(iter.range(), 0, 10), 467 | undefined, 468 | undefined, 469 | true 470 | ); 471 | 472 | expect(list).toEqual([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); 473 | }); 474 | 475 | it("should allow key function", () => { 476 | const list = iter.sorted([{ x: 3 }, { x: 2 }, { x: 1 }], (x) => x.x); 477 | 478 | expect(list).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); 479 | }); 480 | 481 | it("should allow compare function", () => { 482 | const list = iter.sorted([1, 2, 3, 4, 5], undefined, (x, y) => y - x); 483 | 484 | expect(list).toEqual([5, 4, 3, 2, 1]); 485 | }); 486 | 487 | it("should combine key and compare functions", () => { 488 | const list = iter.sorted( 489 | [{ x: 2 }, { x: 1 }, { x: 3 }], 490 | (x) => x.x, 491 | (x, y) => y - x 492 | ); 493 | 494 | expect(list).toEqual([{ x: 3 }, { x: 2 }, { x: 1 }]); 495 | }); 496 | }); 497 | 498 | describe("dict", () => { 499 | it("should create an object from an iterable", () => { 500 | const iterable = iter.zip(iter.range(1, 4), iter.repeat(true)); 501 | 502 | expect(iter.dict(iterable)).toEqual({ 1: true, 2: true, 3: true }); 503 | }); 504 | }); 505 | 506 | describe("len", () => { 507 | it("should count the length of an iterable", () => { 508 | const iterable = iter.range(0, 5); 509 | 510 | expect(iter.len(iterable)).toEqual(5); 511 | }); 512 | }); 513 | 514 | describe("min", () => { 515 | it("should find the minimum value", () => { 516 | expect(iter.min([1, 5, -2])).toEqual(-2); 517 | }); 518 | 519 | it("should find minimum value by key", () => { 520 | const iterable = iter.zip(iter.repeat(true), iter.range(0, 100)); 521 | 522 | expect(iter.min(iterable, (x) => x[1])).toEqual([true, 0]); 523 | }); 524 | }); 525 | 526 | describe("max", () => { 527 | it("should find the maximum value", () => { 528 | expect(iter.max([1, 5, -2])).toEqual(5); 529 | }); 530 | 531 | it("should find maximum value by key", () => { 532 | const iterable = iter.zip(iter.repeat(true), iter.range(0, 100)); 533 | 534 | expect(iter.max(iterable, (x) => x[1])).toEqual([true, 99]); 535 | }); 536 | }); 537 | 538 | describe("sum", () => { 539 | it("should sum an iterable", () => { 540 | expect(iter.sum(iter.range(0, 10))).toEqual(45); 541 | }); 542 | 543 | it("should sum an iterable with custom start", () => { 544 | expect(iter.sum(iter.range(0, 10), 5)).toEqual(50); 545 | }); 546 | }); 547 | 548 | describe("product", () => { 549 | it("should generate the product of multiple iterators", () => { 550 | const iterable = iter.product("ABCD", "xy"); 551 | 552 | const result = [ 553 | ["A", "x"], 554 | ["A", "y"], 555 | ["B", "x"], 556 | ["B", "y"], 557 | ["C", "x"], 558 | ["C", "y"], 559 | ["D", "x"], 560 | ["D", "y"], 561 | ]; 562 | 563 | expect(iter.list(iterable)).toEqual(result); 564 | }); 565 | }); 566 | 567 | describe("iterable", () => { 568 | it("should convert an iterator to iterable", () => { 569 | const iterator = iter.iter([1, 2, 3]); 570 | const iterable = iter.iterable(iterator); 571 | 572 | iter.next(iterator); // Discard first value. 573 | 574 | expect(iter.list(iterable)).toEqual([2, 3]); 575 | }); 576 | }); 577 | }); 578 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { StopIteration, SENTINEL, identity, cmp } from "./common"; 2 | 3 | /** 4 | * Predicate for filtering items. 5 | */ 6 | export type Predicate = 7 | | ((item: T) => item is U) 8 | | ((item: T) => boolean); 9 | 10 | /** 11 | * Reducer function. 12 | */ 13 | export type Reducer = (result: U, item: T) => U; 14 | 15 | /** 16 | * List of values to list of iterable values. 17 | */ 18 | export type TupleIterable = { [K in keyof T]: Iterable }; 19 | 20 | /** 21 | * Unary function mapping an input value to an output value. 22 | */ 23 | export type Func = (item: T) => U; 24 | 25 | /** 26 | * Returns `true` when all values in iterable are truthy. 27 | */ 28 | export function all( 29 | iterable: Iterable, 30 | predicate: Predicate = Boolean 31 | ) { 32 | for (const item of iterable) { 33 | if (!predicate(item)) return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | /** 40 | * Returns `true` when any value in iterable is truthy. 41 | */ 42 | export function any( 43 | iterable: Iterable, 44 | predicate: Predicate = Boolean 45 | ) { 46 | for (const item of iterable) { 47 | if (predicate(item)) return true; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Returns `true` when any value in iterable is equal to `needle`. 55 | */ 56 | export function contains(iterable: Iterable, needle: T) { 57 | return any(iterable, (x) => x === needle); 58 | } 59 | 60 | /** 61 | * Returns an iterable of enumeration pairs. 62 | */ 63 | export function enumerate(iterable: Iterable, offset = 0) { 64 | return zip(range(offset), iterable); 65 | } 66 | 67 | /** 68 | * Get next iterator value, throw when `done`. 69 | */ 70 | export function next(iterator: Iterator): T; 71 | export function next(iterator: Iterator, defaultValue: U): T | U; 72 | export function next(iterator: Iterator, defaultValue?: U): T | U { 73 | const item = iterator.next(); 74 | if (item.done) { 75 | if (arguments.length === 1) throw new StopIteration(); 76 | return defaultValue as U; 77 | } 78 | return item.value; 79 | } 80 | 81 | /** 82 | * Returns an iterator object for the given `iterable`. 83 | */ 84 | export function iter(iterable: Iterable): Iterator { 85 | return iterable[Symbol.iterator](); 86 | } 87 | 88 | /** 89 | * Convert an iterator object back into an iterator. 90 | */ 91 | export function* iterable(iterator: Iterator): Iterable { 92 | while (true) { 93 | const item = iterator.next(); 94 | if (item.done) return; 95 | yield item.value; 96 | } 97 | } 98 | 99 | /** 100 | * Make an iterator that returns accumulated results of binary functions. 101 | */ 102 | export function* accumulate( 103 | iterable: Iterable, 104 | func: Reducer 105 | ): IterableIterator { 106 | const it = iter(iterable); 107 | let item = it.next(); 108 | let total = item.value; 109 | 110 | if (item.done) return; 111 | yield total; 112 | 113 | while ((item = it.next())) { 114 | if (item.done) break; 115 | total = func(total, item.value); 116 | yield total; 117 | } 118 | } 119 | 120 | /** 121 | * Return an iterator flattening one level of nesting in an iterable of iterables. 122 | */ 123 | export function* flatten( 124 | iterable: Iterable> 125 | ): IterableIterator { 126 | for (const it of iterable) { 127 | for (const item of it) { 128 | yield item; 129 | } 130 | } 131 | } 132 | 133 | /** 134 | * Make an iterator that returns elements from the first iterable until it is 135 | * exhausted, then proceeds to the next iterable, until all of the iterables are 136 | * exhausted. Used for treating consecutive sequences as a single sequence. 137 | */ 138 | export function chain( 139 | ...iterables: Array> 140 | ): IterableIterator { 141 | return flatten(iterables); 142 | } 143 | 144 | /** 145 | * This is a versatile function to create lists containing arithmetic progressions. 146 | */ 147 | export function* range( 148 | start = 0, 149 | stop = Infinity, 150 | step = 1 151 | ): IterableIterator { 152 | for (let i = start; i < stop; i += step) yield i; 153 | } 154 | 155 | /** 156 | * Make an iterator returning elements from the iterable and saving a copy of 157 | * each. When the iterable is exhausted, return elements from the saved copy. 158 | * Repeats indefinitely. 159 | */ 160 | export function* cycle(iterable: Iterable): IterableIterator { 161 | const saved: T[] = []; 162 | 163 | for (const item of iterable) { 164 | yield item; 165 | saved.push(item); 166 | } 167 | 168 | while (saved.length) { 169 | for (const item of saved) { 170 | yield item; 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * Make an iterator that repeats `value` over and over again. 177 | */ 178 | export function* repeat(value: T, times?: number): IterableIterator { 179 | if (times === undefined) while (true) yield value; 180 | for (let i = 0; i < times; i++) yield value; 181 | } 182 | 183 | /** 184 | * Make an iterator that drops elements from the iterable as long as the 185 | * predicate is true; afterwards, returns every element. 186 | */ 187 | export function* dropWhile(iterable: Iterable, predicate: Predicate) { 188 | const it = iter(iterable); 189 | let item = it.next(); 190 | 191 | while (!item.done) { 192 | if (!predicate(item.value)) break; 193 | 194 | item = it.next(); 195 | } 196 | 197 | do { 198 | yield item.value; 199 | item = it.next(); 200 | } while (!item.done); 201 | } 202 | 203 | /** 204 | * Make an iterator that returns elements from the iterable as long as the 205 | * predicate is true. 206 | */ 207 | export function* takeWhile(iterable: Iterable, predicate: Predicate) { 208 | for (const item of iterable) { 209 | if (!predicate(item)) break; 210 | 211 | yield item; 212 | } 213 | } 214 | 215 | /** 216 | * Make an iterator that returns consecutive keys and groups from the `iterable`. 217 | * The `func` is a function computing a key value for each element. 218 | */ 219 | export function* groupBy(iterable: Iterable, func: Func) { 220 | const it = iter(iterable); 221 | let item = it.next(); 222 | 223 | if (item.done) return; 224 | 225 | let key = func(item.value); 226 | let currKey: U | typeof SENTINEL = key; 227 | 228 | function* grouper() { 229 | do { 230 | yield item.value; 231 | 232 | item = it.next(); 233 | 234 | // Break iteration when underlying iterator is `done`. 235 | if (item.done) { 236 | currKey = SENTINEL; 237 | return; 238 | } 239 | 240 | currKey = func(item.value); 241 | } while (key === currKey); 242 | } 243 | 244 | do { 245 | yield [key, grouper()] as [U, IterableIterator]; 246 | 247 | // Skip over any remaining values not pulled from `grouper`. 248 | while (key === currKey) { 249 | item = it.next(); 250 | if (item.done) return; 251 | currKey = func(item.value); 252 | } 253 | 254 | key = currKey; 255 | } while (!item.done); 256 | } 257 | 258 | /** 259 | * Make an iterator that returns selected elements from the `iterable`. 260 | */ 261 | export function* slice( 262 | iterable: Iterable, 263 | start = 0, 264 | stop = Infinity, 265 | step = 1 266 | ) { 267 | const it = iter(range(start, stop, step)); 268 | let next = it.next(); 269 | 270 | for (const [index, item] of enumerate(iterable)) { 271 | if (next.done) return; 272 | 273 | if (index === next.value) { 274 | yield item; 275 | next = it.next(); 276 | } 277 | } 278 | } 279 | 280 | /** 281 | * Apply function of two arguments cumulatively to the items of `iterable`, from 282 | * left to right, so as to reduce the iterable to a single value. 283 | */ 284 | export function reduce(iterable: Iterable, reducer: Reducer): T; 285 | export function reduce( 286 | iterable: Iterable, 287 | reducer: Reducer, 288 | initializer: U 289 | ): U; 290 | export function reduce( 291 | iterable: Iterable, 292 | reducer: Reducer, 293 | initializer?: U 294 | ): T | U { 295 | const it = iter(iterable); 296 | let item: IteratorResult; 297 | let accumulator: T | U = initializer === undefined ? next(it) : initializer; 298 | 299 | while ((item = it.next())) { 300 | if (item.done) break; 301 | accumulator = reducer(accumulator, item.value); 302 | } 303 | 304 | return accumulator; 305 | } 306 | 307 | /** 308 | * Apply function to every item of iterable and return an iterable of the results. 309 | */ 310 | export function* map( 311 | iterable: Iterable, 312 | func: Func 313 | ): IterableIterator { 314 | for (const item of iterable) yield func(item); 315 | } 316 | 317 | /** 318 | * Make an iterator that computes the function using arguments obtained from the 319 | * iterable. Used instead of `map()` when argument parameters are already 320 | * grouped in tuples from a single iterable (the data has been "pre-zipped"). 321 | * The difference between `map()` and `spreadmap()` parallels the distinction 322 | * between `function(a, b)` and `function(...c)`. 323 | */ 324 | export function* spreadmap( 325 | iterable: Iterable, 326 | func: (...args: T) => U 327 | ): IterableIterator { 328 | for (const item of iterable) yield func(...item); 329 | } 330 | 331 | /** 332 | * Construct an `iterator` from those elements of `iterable` for which `func` returns true. 333 | */ 334 | export function* filter( 335 | iterable: Iterable, 336 | func: Predicate = Boolean 337 | ): IterableIterator { 338 | for (const item of iterable) { 339 | if (func(item)) yield item; 340 | } 341 | } 342 | 343 | /** 344 | * Make an iterator that aggregates elements from each of the iterables. Returns 345 | * an iterator of tuples, where the `i`-th tuple contains the `i`-th element 346 | * from each of the argument sequences or iterables. The iterator stops when the 347 | * shortest input iterable is exhausted. 348 | */ 349 | export function* zip( 350 | ...iterables: TupleIterable 351 | ): IterableIterator { 352 | const iters = iterables.map((x) => iter(x)); 353 | 354 | while (iters.length) { 355 | const result = Array(iters.length) as T; 356 | 357 | for (let i = 0; i < iters.length; i++) { 358 | const item = iters[i].next(); 359 | if (item.done) return; 360 | result[i] = item.value; 361 | } 362 | 363 | yield result; 364 | } 365 | } 366 | 367 | /** 368 | * Make an iterator that aggregates elements from each of the iterables. If the 369 | * iterables are of uneven length, missing values are `undefined`. Iteration 370 | * continues until the longest iterable is exhausted. 371 | */ 372 | export function zipLongest(...iterables: TupleIterable) { 373 | return zipWithValue(undefined, ...(iterables as any)); 374 | } 375 | 376 | /** 377 | * Make an iterator that aggregates elements from each of the iterables. If the 378 | * iterables are of uneven length, missing values are `fillValue`. Iteration 379 | * continues until the longest iterable is exhausted. 380 | */ 381 | export function* zipWithValue( 382 | fillValue: U, 383 | ...iterables: TupleIterable 384 | ): IterableIterator<{ [K in keyof T]: T[K] | U }> { 385 | const iters = iterables.map>((x) => iter(x)); 386 | const noop = iter(repeat(fillValue)); 387 | let counter = iters.length; 388 | 389 | while (true) { 390 | const result = Array(iters.length) as { [K in keyof T]: T[K] | U }; 391 | 392 | for (let i = 0; i < iters.length; i++) { 393 | const item = iters[i].next(); 394 | 395 | if (item.done) { 396 | counter -= 1; 397 | iters[i] = noop; 398 | result[i] = fillValue; 399 | } else { 400 | result[i] = item.value; 401 | } 402 | } 403 | 404 | if (counter === 0) break; 405 | 406 | yield result; 407 | } 408 | } 409 | 410 | /** 411 | * Return two independent iterables from a single iterable. 412 | */ 413 | export function tee(iterable: Iterable) { 414 | const queue: T[] = []; 415 | const it = iter(iterable); 416 | let owner: -1 | 0 | 1; 417 | 418 | function* gen(id: 0 | 1): IterableIterator { 419 | while (true) { 420 | while (queue.length) { 421 | yield queue.shift()!; 422 | } 423 | 424 | if (owner === -1) return; 425 | 426 | let item: IteratorResult; 427 | 428 | while ((item = it.next())) { 429 | if (item.done) { 430 | owner = -1; 431 | return; 432 | } 433 | 434 | owner = id; 435 | queue.push(item.value); 436 | yield item.value; 437 | if (id !== owner) break; 438 | } 439 | } 440 | } 441 | 442 | return [gen(0), gen(1)] as [IterableIterator, IterableIterator]; 443 | } 444 | 445 | /** 446 | * Break iterable into lists of length `size`. 447 | */ 448 | export function* chunk( 449 | iterable: Iterable, 450 | size: number 451 | ): IterableIterator { 452 | let chunk: T[] = []; 453 | 454 | for (const item of iterable) { 455 | chunk.push(item); 456 | 457 | if (chunk.length === size) { 458 | yield chunk; 459 | chunk = []; 460 | } 461 | } 462 | 463 | if (chunk.length) yield chunk; 464 | } 465 | 466 | /** 467 | * Returns an iterator of paired items, overlapping, from the original. When 468 | * the input iterable has a finite number of items `n`, the outputted iterable 469 | * will have `n - 1` items. 470 | */ 471 | export function* pairwise(iterable: Iterable): IterableIterator<[T, T]> { 472 | const it = iter(iterable); 473 | let item = it.next(); 474 | let prev = item.value; 475 | 476 | if (item.done) return; 477 | 478 | while ((item = it.next())) { 479 | if (item.done) return; 480 | yield [prev, item.value]; 481 | prev = item.value; 482 | } 483 | } 484 | 485 | /** 486 | * Make an iterator that filters elements from `iterable` returning only those 487 | * that have a corresponding element in selectors that evaluates to `true`. 488 | */ 489 | export function* compress( 490 | iterable: Iterable, 491 | selectors: Iterable 492 | ): IterableIterator { 493 | for (const [item, valid] of zip(iterable, selectors)) { 494 | if (valid) yield item; 495 | } 496 | } 497 | 498 | /** 499 | * Creates an array from an iterable object. 500 | */ 501 | export function list( 502 | iterable: Iterable, 503 | fn?: Func 504 | ): Array; 505 | export function list( 506 | iterable: Iterable, 507 | fn: Func = identity 508 | ): Array { 509 | const result: Array = []; 510 | for (const item of iterable) result.push(fn(item)); 511 | return result; 512 | } 513 | 514 | /** 515 | * Return a sorted array from the items in iterable. 516 | */ 517 | export function sorted( 518 | iterable: Iterable, 519 | keyFn?: Func, 520 | cmpFn?: (x: U, y: U) => number, 521 | reverse?: boolean 522 | ): Array; 523 | export function sorted( 524 | iterable: Iterable, 525 | keyFn: Func = identity, 526 | cmpFn: (x: U | T, y: U | T) => number = cmp, 527 | reverse = false 528 | ): Array { 529 | const array = list(iterable, (item) => [keyFn(item), item]); 530 | const sortFn = reverse 531 | ? (a: [U | T, T], b: [U | T, T]) => -cmpFn(a[0], b[0]) 532 | : (a: [U | T, T], b: [U | T, T]) => cmpFn(a[0], b[0]); 533 | return array.sort(sortFn).map((x) => x[1]); 534 | } 535 | 536 | /** 537 | * Return an object from an iterable, i.e. `Array.from` for objects. 538 | */ 539 | export function dict( 540 | iterable: Iterable<[K, V]> 541 | ): Record { 542 | return reduce( 543 | iterable, 544 | (obj, [key, value]) => { 545 | obj[key] = value; 546 | return obj; 547 | }, 548 | Object.create(null) 549 | ); 550 | } 551 | 552 | /** 553 | * Return the length (the number of items) of an iterable. 554 | */ 555 | export function len(iterable: Iterable): number { 556 | let length = 0; 557 | for (const _ of iterable) length++; 558 | return length; 559 | } 560 | 561 | /** 562 | * Return the smallest item in an iterable. 563 | */ 564 | export function min(iterable: Iterable): number; 565 | export function min(iterable: Iterable, keyFn: (x: T) => number): number; 566 | export function min( 567 | iterable: Iterable, 568 | keyFn: Func = identity 569 | ) { 570 | let value = Infinity; 571 | let result = undefined; 572 | 573 | for (const item of iterable) { 574 | const tmp = keyFn(item); 575 | if (tmp < value) { 576 | value = tmp; 577 | result = item; 578 | } 579 | } 580 | 581 | return result; 582 | } 583 | 584 | /** 585 | * Return the largest item in an iterable. 586 | */ 587 | export function max(iterable: Iterable): number; 588 | export function max(iterable: Iterable, keyFn: (x: T) => number): number; 589 | export function max( 590 | iterable: Iterable, 591 | keyFn: Func = identity 592 | ) { 593 | let value = -Infinity; 594 | let result = undefined; 595 | 596 | for (const item of iterable) { 597 | const tmp = keyFn(item); 598 | if (tmp > value) { 599 | value = tmp; 600 | result = item; 601 | } 602 | } 603 | 604 | return result; 605 | } 606 | 607 | /** 608 | * Sums `start` and the items of an `iterable` from left to right and returns 609 | * the total. 610 | */ 611 | export function sum(iterable: Iterable, start = 0): number { 612 | return reduce(iterable, (x, y) => x + y, start); 613 | } 614 | 615 | /** 616 | * Recursively produce all produces of a list of iterators. 617 | */ 618 | function* _product( 619 | pools: Iterator[], 620 | buffer: T[] = [] 621 | ): IterableIterator { 622 | if (pools.length === 0) { 623 | yield buffer.slice(); 624 | return; 625 | } 626 | 627 | const [pool, ...others] = pools; 628 | 629 | while (true) { 630 | const item = pool.next(); 631 | if (item.value === SENTINEL) break; 632 | buffer.push(item.value); 633 | yield* _product(others, buffer); 634 | buffer.pop(); 635 | } 636 | } 637 | 638 | /** 639 | * Cartesian product of input iterables. 640 | */ 641 | export function* product( 642 | ...iterables: TupleIterable 643 | ): IterableIterator { 644 | const pools = iterables.map((x) => 645 | iter(cycle(chain(x, repeat(SENTINEL, 1)))) 646 | ); 647 | 648 | yield* _product(pools) as IterableIterator; 649 | } 650 | -------------------------------------------------------------------------------- /src/async.spec.ts: -------------------------------------------------------------------------------- 1 | import * as iter from "./async"; 2 | import { expectType, TypeEqual } from "ts-expect"; 3 | 4 | describe("iterative", () => { 5 | describe("all", () => { 6 | it("should return true when all match", async () => { 7 | const result = await iter.all([1, 2, 3], (x) => true); 8 | 9 | expect(result).toBe(true); 10 | }); 11 | 12 | it("should return false when a value does not match", async () => { 13 | const result = await iter.all([1, 2, 3], (x) => x % 2 === 1); 14 | 15 | expect(result).toBe(false); 16 | }); 17 | }); 18 | 19 | describe("any", () => { 20 | it("should return true when any match", async () => { 21 | const result = await iter.any([1, 2, 3], (x) => x === 3); 22 | 23 | expect(result).toBe(true); 24 | }); 25 | 26 | it("should return false when none match", async () => { 27 | const result = await iter.any([1, 2, 3], (x) => x === 5); 28 | 29 | expect(result).toBe(false); 30 | }); 31 | }); 32 | 33 | describe("contains", () => { 34 | it("should find value in iterator", async () => { 35 | const result = await iter.contains("test", "s"); 36 | 37 | expect(result).toBe(true); 38 | }); 39 | 40 | it("should return false when not found", async () => { 41 | const result = await iter.contains("test", "a"); 42 | 43 | expect(result).toBe(false); 44 | }); 45 | }); 46 | 47 | describe("accumulate", () => { 48 | it("should accumulate values from iterator", async () => { 49 | const iterable = iter.accumulate([1, 2, 3], (x, y) => x + y); 50 | 51 | expect(await iter.list(iterable)).toEqual([1, 3, 6]); 52 | }); 53 | }); 54 | 55 | describe("range", () => { 56 | it("should generate range from 0 until stop", async () => { 57 | const iterable = iter.range(0, 5); 58 | 59 | expect(await iter.list(iterable)).toEqual([0, 1, 2, 3, 4]); 60 | }); 61 | 62 | it("should generate range from start to stop", async () => { 63 | const iterable = iter.range(5, 10); 64 | 65 | expect(await iter.list(iterable)).toEqual([5, 6, 7, 8, 9]); 66 | }); 67 | 68 | it("should generate range from start to stop with step", async () => { 69 | const iterable = iter.range(0, 30, 5); 70 | 71 | expect(await iter.list(iterable)).toEqual([0, 5, 10, 15, 20, 25]); 72 | }); 73 | }); 74 | 75 | describe("flatten", () => { 76 | it("should flatten an iterable of iterables", async () => { 77 | const iterable = iter.slice( 78 | iter.flatten(iter.map(iter.range(), (stop) => iter.range(0, stop))), 79 | 10, 80 | 20 81 | ); 82 | 83 | expect(await iter.list(iterable)).toEqual([0, 1, 2, 3, 4, 0, 1, 2, 3, 4]); 84 | }); 85 | }); 86 | 87 | describe("chain", () => { 88 | it("should chain together iterables", async () => { 89 | const iterable = iter.chain(iter.range(0, 5), iter.range(0, 5)); 90 | 91 | expect(await iter.list(iterable)).toEqual([0, 1, 2, 3, 4, 0, 1, 2, 3, 4]); 92 | }); 93 | 94 | it("should allow flat map", async () => { 95 | const iterable = iter.chain( 96 | ...(await iter.list( 97 | iter.map(iter.range(0, 5), (stop) => iter.range(0, stop)) 98 | )) 99 | ); 100 | 101 | expect(await iter.list(iterable)).toEqual([0, 0, 1, 0, 1, 2, 0, 1, 2, 3]); 102 | }); 103 | }); 104 | 105 | describe("slice", () => { 106 | it("should slice an iterable", async () => { 107 | const iterable = iter.slice(iter.range(0, 1000), 0, 10); 108 | 109 | expect(await iter.list(iterable)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 110 | }); 111 | 112 | it("should slice from non-zero offset", async () => { 113 | const iterable = iter.slice(iter.range(), 2, 4); 114 | 115 | expect(await iter.list(iterable)).toEqual([2, 3]); 116 | }); 117 | }); 118 | 119 | describe("dropWhile", () => { 120 | it("should drop values until predicate becomes falsy", async () => { 121 | const iterable = iter.slice( 122 | iter.dropWhile(iter.range(), (x) => x < 10), 123 | 0, 124 | 3 125 | ); 126 | 127 | expect(await iter.list(iterable)).toEqual([10, 11, 12]); 128 | }); 129 | 130 | it("should drop nothing if immediately returns false", async () => { 131 | const iterable = iter.slice( 132 | iter.dropWhile(iter.range(), (x) => false), 133 | 0, 134 | 3 135 | ); 136 | 137 | expect(await iter.list(iterable)).toEqual([0, 1, 2]); 138 | }); 139 | }); 140 | 141 | describe("takeWhile", () => { 142 | it("take while predicate is truthy", async () => { 143 | const iterable = iter.takeWhile(iter.range(), (x) => x < 5); 144 | 145 | expect(await iter.list(iterable)).toEqual([0, 1, 2, 3, 4]); 146 | }); 147 | }); 148 | 149 | describe("repeat", () => { 150 | it("should repeat a value indefinitely", async () => { 151 | const iterable = iter.slice(iter.repeat(10), 0, 5); 152 | 153 | expect(await iter.list(iterable)).toEqual([10, 10, 10, 10, 10]); 154 | }); 155 | 156 | it("should repeat a value up to times", async () => { 157 | const iterable = iter.repeat(10, 5); 158 | 159 | expect(await iter.list(iterable)).toEqual([10, 10, 10, 10, 10]); 160 | }); 161 | }); 162 | 163 | describe("cycle", () => { 164 | it("should cycle over an iterator producing an infinite iterator", async () => { 165 | const iterable = iter.slice(iter.cycle("abc"), 0, 5); 166 | 167 | expect(await iter.list(iterable)).toEqual(["a", "b", "c", "a", "b"]); 168 | }); 169 | }); 170 | 171 | describe("groupBy", () => { 172 | it("should group by sequentially", async () => { 173 | const iterable = iter.groupBy([1, 2, 3, 4, 5], (x) => Math.floor(x / 2)); 174 | const result = await iter.list(iterable, async ([index, iterable]) => [ 175 | index, 176 | await iter.list(iterable), 177 | ]); 178 | 179 | expect(result).toEqual([ 180 | [0, [1]], 181 | [1, [2, 3]], 182 | [2, [4, 5]], 183 | ]); 184 | }); 185 | 186 | it("should skip over groups when not consumed", async () => { 187 | const iterable = iter.groupBy([1, 2, 3, 4, 5], (x) => Math.floor(x / 2)); 188 | const result = await iter.list(iterable, ([index]) => index); 189 | 190 | expect(result).toEqual([0, 1, 2]); 191 | }); 192 | 193 | it("should consume partial groups", async () => { 194 | const iterable = iter.groupBy([1, 2, 3, 4, 5], (x) => Math.floor(x / 2)); 195 | const result = await iter.list(iterable, async ([index, iterable]) => { 196 | return [index, await iter.next(iterable)]; 197 | }); 198 | 199 | expect(result).toEqual([ 200 | [0, 1], 201 | [1, 2], 202 | [2, 4], 203 | ]); 204 | }); 205 | }); 206 | 207 | describe("slice", () => { 208 | it("should slice an iterable", async () => { 209 | const iterable = iter.slice([1, 2, 3, 4, 5], 0, 2); 210 | 211 | expect(await iter.list(iterable)).toEqual([1, 2]); 212 | }); 213 | 214 | it("should exhaust an iterable when range is too large", async () => { 215 | const iterable = iter.slice([1, 2, 3, 4, 5], 2, 10); 216 | 217 | expect(await iter.list(iterable)).toEqual([3, 4, 5]); 218 | }); 219 | 220 | it("should specify a custom step", async () => { 221 | const iterable = iter.slice([1, 2, 3, 4, 5], 0, Infinity, 3); 222 | 223 | expect(await iter.list(iterable)).toEqual([1, 4]); 224 | }); 225 | }); 226 | 227 | describe("reduce", () => { 228 | it("should reduce an iterator to a single value", async () => { 229 | const result = await iter.reduce(iter.range(0, 5), (x, y) => x + y); 230 | 231 | expect(result).toEqual(10); 232 | }); 233 | }); 234 | 235 | describe("map", () => { 236 | it("should map iterator values", async () => { 237 | const iterable = iter.slice( 238 | iter.map(iter.range(), (x) => x * x), 239 | 0, 240 | 5 241 | ); 242 | 243 | expect(await iter.list(iterable)).toEqual([0, 1, 4, 9, 16]); 244 | }); 245 | }); 246 | 247 | describe("spreadmap", () => { 248 | it("should spread map iterator values", async () => { 249 | const iterable = iter.slice( 250 | iter.spreadmap(iter.zip(iter.range(), iter.range()), (a, b) => a + b), 251 | 0, 252 | 5 253 | ); 254 | 255 | expect(await iter.list(iterable)).toEqual([0, 2, 4, 6, 8]); 256 | }); 257 | }); 258 | 259 | describe("filter", () => { 260 | it("should filter values from iterator", async () => { 261 | const iterable = iter.slice( 262 | iter.filter(iter.range(), (x) => x % 2 === 0), 263 | 0, 264 | 5 265 | ); 266 | 267 | expect(await iter.list(iterable)).toEqual([0, 2, 4, 6, 8]); 268 | }); 269 | 270 | it("should filter with correct output type", async () => { 271 | const iterable = iter.filter( 272 | ["a", 1, "b", 2, "c", 3], 273 | (x): x is string => typeof x === "string" 274 | ); 275 | 276 | expect(await iter.list(iterable)).toEqual(["a", "b", "c"]); 277 | }); 278 | }); 279 | 280 | describe("tee", () => { 281 | it("should return two independent iterables from one", async () => { 282 | const iterable = iter.map([1, 2, 3], (x) => x * 2); 283 | const [a, b] = iter.tee(iterable); 284 | 285 | expect(await iter.list(a)).toEqual([2, 4, 6]); 286 | expect(await iter.list(b)).toEqual([2, 4, 6]); 287 | }); 288 | 289 | it("should read varying from cache to iterable", async () => { 290 | const iterable = iter.range(0, 5); 291 | const [a, b] = iter.tee(iterable).map(iter.iter); 292 | 293 | expect([(await a.next()).value, (await a.next()).value]).toEqual([0, 1]); 294 | 295 | expect([ 296 | (await b.next()).value, 297 | (await b.next()).value, 298 | (await b.next()).value, 299 | ]).toEqual([0, 1, 2]); 300 | 301 | expect([(await a.next()).value, (await a.next()).value]).toEqual([2, 3]); 302 | expect([(await b.next()).value, (await b.next()).value]).toEqual([3, 4]); 303 | 304 | expect((await a.next()).value).toEqual(4); 305 | expect((await b.next()).value).toEqual(undefined); 306 | }); 307 | 308 | it("should tee empty iterable", async () => { 309 | const iterable = iter.range(0, 0); 310 | const [a, b] = iter.tee(iterable); 311 | 312 | expect(await iter.list(a)).toEqual([]); 313 | expect(await iter.list(b)).toEqual([]); 314 | }); 315 | 316 | it("should call `next` the right number of times", async () => { 317 | let i = 0; 318 | const next = jest.fn(() => ({ value: i++, done: i > 10 })); 319 | const iterable: Iterable = { 320 | [Symbol.iterator]: () => ({ next }), 321 | }; 322 | 323 | const [a, b] = iter.tee(iterable); 324 | 325 | // Exhaust both iterables. 326 | expect(await iter.list(a)).toEqual(await iter.list(iter.range(0, 10))); 327 | expect(await iter.list(b)).toEqual(await iter.list(iter.range(0, 10))); 328 | 329 | expect(next.mock.calls.length).toEqual(11); 330 | }); 331 | }); 332 | 333 | describe("chunk", () => { 334 | it("should chunk an iterable", async () => { 335 | const iterable = iter.chunk([1, 2, 3, 4, 5, 6], 2); 336 | 337 | expect(await iter.list(iterable)).toEqual([ 338 | [1, 2], 339 | [3, 4], 340 | [5, 6], 341 | ]); 342 | }); 343 | 344 | it("should yield last chunk when less than chunk size", async () => { 345 | const iterable = iter.chunk([1, 2, 3, 4, 5], 3); 346 | 347 | expect(await iter.list(iterable)).toEqual([ 348 | [1, 2, 3], 349 | [4, 5], 350 | ]); 351 | }); 352 | }); 353 | 354 | describe("pairwise", () => { 355 | it("should generate pairwise iterator", async () => { 356 | const iterable = iter.pairwise([1, 2, 3, 4]); 357 | 358 | expect(await iter.list(iterable)).toEqual([ 359 | [1, 2], 360 | [2, 3], 361 | [3, 4], 362 | ]); 363 | }); 364 | 365 | it("should not generate any values when iterator too small", async () => { 366 | const iterable = iter.pairwise([1]); 367 | 368 | expect(await iter.list(iterable)).toEqual([]); 369 | }); 370 | }); 371 | 372 | describe("zip", () => { 373 | it("should zip two iterables", async () => { 374 | const iterable = iter.zip([1, 2, 3], ["a", "b", "c"]); 375 | 376 | expect(await iter.list(iterable)).toEqual([ 377 | [1, "a"], 378 | [2, "b"], 379 | [3, "c"], 380 | ]); 381 | }); 382 | 383 | it("should stop when an iterable is done", async () => { 384 | const iterable = iter.zip([1, 2, 3], [1, 2, 3, 4, 5]); 385 | 386 | expect(await iter.list(iterable)).toEqual([ 387 | [1, 1], 388 | [2, 2], 389 | [3, 3], 390 | ]); 391 | }); 392 | 393 | it("should do nothing without iterables", async () => { 394 | const iterable = iter.zip(); 395 | 396 | expect(await iter.list(iterable)).toEqual([]); 397 | }); 398 | }); 399 | 400 | describe("zipLongest", () => { 401 | it("should zip until the longest value", async () => { 402 | const iterable = iter.zipLongest(iter.range(0, 2), iter.range(0, 5)); 403 | 404 | expectType< 405 | TypeEqual< 406 | AsyncIterableIterator<[number | undefined, number | undefined]>, 407 | typeof iterable 408 | > 409 | >(true); 410 | 411 | expect(await iter.list(iterable)).toEqual([ 412 | [0, 0], 413 | [1, 1], 414 | [undefined, 2], 415 | [undefined, 3], 416 | [undefined, 4], 417 | ]); 418 | }); 419 | 420 | it("should do nothing without iterables", async () => { 421 | const iterable = iter.zipLongest(); 422 | 423 | expect(await iter.list(iterable)).toEqual([]); 424 | }); 425 | }); 426 | 427 | describe("zipWithValue", () => { 428 | it("should zip until the longest value", async () => { 429 | const iterable = iter.zipWithValue( 430 | "test", 431 | iter.range(0, 2), 432 | iter.range(0, 5) 433 | ); 434 | 435 | expectType< 436 | TypeEqual< 437 | AsyncIterableIterator<[number | string, number | string]>, 438 | typeof iterable 439 | > 440 | >(true); 441 | 442 | expect(await iter.list(iterable)).toEqual([ 443 | [0, 0], 444 | [1, 1], 445 | ["test", 2], 446 | ["test", 3], 447 | ["test", 4], 448 | ]); 449 | }); 450 | }); 451 | 452 | describe("compress", () => { 453 | it("should compress an iterable based on boolean sequence", async () => { 454 | const iterable = iter.compress(iter.range(), [true, false, true]); 455 | 456 | expect(await iter.list(iterable)).toEqual([0, 2]); 457 | }); 458 | }); 459 | 460 | describe("sorted", () => { 461 | it("should return a sorted list", async () => { 462 | const list = await iter.sorted(iter.slice(iter.cycle([1, 2, 3]), 0, 10)); 463 | 464 | expect(list).toEqual([1, 1, 1, 1, 2, 2, 2, 3, 3, 3]); 465 | }); 466 | 467 | it("should return list in reverse order", async () => { 468 | const list = await iter.sorted( 469 | iter.slice(iter.range(), 0, 10), 470 | undefined, 471 | undefined, 472 | true 473 | ); 474 | 475 | expect(list).toEqual([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]); 476 | }); 477 | 478 | it("should allow key function", async () => { 479 | const list = await iter.sorted( 480 | [{ x: 3 }, { x: 2 }, { x: 1 }], 481 | (x) => x.x 482 | ); 483 | 484 | expect(list).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]); 485 | }); 486 | 487 | it("should allow compare function", async () => { 488 | const list = await iter.sorted( 489 | [1, 2, 3, 4, 5], 490 | undefined, 491 | (x, y) => y - x 492 | ); 493 | 494 | expect(list).toEqual([5, 4, 3, 2, 1]); 495 | }); 496 | 497 | it("should combine key and compare functions", async () => { 498 | const list = await iter.sorted( 499 | [{ x: 2 }, { x: 1 }, { x: 3 }], 500 | (x) => x.x, 501 | (x, y) => y - x 502 | ); 503 | 504 | expect(list).toEqual([{ x: 3 }, { x: 2 }, { x: 1 }]); 505 | }); 506 | }); 507 | 508 | describe("dict", () => { 509 | it("should create an object from an iterable", async () => { 510 | const iterable = iter.zip(iter.range(1, 4), iter.repeat(true)); 511 | 512 | expect(await iter.dict(iterable)).toEqual({ 1: true, 2: true, 3: true }); 513 | }); 514 | }); 515 | 516 | describe("len", () => { 517 | it("should count the length of an iterable", async () => { 518 | const iterable = iter.range(0, 5); 519 | 520 | expect(await iter.len(iterable)).toEqual(5); 521 | }); 522 | }); 523 | 524 | describe("min", () => { 525 | it("should find the minimum value", async () => { 526 | expect(await iter.min([1, 5, -2])).toEqual(-2); 527 | }); 528 | 529 | it("should find minimum value by key", async () => { 530 | const iterable = iter.zip(iter.repeat(true), iter.range(0, 100)); 531 | 532 | expect(await iter.min(iterable, (x) => x[1])).toEqual([true, 0]); 533 | }); 534 | }); 535 | 536 | describe("max", () => { 537 | it("should find the maximum value", async () => { 538 | expect(await iter.max([1, 5, -2])).toEqual(5); 539 | }); 540 | 541 | it("should find maximum value by key", async () => { 542 | const iterable = iter.zip(iter.repeat(true), iter.range(0, 100)); 543 | 544 | expect(await iter.max(iterable, (x) => x[1])).toEqual([true, 99]); 545 | }); 546 | }); 547 | 548 | describe("sum", () => { 549 | it("should sum an iterable", async () => { 550 | expect(await iter.sum(iter.range(0, 10))).toEqual(45); 551 | }); 552 | 553 | it("should sum an iterable with custom start", async () => { 554 | expect(await iter.sum(iter.range(0, 10), 5)).toEqual(50); 555 | }); 556 | }); 557 | 558 | describe("product", () => { 559 | it("should generate the product of multiple iterators", async () => { 560 | const iterable = iter.product("ABCD", "xy"); 561 | 562 | const result = [ 563 | ["A", "x"], 564 | ["A", "y"], 565 | ["B", "x"], 566 | ["B", "y"], 567 | ["C", "x"], 568 | ["C", "y"], 569 | ["D", "x"], 570 | ["D", "y"], 571 | ]; 572 | 573 | expect(await iter.list(iterable)).toEqual(result); 574 | }); 575 | }); 576 | 577 | describe("iterable", () => { 578 | it("should convert an iterator to iterable", async () => { 579 | const iterator = iter.iter([1, 2, 3]); 580 | const iterable = iter.iterable(iterator); 581 | 582 | await iter.next(iterator); // Discard first value. 583 | 584 | expect(await iter.list(iterable)).toEqual([2, 3]); 585 | }); 586 | }); 587 | }); 588 | -------------------------------------------------------------------------------- /src/async.ts: -------------------------------------------------------------------------------- 1 | import { StopIteration, SENTINEL, identity, cmp } from "./common"; 2 | 3 | /** 4 | * Sync and async iterable objects. 5 | */ 6 | export type AnyIterable = AsyncIterable | Iterable; 7 | 8 | /** 9 | * Sync and async iterator objects. 10 | */ 11 | export type AnyIterator = AsyncIterator | Iterator; 12 | 13 | /** 14 | * List of values to list of iterable values. 15 | */ 16 | export type AnyTupleIterable = { 17 | [K in keyof T]: AnyIterable; 18 | }; 19 | 20 | /** 21 | * Async predicate for filtering items. 22 | */ 23 | export type AnyPredicate = 24 | | ((item: T) => item is U) 25 | | ((item: T) => boolean | Promise); 26 | 27 | /** 28 | * Async reducer function. 29 | */ 30 | export type AnyReducer = (result: U, item: T) => U | Promise; 31 | 32 | /** 33 | * Unary function mapping an input value to an output value. 34 | */ 35 | export type AnyFunc = (item: T) => U | Promise; 36 | 37 | /** 38 | * Returns `true` when all values in iterable are truthy. 39 | */ 40 | export async function all( 41 | iterable: AnyIterable, 42 | predicate: AnyPredicate = Boolean 43 | ) { 44 | for await (const item of iterable) { 45 | if (!(await predicate(item))) return false; 46 | } 47 | 48 | return true; 49 | } 50 | 51 | /** 52 | * Returns `true` when any value in iterable is truthy. 53 | */ 54 | export async function any( 55 | iterable: AnyIterable, 56 | predicate: AnyPredicate = Boolean 57 | ) { 58 | for await (const item of iterable) { 59 | if (await predicate(item)) return true; 60 | } 61 | 62 | return false; 63 | } 64 | 65 | /** 66 | * Returns `true` when any value in iterable is equal to `needle`. 67 | */ 68 | export function contains(iterable: AnyIterable, needle: T) { 69 | return any(iterable, (x) => x === needle); 70 | } 71 | 72 | /** 73 | * Returns an iterable of enumeration pairs. 74 | */ 75 | export function enumerate(iterable: AnyIterable, offset = 0) { 76 | return zip(range(offset), iterable); 77 | } 78 | 79 | /** 80 | * Get next iterator value, throw when `done`. 81 | */ 82 | export function next(iterator: AnyIterator): Promise; 83 | export function next( 84 | iterator: AnyIterator, 85 | defaultValue: U 86 | ): Promise; 87 | export async function next( 88 | iterator: AnyIterator, 89 | defaultValue?: U 90 | ): Promise { 91 | const item = await iterator.next(); 92 | if (item.done) { 93 | if (arguments.length === 1) throw new StopIteration(); 94 | return defaultValue as U; 95 | } 96 | return item.value; 97 | } 98 | 99 | /** 100 | * Returns an iterator object for the given `iterable`. 101 | */ 102 | export function iter(iterable: AnyIterable): AnyIterator { 103 | if ((iterable as AsyncIterable)[Symbol.asyncIterator]) { 104 | return (iterable as AsyncIterable)[Symbol.asyncIterator](); 105 | } 106 | 107 | return (iterable as Iterable)[Symbol.iterator](); 108 | } 109 | 110 | /** 111 | * Convert an iterator object back into an iterator. 112 | */ 113 | export async function* iterable(iterator: AnyIterator): AsyncIterable { 114 | while (true) { 115 | const item = await iterator.next(); 116 | if (item.done) return; 117 | yield item.value; 118 | } 119 | } 120 | 121 | /** 122 | * Make an iterator that returns accumulated results of binary functions. 123 | */ 124 | export async function* accumulate( 125 | iterable: AnyIterable, 126 | func: AnyReducer 127 | ): AsyncIterableIterator { 128 | const it = iter(iterable); 129 | let item = await it.next(); 130 | let total = item.value; 131 | 132 | if (item.done) return; 133 | yield total; 134 | 135 | while ((item = await it.next())) { 136 | if (item.done) break; 137 | total = await func(total, item.value); 138 | yield total; 139 | } 140 | } 141 | 142 | /** 143 | * Return an iterator flattening one level of nesting in an iterable of iterables. 144 | */ 145 | export async function* flatten( 146 | iterable: AnyIterable> 147 | ): AsyncIterableIterator { 148 | for await (const it of iterable) { 149 | for await (const item of it) { 150 | yield item; 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * Make an iterator that returns elements from the first iterable until it is 157 | * exhausted, then proceeds to the next iterable, until all of the iterables are 158 | * exhausted. Used for treating consecutive sequences as a single sequence. 159 | */ 160 | export function chain( 161 | ...iterables: Array> 162 | ): AsyncIterableIterator { 163 | return flatten(iterables); 164 | } 165 | 166 | /** 167 | * This is a versatile function to create lists containing arithmetic progressions. 168 | */ 169 | export async function* range( 170 | start = 0, 171 | stop = Infinity, 172 | step = 1 173 | ): AsyncIterableIterator { 174 | for (let i = start; i < stop; i += step) yield i; 175 | } 176 | 177 | /** 178 | * Make an iterator returning elements from the iterable and saving a copy of 179 | * each. When the iterable is exhausted, return elements from the saved copy. 180 | * Repeats indefinitely. 181 | */ 182 | export async function* cycle( 183 | iterable: AnyIterable 184 | ): AsyncIterableIterator { 185 | const saved: T[] = []; 186 | 187 | for await (const item of iterable) { 188 | yield item; 189 | saved.push(item); 190 | } 191 | 192 | while (saved.length) { 193 | for (const item of saved) { 194 | yield item; 195 | } 196 | } 197 | } 198 | 199 | /** 200 | * Make an iterator that repeats `value` over and over again. 201 | */ 202 | export async function* repeat( 203 | value: T, 204 | times?: number 205 | ): AsyncIterableIterator { 206 | if (times === undefined) while (true) yield value; 207 | for (let i = 0; i < times; i++) yield value; 208 | } 209 | 210 | /** 211 | * Make an iterator that drops elements from the iterable as long as the 212 | * predicate is true; afterwards, returns every element. 213 | */ 214 | export async function* dropWhile( 215 | iterable: AnyIterable, 216 | predicate: AnyPredicate 217 | ) { 218 | const it = iter(iterable); 219 | let item = await it.next(); 220 | 221 | while (!item.done) { 222 | if (!(await predicate(item.value))) break; 223 | 224 | item = await it.next(); 225 | } 226 | 227 | do { 228 | yield item.value; 229 | item = await it.next(); 230 | } while (!item.done); 231 | } 232 | 233 | /** 234 | * Make an iterator that returns elements from the iterable as long as the 235 | * predicate is true. 236 | */ 237 | export async function* takeWhile( 238 | iterable: AnyIterable, 239 | predicate: AnyPredicate 240 | ) { 241 | for await (const item of iterable) { 242 | if (!(await predicate(item))) break; 243 | 244 | yield item; 245 | } 246 | } 247 | 248 | /** 249 | * Make an iterator that returns consecutive keys and groups from the `iterable`. 250 | * The `func` is a function computing a key value for each element. 251 | */ 252 | export async function* groupBy( 253 | iterable: AnyIterable, 254 | func: AnyFunc 255 | ) { 256 | const it = iter(iterable); 257 | let item = await it.next(); 258 | 259 | if (item.done) return; 260 | 261 | let key = await func(item.value); 262 | let currKey: U | typeof SENTINEL = key; 263 | 264 | async function* grouper() { 265 | do { 266 | yield item.value; 267 | 268 | item = await it.next(); 269 | 270 | // Break iteration when underlying iterator is `done`. 271 | if (item.done) { 272 | currKey = SENTINEL; 273 | return; 274 | } 275 | 276 | currKey = await func(item.value); 277 | } while (key === currKey); 278 | } 279 | 280 | do { 281 | yield [key, grouper()] as [U, AsyncIterableIterator]; 282 | 283 | // Skip over any remaining values not pulled from `grouper`. 284 | while (key === currKey) { 285 | item = await it.next(); 286 | if (item.done) return; 287 | currKey = await func(item.value); 288 | } 289 | 290 | key = currKey; 291 | } while (!item.done); 292 | } 293 | 294 | /** 295 | * Make an iterator that returns selected elements from the `iterable`. 296 | */ 297 | export async function* slice( 298 | iterable: AnyIterable, 299 | start = 0, 300 | stop = Infinity, 301 | step = 1 302 | ) { 303 | const it = iter(range(start, stop, step)); 304 | let next = await it.next(); 305 | 306 | for await (const [index, item] of enumerate(iterable)) { 307 | if (next.done) return; 308 | 309 | if (index === next.value) { 310 | yield item; 311 | next = await it.next(); 312 | } 313 | } 314 | } 315 | 316 | /** 317 | * Apply function of two arguments cumulatively to the items of `iterable`, from 318 | * left to right, so as to reduce the iterable to a single value. 319 | */ 320 | export function reduce( 321 | iterable: AnyIterable, 322 | reducer: AnyReducer 323 | ): Promise; 324 | export function reduce( 325 | iterable: AnyIterable, 326 | reducer: AnyReducer, 327 | initializer: U 328 | ): Promise; 329 | export async function reduce( 330 | iterable: AnyIterable, 331 | reducer: AnyReducer, 332 | initializer?: U | Promise 333 | ): Promise { 334 | const it = iter(iterable); 335 | let item: IteratorResult; 336 | let accumulator = await (initializer === undefined ? next(it) : initializer); 337 | 338 | while ((item = await it.next())) { 339 | if (item.done) break; 340 | accumulator = await reducer(accumulator, item.value); 341 | } 342 | 343 | return accumulator; 344 | } 345 | 346 | /** 347 | * Apply function to every item of iterable and return an iterable of the results. 348 | */ 349 | export async function* map( 350 | iterable: AnyIterable, 351 | func: AnyFunc 352 | ): AsyncIterableIterator { 353 | for await (const item of iterable) yield func(item); 354 | } 355 | 356 | /** 357 | * Make an iterator that computes the function using arguments obtained from the 358 | * iterable. Used instead of `map()` when argument parameters are already 359 | * grouped in tuples from a single iterable (the data has been "pre-zipped"). 360 | * The difference between `map()` and `spreadmap()` parallels the distinction 361 | * between `function(a, b)` and `function(...c)`. 362 | */ 363 | export async function* spreadmap( 364 | iterable: AnyIterable, 365 | func: (...args: T) => U | Promise 366 | ): AsyncIterableIterator { 367 | for await (const item of iterable) yield func(...item); 368 | } 369 | 370 | /** 371 | * Construct an `iterator` from those elements of `iterable` for which `func` returns true. 372 | */ 373 | export async function* filter( 374 | iterable: AnyIterable, 375 | func: AnyPredicate = Boolean 376 | ): AsyncIterableIterator { 377 | for await (const item of iterable) { 378 | if (await func(item)) yield item; 379 | } 380 | } 381 | 382 | /** 383 | * Make an iterator that aggregates elements from each of the iterables. Returns 384 | * an iterator of tuples, where the `i`-th tuple contains the `i`-th element 385 | * from each of the argument sequences or iterables. The iterator stops when the 386 | * shortest input iterable is exhausted. 387 | */ 388 | export async function* zip( 389 | ...iterables: AnyTupleIterable 390 | ): AsyncIterableIterator { 391 | const iters = iterables.map((x) => iter(x)); 392 | 393 | while (iters.length) { 394 | const result = Array(iters.length) as T; 395 | const items = await Promise.all(iters.map((x) => x.next())); 396 | 397 | for (let i = 0; i < items.length; i++) { 398 | const item = items[i]; 399 | if (item.done) return; 400 | result[i] = item.value; 401 | } 402 | 403 | yield result; 404 | } 405 | } 406 | 407 | /** 408 | * Make an iterator that aggregates elements from each of the iterables. If the 409 | * iterables are of uneven length, missing values are `undefined`. Iteration 410 | * continues until the longest iterable is exhausted. 411 | */ 412 | export function zipLongest(...iterables: AnyTupleIterable) { 413 | return zipWithValue(undefined, ...(iterables as any)); 414 | } 415 | 416 | /** 417 | * Make an iterator that aggregates elements from each of the iterables. If the 418 | * iterables are of uneven length, missing values are `fillValue`. Iteration 419 | * continues until the longest iterable is exhausted. 420 | */ 421 | export async function* zipWithValue( 422 | fillValue: U, 423 | ...iterables: AnyTupleIterable 424 | ): AsyncIterableIterator<{ [K in keyof T]: T[K] | U }> { 425 | const iters = iterables.map>((x) => iter(x)); 426 | const noop = iter(repeat(fillValue)); 427 | let counter = iters.length; 428 | 429 | while (true) { 430 | const result = Array(iters.length) as { [K in keyof T]: T[K] | U }; 431 | const items = await Promise.all(iters.map((x) => x.next())); 432 | 433 | for (let i = 0; i < items.length; i++) { 434 | const item = items[i]; 435 | 436 | if (item.done) { 437 | counter -= 1; 438 | iters[i] = noop; 439 | result[i] = fillValue; 440 | } else { 441 | result[i] = item.value; 442 | } 443 | } 444 | 445 | if (counter === 0) break; 446 | 447 | yield result; 448 | } 449 | } 450 | 451 | /** 452 | * Return two independent iterables from a single iterable. 453 | */ 454 | export function tee(iterable: AnyIterable) { 455 | const queue: T[] = []; 456 | const it = iter(iterable); 457 | let owner: -1 | 0 | 1; 458 | 459 | async function* gen(id: 0 | 1): AsyncIterableIterator { 460 | while (true) { 461 | while (queue.length) { 462 | yield queue.shift()!; 463 | } 464 | 465 | if (owner === -1) return; 466 | 467 | let item: IteratorResult; 468 | 469 | while ((item = await it.next())) { 470 | if (item.done) { 471 | owner = -1; 472 | return; 473 | } 474 | 475 | owner = id; 476 | queue.push(item.value); 477 | yield item.value; 478 | if (id !== owner) break; 479 | } 480 | } 481 | } 482 | 483 | return [gen(0), gen(1)] as [ 484 | AsyncIterableIterator, 485 | AsyncIterableIterator 486 | ]; 487 | } 488 | 489 | /** 490 | * Break iterable into lists of length `size`. 491 | */ 492 | export async function* chunk( 493 | iterable: AnyIterable, 494 | size: number 495 | ): AsyncIterableIterator { 496 | let chunk: T[] = []; 497 | 498 | for await (const item of iterable) { 499 | chunk.push(item); 500 | 501 | if (chunk.length === size) { 502 | yield chunk; 503 | chunk = []; 504 | } 505 | } 506 | 507 | if (chunk.length) yield chunk; 508 | } 509 | 510 | /** 511 | * Returns an iterator of paired items, overlapping, from the original. When 512 | * the input iterable has a finite number of items `n`, the outputted iterable 513 | * will have `n - 1` items. 514 | */ 515 | export async function* pairwise( 516 | iterable: AnyIterable 517 | ): AsyncIterableIterator<[T, T]> { 518 | const it = iter(iterable); 519 | let item = await it.next(); 520 | let prev = item.value; 521 | 522 | if (item.done) return; 523 | 524 | while ((item = await it.next())) { 525 | if (item.done) return; 526 | yield [prev, item.value]; 527 | prev = item.value; 528 | } 529 | } 530 | 531 | /** 532 | * Make an iterator that filters elements from `iterable` returning only those 533 | * that have a corresponding element in selectors that evaluates to `true`. 534 | */ 535 | export async function* compress( 536 | iterable: AnyIterable, 537 | selectors: AnyIterable 538 | ): AsyncIterableIterator { 539 | for await (const [item, valid] of zip(iterable, selectors)) { 540 | if (valid) yield item; 541 | } 542 | } 543 | 544 | /** 545 | * Creates an array from an iterable object. 546 | */ 547 | export function list( 548 | iterable: AnyIterable, 549 | fn?: AnyFunc 550 | ): Promise>; 551 | export async function list( 552 | iterable: AnyIterable, 553 | fn: AnyFunc = identity 554 | ): Promise> { 555 | const result: Array = []; 556 | for await (const item of iterable) result.push(await fn(item)); 557 | return result; 558 | } 559 | 560 | /** 561 | * Return a sorted array from the items in iterable. 562 | */ 563 | export function sorted( 564 | iterable: AnyIterable, 565 | keyFn?: AnyFunc, 566 | cmpFn?: (x: U, y: U) => number, 567 | reverse?: boolean 568 | ): Promise>; 569 | export async function sorted( 570 | iterable: AnyIterable, 571 | keyFn: AnyFunc = identity, 572 | cmpFn: (x: U | T, y: U | T) => number = cmp, 573 | reverse = false 574 | ): Promise> { 575 | const array = await list( 576 | iterable, 577 | async (item): Promise<[U | T, T]> => [await keyFn(item), item] 578 | ); 579 | const sortFn = reverse 580 | ? (a: [U | T, T], b: [U | T, T]) => -cmpFn(a[0], b[0]) 581 | : (a: [U | T, T], b: [U | T, T]) => cmpFn(a[0], b[0]); 582 | return array.sort(sortFn).map((x) => x[1]); 583 | } 584 | 585 | /** 586 | * Return an object from an iterable, i.e. `Array.from` for objects. 587 | */ 588 | export async function dict( 589 | iterable: AnyIterable<[K, V]> 590 | ): Promise> { 591 | return reduce( 592 | iterable, 593 | (obj, [key, value]) => { 594 | obj[key] = value; 595 | return obj; 596 | }, 597 | Object.create(null) 598 | ); 599 | } 600 | 601 | /** 602 | * Return the length (the number of items) of an iterable. 603 | */ 604 | export async function len(iterable: AnyIterable): Promise { 605 | let length = 0; 606 | for await (const _ of iterable) length++; 607 | return length; 608 | } 609 | 610 | /** 611 | * Return the smallest item in an iterable. 612 | */ 613 | export function min(iterable: AnyIterable): Promise; 614 | export function min( 615 | iterable: AnyIterable, 616 | keyFn: AnyFunc 617 | ): Promise; 618 | export async function min( 619 | iterable: AnyIterable, 620 | keyFn: AnyFunc = identity 621 | ) { 622 | let value = Infinity; 623 | let result = undefined; 624 | 625 | for await (const item of iterable) { 626 | const tmp = await keyFn(item); 627 | if (tmp < value) { 628 | value = tmp; 629 | result = item; 630 | } 631 | } 632 | 633 | return result; 634 | } 635 | 636 | /** 637 | * Return the largest item in an iterable. 638 | */ 639 | export function max(iterable: AnyIterable): Promise; 640 | export function max( 641 | iterable: AnyIterable, 642 | keyFn: AnyFunc 643 | ): Promise; 644 | export async function max( 645 | iterable: AnyIterable, 646 | keyFn: AnyFunc = identity 647 | ) { 648 | let value = -Infinity; 649 | let result = undefined; 650 | 651 | for await (const item of iterable) { 652 | const tmp = await keyFn(item); 653 | if (tmp > value) { 654 | value = tmp; 655 | result = item; 656 | } 657 | } 658 | 659 | return result; 660 | } 661 | 662 | /** 663 | * Sums `start` and the items of an `iterable` from left to right and returns 664 | * the total. 665 | */ 666 | export async function sum( 667 | iterable: AnyIterable, 668 | start = 0 669 | ): Promise { 670 | return reduce(iterable, (x, y) => x + y, start); 671 | } 672 | 673 | /** 674 | * Recursively produce all produces of a list of iterators. 675 | */ 676 | async function* _product( 677 | pools: AnyIterator[], 678 | buffer: T[] = [] 679 | ): AsyncIterableIterator { 680 | if (pools.length === 0) { 681 | yield buffer.slice(); 682 | return; 683 | } 684 | 685 | const [pool, ...others] = pools; 686 | 687 | while (true) { 688 | const item = await pool.next(); 689 | if (item.value === SENTINEL) break; 690 | buffer.push(item.value); 691 | yield* _product(others, buffer); 692 | buffer.pop(); 693 | } 694 | } 695 | 696 | /** 697 | * Cartesian product of input iterables. 698 | */ 699 | export async function* product( 700 | ...iterables: AnyTupleIterable 701 | ): AsyncIterableIterator { 702 | const pools = iterables.map((x) => 703 | iter(cycle(chain(x, repeat(SENTINEL, 1)))) 704 | ); 705 | 706 | yield* _product(pools) as AsyncIterableIterator; 707 | } 708 | --------------------------------------------------------------------------------