├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── package.json ├── test ├── 00-chai.js ├── 01-import.js ├── 02-basic.js ├── 03-array.js ├── 04-fun.js └── index.html └── xiterable.ts /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # cf. 2 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs#starting-with-the-nodejs-workflow-template 3 | # https://dev.classmethod.jp/articles/caching-dependencies-in-workflow-execution-on-github-actions/ 4 | name: CI via GitHub Actions 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | node-version: [20, 18, 16] 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - uses: actions/cache@v3 25 | id: node_modules_cache_id 26 | env: 27 | cache-name: cache-node-modules 28 | with: 29 | path: '**/node_modules' 30 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 31 | - run: echo '${{ toJSON(steps.node_modules_cache_id.outputs) }}' 32 | - if: ${{ steps.node_modules_cache_id.outputs.cache-hit != 'true' }} 33 | run: npm install 34 | - run: npm test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # ignore all . folders but include . files 107 | .*/ 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dan Kogai 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PJ=package.json 2 | TS=xiterable.ts 3 | JS=xiterable.js 4 | MJS=xiterable.mjs 5 | DTS=xiterable.d.ts 6 | 7 | all: $(JS) 8 | 9 | $(JS): $(PJ) $(TS) 10 | tsc -d --module nodenext $(TS) 11 | 12 | test: $(PJ) $(JS) 13 | mocha 14 | 15 | clean: 16 | -rm $(DTS) $(MJS) $(JS) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ES2015](https://img.shields.io/badge/JavaScript-ES2015-blue.svg)](http://www.ecma-international.org/ecma-262/6.0/) 2 | [![MIT LiCENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 3 | [![CI via GitHub Actions](https://github.com/dankogai/js-xiterable/actions/workflows/node.js.yml/badge.svg)](https://github.com/dankogai/js-xiterable/actions/workflows/node.js.yml) 4 | 5 | # js-xiterable 6 | 7 | Make ES6 Iterators Functional Again 8 | 9 | ## Synopsis 10 | 11 | Suppose we have a generator like this. 12 | 13 | ```javascript 14 | function* count(n) { 15 | for (let i = 0; i < n; i++) yield i; 16 | }; 17 | ``` 18 | 19 | We make it more functional like this. 20 | 21 | ```javascript 22 | import {Xiterable} from './xiterable.js'; 23 | const xcount = n => new Xiterable(() => count(n)); 24 | const tens = xcount(10); 25 | const odds = tens.filter(v=>v%2).map(v=>v*v); 26 | const zips = tens.zip(odds); 27 | [...tens]; // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 28 | [...odds]; // [ 1, 9, 25, 49, 81] 29 | [...zips]; // [[0, 1], [1, 9], [2, 25], [3, 49], [4, 81]] 30 | ``` 31 | 32 | In other words, this module make any iterables work like `Array`, with `.map`, `.filter` and so on. 33 | 34 | ### Install 35 | 36 | ```shell 37 | npm install js-xiterable 38 | ``` 39 | 40 | ### Usage 41 | 42 | locally 43 | 44 | ```javascript 45 | import { 46 | Xiterable, 47 | xiterable, zip, zipWith, xrange, repeat 48 | } from './xiterable.js'; 49 | ``` 50 | 51 | remotely 52 | 53 | ```javascript 54 | import {Xiterable} from 'https://cdn.jsdelivr.net/npm/js-xiterable@0.2.2/xiterable.min.js'; 55 | ``` 56 | 57 | ### commonjs (node.js) 58 | 59 | use [babel] or [esm]. 60 | 61 | [babel]: https://babeljs.io 62 | [esm]: https://github.com/standard-things/esm 63 | 64 | ```shell 65 | % node -r esm 66 | Welcome to Node.js v14.5.0. 67 | Type ".help" for more information. 68 | > import * as $X from 'js-xiterable' 69 | undefined 70 | > $X 71 | [Module] { 72 | Xiterable: [Function: Xiterable], 73 | isIterable: [Function: isIterable], 74 | repeat: [Function: repeat], 75 | version: '0.0.3', 76 | xiterable: [Function: xiterable], 77 | xrange: [Function: xrange], 78 | zip: [Function: zip], 79 | zipWith: [Function: zipWith] 80 | } 81 | > [...$X.xrange().take(10).filter(v=>v%2).map(v=>v*v)] 82 | [ 1, 9, 25, 49, 81 ] 83 | > 84 | ``` 85 | 86 | ## Description 87 | 88 | This module makes any given iterables behave like an array with all functional methods like `.map()`, `.filter()`, `.reduce()` and so on. But for methods that `Array.prototype` returns an instance of `Array`, an instace of `Xiterable` is returned. An `Xiterable` instance are: 89 | 90 | * like an instance of `Array` that has `.map()`, `.filter()`, `.reduce()`… 91 | 92 | * unlike an instance of `Array` that demands the storage for elements. Elements are generated on demand. 93 | 94 | ```javascript 95 | [...Array(1e9).keys()].slice(0,10) // gets stuck with a billion elements 96 | [...xrange(1e9).slice(0,10)] // same expected result instantly. 97 | ``` 98 | 99 | * All elements are lazily generated. They are not generated until needed. 100 | 101 | * That is why [.filter](#filter) marks the result infinite, even though it is finite. You cannot estimate the number of elements until you apply the predicate function. 102 | 103 | ### constructor 104 | 105 | from any built-in iterables... 106 | 107 | ```javascript 108 | new Xiterable([0,1,2,3]); 109 | new Xiterable('0123'); 110 | new Xiterable(new Uint8Array([0,1,2,3])) 111 | ``` 112 | 113 | or your custom generator (with no argument)... 114 | 115 | ```javascript 116 | let it = new Xiterable(function *() { 117 | for (let i = 0; true; i++) yield i; 118 | }); 119 | [...it.take(8)]; // [ 0, 1, 2, 3, 4, 5, 6, 7] 120 | [...it.take(8).reversed()] // throws TypeError; 121 | ``` 122 | 123 | Generators are treated as an infinite iterable. But you can override it by giving its length for the 2nd argument and the implentation of `nth` for the 3rd argument. see [.at()](#at) and [.map()](#map) for more example. 124 | 125 | ```javascript 126 | let it = new Xiterable(function *() { 127 | for (let i = 0; true; i++) yield i; 128 | }, Number.POSIVE_INFINITY, n => n); 129 | it.at(42); // 42 130 | it.take(42).reversed().at(0) // 41 131 | ``` 132 | 133 | A factory function is also exported as `xiterable`. 134 | 135 | ```javascript 136 | import {xiterable as $X} from 'js-xiterable'; 137 | $X('01234567').zip('abcdefgh').map(v=>v.join('')).toArray(); /* [ 138 | '0a', '1b', '2c', '3d', '4e', '5f', '6g', '7h' 139 | ] */ 140 | ``` 141 | 142 | ### Instance Methods and Properties 143 | 144 | #### `.toArray()` 145 | 146 | Returns `[...this]` unless `this` is infinite, in which case throws `RangeError`. It takes longer to spell than `[...this]` but slightly safer. 147 | 148 | #### `.at()` 149 | 150 | `.at(n)` returns the nth element of `this` if the original itertor has `.at` or `.nth` or Array-like (can access nth element via `[n]`. In which case `at()` is auto-generated). 151 | 152 | The function was previously named `nth()`, which is still an alias of `at()` for compatibility reason. 153 | 154 | Unlike `[n]`, `.at(n)` accepts `BigInt` and negatives. 155 | 156 | ```javascript 157 | let it = xiterable('javascript'); 158 | it.at(0); // 'j' 159 | it.at(0n); // 'j' 160 | it.at(9); // 't' 161 | it.at(-1); // 't' 162 | it.at(-1n); // 't' 163 | it.at(-10); // 'j' 164 | ``` 165 | 166 | It raises exceptions on infinite (and indefinite) iterables 167 | 168 | ```javascript 169 | it = xiterable(function*(){ for(;;) yield 42 }); // infinite 170 | [...it.take(42)]; // Array(42).fill(42) 171 | it.at(0); // throws TypeError; 172 | ``` 173 | 174 | ```javascript 175 | it = xiterable('javascript'); 176 | it = it.filter(v=>!new Set('aeiou').has(v)); // indefinite 177 | [...it]; // ['j', 'v', 's', 'c', 'r', 'p', 't'] 178 | it.at(0); // throws TypeError; 179 | ``` 180 | 181 | [js-combinatorics]: https://github.com/dankogai/js-combinatorics 182 | 183 | `BigInt` is sometimes necessary when you deal with large -- combinatorially large -- iterables like [js-combinatorics] handles. 184 | 185 | ```javascript 186 | import * as $C from 'js-combinatorics'; 187 | let it = xiterable(new $C.Permutation('abcdefghijklmnopqrstuvwxyz')); 188 | it = it.map(v=>v.join('')); 189 | it.at(0); // 'abcdefghijklmnopqrstuvwxyz' 190 | it.at(-1); // 'zyxwvutsrqponmlkjihgfedcba' 191 | it.at(403291461126605635583999999n) === it.at(-1); // true 192 | ``` 193 | 194 | #### `.length` 195 | 196 | The (maximum) number of elements in the iterable. For infinite (or indefinite iterables like the result of `.filter()`) `Number.POSITIVE_INFINITY` is set. 197 | 198 | ```javascript 199 | xrange().length; // Number.POSITIVE_INFINITY 200 | xrange().take(42).length // 42 201 | xrange().take(42).filter(v=>v%2).length; // Number.POSITIVE_INFINITY 202 | ``` 203 | 204 | The number can be `BigInt` for very large iterable. 205 | 206 | ```javascript 207 | it = xiterable(new $C.Permutation('abcdefghijklmnopqrstuvwxyz')); 208 | it.length; // 403291461126605635584000000n 209 | ``` 210 | 211 | You can tell if the iterable is infinite or indefinite via `.isEndless`. 212 | 213 | ```javascript 214 | xrange().isEndless; // true 215 | xrange().take(42).isEndless // false 216 | xrange().take(42).filter(v=>v%2).isEndless; // true 217 | ``` 218 | 219 | #### `.map()` 220 | 221 | `.map(fn, thisArg?)` works just like `Array.prototype.map` except: 222 | 223 | * `.map` of this module works with infinite iterables. 224 | 225 | * if `this` is finite with working `.nth`, the resulting iterable is also reversible with `.reversed` and random-accissible via `.nth`. 226 | 227 | ```javascript 228 | it = xiterable(function*(){ let i = 0; for(;;) yield i++ }); 229 | [...it.map(v=>v*v).take(8)] // [0, 1, 4, 9, 16, 25, 36, 49] 230 | it.at(42); // throws TypeError 231 | it = xiterable(it.seed, it.length, n=>n); // installs nth 232 | it.at(42); // 41 233 | ``` 234 | 235 | #### `.filter()` 236 | 237 | `.filter(fn, thisArg?)` works just like `Array.prototype.filter` except: 238 | 239 | * `.filter` of this module works with infinite iterables. 240 | 241 | * unlike [.map()](#map) the resulting iterable is always marked infinite because there is no way to know its length lazily, that is, prior to iteration. See [.length](#length) for more examples. 242 | 243 | ```javascript 244 | it = xiterable('javascript'); 245 | it.length; // 10 246 | it = it.filter(v=>!new Set('aeiou').has(v)); 247 | it.length; // Number.POSITIVE_INFINITY 248 | [...it].length; // 7 249 | [...it] // [ 'j', 'v', 's', 'c', 'r', 'p', 't' ] 250 | ``` 251 | 252 | #### `.mapFilter()` 253 | 254 | `.mapFilter(fn, thisArg?)` works just like `.filter()` but instead of dropping elements, it is replaced with `undefined`. That way the number of elements remains unchanged so you can use `.at()` and `.reversed()`. 255 | 256 | ```javascript 257 | it = xiterable('javascript'); 258 | it.length; // 10 259 | it = it.mapFilter(v=>!new Set('aeiou').has(v)); 260 | it.length; // 10 261 | [...it].length; // 10 262 | [...it]; /* [ 263 | 'j', undefined, 'v', undefined, 264 | 's', 'c', 'r', undefined, 'p', 't' 265 | ] */ 266 | ``` 267 | 268 | 269 | #### `.take()` 270 | 271 | `.take(n)` returns an iterable with the first `n` elements from `this`. 272 | If `n <= this.length` it is a no-op. 273 | 274 | ```javascript 275 | [...xrange().take(8)]; // [0, 1, 2, 3, 4, 5, 6, 7] 276 | [...xrange().take(4).take(8)]; // [0, 1, 2, 3] 277 | ``` 278 | 279 | #### `.drop()` 280 | 281 | `.drop(n)`returns an iterable without the first `n` elements from `this`. 282 | If `n <= this.length` an empty iterable is returned. 283 | 284 | ```javascript 285 | [...xrange(8).drop(4)]; // [4, 5, 6, 7] 286 | [...xrange(4).drop(8)]; // [] 287 | ``` 288 | 289 | Note the infinite iterable remains infinite even after you `.drop(n)` 290 | 291 | ```javascript 292 | xrange().drop(8).length; // Number.POSITIVE_INFINITY 293 | [...xrange().drop(8).take(4)]; // [ 8, 9, 10, 11 ] 294 | ``` 295 | 296 | #### `.takeWhile()` 297 | 298 | `.takeWhile(fn, thisArg?)` returns an iterable with which iterates till `fn` is no longer `true`. Similar to [.filter](#filter) but unlinke `.filter()` the iterator terminates at the first element where `fn()` returns false. 299 | 300 | ```javascript 301 | let it = xiterable('javascript'); 302 | let fn = v=>!new Set('aeiou').has(v); 303 | [...it.filter(fn)]; // ['j', 'v', 's', 'c', 'r', 'p', 't'] 304 | [...it.takeWhile(fn)]; // ['j'] 305 | ``` 306 | 307 | #### `.filled()` 308 | 309 | `.filled(value)` returns an iterator with all elements replaced with `value`. See also [Xiterable.repeat](#xiterablerepeat). 310 | 311 | #### `.reversed()` 312 | 313 | `.reversed()` returns an iterator that returns elements in reverse order. `this` must be finite and random-accesible via `.at()` or exception is thrown. 314 | 315 | ```javascript 316 | [...xrange().take(4).reversed()]; // [3, 2, 1, 0] 317 | [...xrange().reversed()]; // throws RangeError 318 | ``` 319 | 320 | #### `.zip()` 321 | 322 | `.zip(...args)` zips iterators in the `args`. Static version also [available](#Xiterablezip). 323 | 324 | ```javascript 325 | [...Xiterable.xrange().zip('abcd')] // [[0,"a"],[1,"b"],[2,"c"],[3,"d"]] 326 | ``` 327 | 328 | ### Instance methods found in `Array.prototype` 329 | 330 | The following methods in `Array.prototype` are supported as follows. For any method `meth`, `[...iter.meth(arg)]` deeply equals to `[...iter].meth(arg)`. 331 | 332 | | method | available? | Comment | 333 | |:--------------|:----:|:---------| 334 | |[concat] | ✔︎ | | 335 | |[copyWithin] | ❌ | mutating | 336 | |[entries] | ✔︎ | | 337 | |[every] | ✔︎ | | 338 | |[fill] | ❌ | mutating ; see [.filled](#filled) | 339 | |[filter] | ✔︎ | see [filter](#filter) | 340 | |[find] | ✔︎ | | 341 | |[findIndex] | ✔︎ | | 342 | |[flat] | ✔︎ | | 343 | |[flatMap] | ✔︎ | | 344 | |[forEach] | ✔︎ | | 345 | |[includes] | ✔︎*| * throws `RangeError` on infinite iterables if the 2nd arg is negative | 346 | |[indexOf] | ✔︎*| * throws `RangeError` on infinite iterables if the 2nd arg is negative | 347 | |[join] | ✔︎ | | 348 | |[keys] | ✔︎ | | 349 | |[lastIndexOf] | ✔︎*| * throws `RangeError` on infinite iterables if the 2nd arg is negative | 350 | |[map] | ✔︎ | see [map](#map) | 351 | |[pop] | ❌ | mutating | 352 | |[push] | ❌ | mutating | 353 | |[reduce] | ✔︎* | * throws `RangeError` on infinite iterables | 354 | |[reduceRight] | ✔︎* | * throws `RangeError` on infinite iterables | 355 | |[reverse] | ❌ | mutating. See [reversed](#reversed) | 356 | |[shift] | ❌ | mutating | 357 | |[slice] | ✔︎* | * throws `RangeError` on infinite iterables if any of the args is negative | 358 | |[some] | ✔︎ | | 359 | |[sort] | ❌ | mutating | 360 | |[splice] | ❌ | mutating | 361 | |[unshift] | ❌ | mutating | 362 | |[filter] | ✔︎ | | 363 | 364 | 365 | * Mutating functions (functions that change `this`) are deliberately made unavailable. e.g. `pop`, `push`… 366 | 367 | * Functions that need toiterate backwards do not work on infinite iterables. e.g. `lastIndexOf()`, `reduceRight()`… 368 | 369 | [concat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat 370 | [copyWithin]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin 371 | [entries]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/entries 372 | [every]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every 373 | [fill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill 374 | [filter]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter 375 | [find]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find 376 | [findIndex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex 377 | [flat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat 378 | [flatMap]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap 379 | [forEach]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach 380 | [includes]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes 381 | [indexOf]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf 382 | [join]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join 383 | [keys]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/keys 384 | [lastIndexOf]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf 385 | [map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 386 | [pop]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop 387 | [push]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push 388 | [reduce]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce 389 | [reduceRight]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduceRight 390 | [reverse]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse 391 | [shift]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift 392 | [slice]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice 393 | [some]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some 394 | [sort]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort 395 | [splice]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice 396 | [unshift]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift 397 | [values]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/values 398 | 399 | ### Static methods 400 | 401 | They are also exported so you can: 402 | 403 | ```javascript 404 | import {repeat,xrange,zip,zipWith} from 'js-xiterable' 405 | ``` 406 | Examples below assumes 407 | 408 | ```javascript 409 | import {Xiterable} from 'js-xiterable'. 410 | ``` 411 | 412 | Examples below assumes 413 | 414 | #### `Xiterable.zip` 415 | 416 | Zips iterators in the argument. 417 | 418 | ```javascript 419 | [...Xiterable.zip('0123', 'abcd')] // [[0,"a"],[1,"b"],[2,"c"],[3,"d"]] 420 | ``` 421 | 422 | #### `Xiterable.zipWith` 423 | 424 | Zips iterators and then feed it to the function. 425 | 426 | ```javascript 427 | [...Xiterable.zipWith((a,b)=>a+b, 'bcdfg', 'aeiou')] // ["ba","ce","di","fo","gu"] 428 | ``` 429 | 430 | #### `Xiterable.xrange` 431 | 432 | `xrange()` as Python 2 (or `range()` of Python 3). 433 | 434 | ```javascript 435 | for (const i of Xiterable.xrange()){ // infinite stream of 0, 1, ... 436 | console.log(i) 437 | } 438 | ``` 439 | 440 | ```javascript 441 | [...Xiterable.xrange(4)] // [0, 1, 2, 3] 442 | [...Xiterable.xrange(1,5)] // [1, 2, 3, 4] 443 | [...Xiterable.xrange(1,5,2)] // [1, 3] 444 | ``` 445 | 446 | #### `Xiterable.repeat` 447 | 448 | Returns an iterator with all elements are the same. 449 | 450 | ```javascript 451 | for (const i of Xiterable.repeat('spam')) { // infinite stream of 'spam' 452 | console.log(i) 453 | } 454 | ``` 455 | 456 | ```javascript 457 | [...Xiterable.repeat('spam', 4)] // ['spam', 'spam', 'spam', 'spam'] 458 | ``` 459 | 460 | ## See Also 461 | 462 | ### [tc39/proposal-iterator-helpers] 463 | 464 | [tc39/proposal-iterator-helpers]: https://github.com/tc39/proposal-iterator-helpers 465 | 466 | Looks like this is what standard iterators were supposed to be. 467 | 468 | #### Pro 469 | 470 | * It will be the part of the standard if it passes 471 | * lazy like this module 472 | * async version also available. 473 | 474 | ### Cons 475 | 476 | * sequencial access only. 477 | * no `.at()` 478 | * no `.reversed()` 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-xiterable", 3 | "version": "0.2.2", 4 | "description": "Make ES6 Iterators Functional Again", 5 | "type": "module", 6 | "main": "xiterable.js", 7 | "module": "xiterable.js", 8 | "types": "xiterable.d.ts", 9 | "files": [ 10 | "xiterable.js", 11 | "xiterable.d.ts" 12 | ], 13 | "runkitExample": "require=require(\"esm\")(module);\nvar $X=require(\"js-xiterable\");\n", 14 | "devDependencies": { 15 | "typescript": "^5.0.0", 16 | "@types/node": "^20.9.0", 17 | "mocha": "^10.0.0", 18 | "chai": "^4.2.0" 19 | }, 20 | "scripts": { 21 | "test": "make clean && make test" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/dankogai/js-xiterable.git" 26 | }, 27 | "keywords": [ 28 | "iterable", 29 | "iterator", 30 | "generator", 31 | "es2015", 32 | "es6" 33 | ], 34 | "author": "Dan Kogai", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/dankogai/js-xiterable/issues" 38 | }, 39 | "homepage": "https://github.com/dankogai/js-xiterable#readme" 40 | } 41 | -------------------------------------------------------------------------------- /test/00-chai.js: -------------------------------------------------------------------------------- 1 | import * as _chai from 'chai'; 2 | global.chai = _chai; 3 | -------------------------------------------------------------------------------- /test/01-import.js: -------------------------------------------------------------------------------- 1 | import * as _Module from '../xiterable.js'; 2 | describe('import', () => { 3 | for (const k in _Module) { 4 | const tn = k === 'version' ? 'string' : 'function'; 5 | it(`${k} is a ${tn}`, () => chai.expect(typeof _Module[k]).to.equal(tn)); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /test/02-basic.js: -------------------------------------------------------------------------------- 1 | import { Xiterable as _ } from '../xiterable.js'; 2 | const $ = chai.expect; 3 | 4 | describe('isIterable', () => { 5 | it('String', () => $(_.isIterable('')).to.equal(true)); 6 | it('Boolean', () => $(_.isIterable(false)).to.equal(false)); 7 | it('Number', () => $(_.isIterable(0)).to.equal(false)); 8 | it('Symbol', () => $(_.isIterable(Symbol())).to.equal(false)); 9 | if (typeof BigInt !== 'undefined') { 10 | let bzero; try { eval('bzero = 0n') } catch (err) { }; 11 | it('BigInt', () => $(_.isIterable(bzero)).to.equal(false)); 12 | } else { 13 | it.skip('BigInt missing', x => x); 14 | } 15 | it('null', () => $(_.isIterable(null)).to.equal(false)); 16 | it('undefined', () => $(_.isIterable(undefined)).to.equal(false)); 17 | it('Object', () => $(_.isIterable({})).to.equal(false)); 18 | it('Array', () => $(_.isIterable([])).to.equal(true)); 19 | it('Map', () => $(_.isIterable(new Map())).to.equal(true)); 20 | it('Set', () => $(_.isIterable(new Set())).to.equal(true)); 21 | it('Int8Array', () => $(_.isIterable(new Int8Array())).to.equal(true)); 22 | it('Uint8Array', () => $(_.isIterable(new Uint8Array())).to.equal(true)); 23 | it('Uint8ClampedArray', 24 | () => $(_.isIterable(new Uint8ClampedArray())).to.equal(true)); 25 | it('Int16Array', () => $(_.isIterable(new Int16Array())).to.equal(true)); 26 | it('Uint16Array', () => $(_.isIterable(new Uint16Array())).to.equal(true)); 27 | it('Int32Array', () => $(_.isIterable(new Int32Array())).to.equal(true)); 28 | it('Uint32Array', () => $(_.isIterable(new Uint32Array())).to.equal(true)); 29 | it('Float32Array', () => $(_.isIterable(new Float32Array())).to.equal(true)); 30 | it('Float64Array', () => $(_.isIterable(new Float64Array())).to.equal(true)); 31 | if (typeof BigInt64Array !== 'undefined') { 32 | it('BigInt64Array', 33 | () => $(_.isIterable(new BigInt64Array())).to.equal(true)); 34 | it('BigUint64Array', 35 | () => $(_.isIterable(new BigUint64Array())).to.equal(true)); 36 | } else { 37 | it.skip('BigInt64Array missing', x => x); 38 | it.skip('BigUint64Array missing', x => x); 39 | } 40 | ; 41 | }); 42 | -------------------------------------------------------------------------------- /test/03-array.js: -------------------------------------------------------------------------------- 1 | import { Xiterable, xiterable, xrange } from '../xiterable.js'; 2 | const $ = chai.expect; 3 | const should = chai.should; 4 | const xr4 = xrange(4); 5 | const ar4 = [...xr4]; 6 | describe('[...]', () => { 7 | it('[...xrange(4)] === [0,1,2,3]', () => 8 | $([...xrange(4)]).to.deep.equal([0, 1, 2, 3]) 9 | ); 10 | }); 11 | describe('.map()', () => { 12 | for (const f of [v => v * v, (v, i) => i * v]) { 13 | it(`[...xi.map(${f})] === [...xi].map(${f})`, () => 14 | $([...xr4.map(f)]).to.deep.equal([...xr4].map(f)) 15 | ); 16 | } 17 | }); 18 | describe('.entries(), .values(), .keys()', () => { 19 | it(`[...xi.entries()] === [...[...xi].entries()])`, () => 20 | $([...xr4.entries()]).to.deep.equal([...[...xr4].entries()]) 21 | ); 22 | it(`[...xi.values()] === [...[...xi].values()])`, () => 23 | $([...xr4.values()]).to.deep.equal([...[...xr4].values()]) 24 | ); 25 | it(`[...xi.keys()] === [...[...xi].keys()])`, () => 26 | $([...xr4.keys()]).to.deep.equal([...[...xr4].keys()]) 27 | ); 28 | }); 29 | describe('.filter()', () => { 30 | for (const f of [v => v % 2, (v, i) => v === i]) { 31 | it(`[...xi.filter(${f})] === [...filter].fiter(${f})`, () => 32 | $([...xr4.filter(f)]).to.deep.equal([...xr4].filter(f)) 33 | ); 34 | } 35 | }); 36 | describe('.indexOf(), lastIndexOf()', () => { 37 | let str = 'dankogai' 38 | let xi = xiterable(str); 39 | it(`xi.indexOf('a') === [...xi].indexOf('a')`, () => 40 | $(xi.indexOf('a')).to.equal([...xi].indexOf('a')) 41 | ); 42 | it(`xi.indexOf('A') === [...xi].indexOf('A')`, () => 43 | $(xi.indexOf('A')).to.equal([...xi].indexOf('A')) 44 | ); 45 | it(`xi.lastIndexOf('a') === [...xi].lastIndexOf('a')`, () => 46 | $(xi.lastIndexOf('a')).to.equal([...xi].lastIndexOf('a')) 47 | ); 48 | it(`xi.lastIndexOf('A') === [...xi].lastIndexOf('A')`, () => 49 | $(xi.lastIndexOf('A')).to.equal([...xi].lastIndexOf('A')) 50 | ); 51 | for (const i of xrange(-str.length, str.length)) { 52 | it(`xi.indexOf('a', ${i}) === [...xi].indexOf('a', ${i})`, () => 53 | $(xi.indexOf('a', i)).to.equal([...xi].indexOf('a', i)) 54 | ); 55 | it(`xi.lastIndexOf('a', ${i}) === [...xi].lastIndexOf('a', ${i})`, () => 56 | $(xi.lastIndexOf('a', i)).to.equal([...xi].lastIndexOf('a', i)) 57 | ); 58 | } 59 | }); 60 | describe('.reduce(), .reduceRight()', () => { 61 | const f = (a, v) => a + v; 62 | it(`xi.reduce(${f}) === [...xi].reduce(${f})`, () => 63 | $(xr4.reduce(f)).to.equal([...xr4].reduce(f)) 64 | ); 65 | it(`xi.reduce(${f}, '') === [...xi].reduce(${f}, '')`, () => 66 | $(xr4.reduce(f, '')).to.equal([...xr4].reduce(f, '')) 67 | ); 68 | it(`xi.reduceRight(${f}) === [...xi].reduceRight(${f})`, () => 69 | $(xr4.reduceRight(f)).to.equal([...xr4].reduceRight(f)) 70 | ); 71 | it(`xi.reduceRight(${f}, '') === [...xi].reduceRight(${f}, '')`, () => 72 | $(xr4.reduceRight(f, '')).to.equal([...xr4].reduceRight(f, '')) 73 | ); 74 | }); 75 | describe('.slice()', () => { 76 | const ary = [...xrange(-4, 4)]; 77 | for (const s of ary) { 78 | it(`[...xi.slice(${s})] === [...xi].slice(${s})`, () => 79 | $([...xr4.slice(s)]).to.deep.equal(ar4.slice(s)) 80 | ); 81 | for (const e of ary) { 82 | it(`[...xi.slice(${s},${e})] === [...xi].slice(${s},${e})`, () => 83 | $([...xr4.slice(s, e)]).to.deep.equal(ar4.slice(s, e)) 84 | ); 85 | } 86 | } 87 | }); -------------------------------------------------------------------------------- /test/04-fun.js: -------------------------------------------------------------------------------- 1 | import { Xiterable, xiterable, xrange, repeat, zip, zipWith } from '../xiterable.js'; 2 | const $ = chai.expect; 3 | const should = chai.should; 4 | describe('.reversed', () => { 5 | let xi = xrange().take(4).reversed(); 6 | let ar = [3, 2, 1, 0] 7 | it(`xrange().take(4).reversed() === ${JSON.stringify(ar)}`, () => 8 | $([...xi]).to.deep.equal(ar)) 9 | }); 10 | describe('zip, zipWith', () => { 11 | let xi = zip(xrange(), repeat('x')); 12 | let ar = Array(4).fill('x').map((v, i) => [i, v]); 13 | it(`[...zip(xrange(), repeat('x')).take(4)] 14 | === ${JSON.stringify(ar)}`, () => 15 | $([...xi.take(4)]).to.deep.equal(ar)) 16 | 17 | xi = zipWith((a, b) => a + b, xrange(), repeat('x')); 18 | ar = Array(4).fill('x').map((v, i) => i + v); 19 | it(`[...zipWith((a, b) => a + b, xrange(), repeat('x')).take(4)] 20 | === ${JSON.stringify(ar)}`, () => 21 | $([...xi.take(4)]).to.deep.equal(ar)) 22 | }); 23 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Mocha Tests of Xiterable 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /xiterable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * xiterable.ts 3 | * 4 | * @version: 0.2.2 5 | * @author: dankogai 6 | * 7 | */ 8 | export const version = '0.2.2'; 9 | // MARK: types 10 | type anyint = number | bigint; 11 | declare const BigInt: typeof Number; 12 | type anyfunction = (...any) => any; 13 | type transform = (T, anyint?, any?) => U; 14 | type predicate = (T, anyint?, any?) => boolean; 15 | type accumulate = (U, T, anyint?, any?) => U; 16 | type subscript = (anyint) => any; 17 | // MARK: Utility 18 | /** 19 | * `true` if `obj` is iterable. `false` otherwise. 20 | */ 21 | export function isIterable(obj) { 22 | if (typeof obj === 'string') return true; // string is iterable 23 | if (obj !== Object(obj)) return false; // other primitives 24 | return typeof obj[Symbol.iterator] === 'function'; 25 | } 26 | /** 27 | * `true` if `o` is an integer (Number with integral value or BigInt). 28 | */ 29 | export function isAnyInt(o) { 30 | return typeof o === 'number' ? Number.isInteger(o) : typeof o === 'bigint'; 31 | } 32 | function min(...args: anyint[]) { 33 | let result: anyint = Number.POSITIVE_INFINITY; 34 | for (const v of args) { 35 | if (v < result) result = v; 36 | } 37 | return result; 38 | } 39 | const atError = (n: anyint) => { 40 | throw TypeError('I do not know how to random access!'); 41 | } 42 | /** 43 | * main class 44 | */ 45 | export class Xiterable { 46 | seed: Iterable; 47 | length: anyint; 48 | at: (anyint) => T; 49 | static get version() { return version } 50 | static isIterable(obj) { return isIterable(obj) } 51 | /** 52 | * @constructor 53 | */ 54 | constructor( 55 | seed: Iterable | anyfunction, 56 | length: anyint = Number.POSITIVE_INFINITY, 57 | at?: subscript 58 | ) { 59 | if (seed instanceof Xiterable) { 60 | return seed; 61 | } 62 | if (!isIterable(seed)) { 63 | if (typeof seed !== 'function') { 64 | throw TypeError(`${seed} is neither iterable or a generator`); 65 | } 66 | // treat obj as a generator 67 | seed = { [Symbol.iterator]: seed }; 68 | } else if (!at) { 69 | if (typeof seed['at'] === 'function') { 70 | at = seed['at'].bind(seed); 71 | } else if (isAnyInt(seed['length'])) { 72 | const len = seed['length']; 73 | const ctor = len.constructor; 74 | at = (n: anyint) => seed[ctor(n < 0 ? len : 0) + ctor(n)]; 75 | } 76 | } 77 | if (isAnyInt(seed['length'])) length = seed['length']; 78 | if (!at) at = atError; 79 | Object.assign(this, { seed: seed, length: length, at: at, nth: at }); 80 | Object.freeze(this); 81 | } 82 | /* 83 | * 84 | */ 85 | make(...args) { 86 | return new (Function.prototype.bind.apply(this, [null, ...args])); 87 | } 88 | /** 89 | * `true` if this iterable is endless 90 | */ 91 | get isEndless() { 92 | return this.length === Number.POSITIVE_INFINITY; 93 | } 94 | /** 95 | * does `new` 96 | * @param {*} args 97 | * @returns {Xiterable} 98 | */ 99 | static of(...args) { 100 | return new (Function.prototype.bind.apply(this, [null].concat(args))); 101 | } 102 | /** 103 | * Same as `of` but takes a single array `arg` 104 | * 105 | * cf. https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible 106 | * @param {Array} arg 107 | * @returns {Xiterable} 108 | */ 109 | static from(arg) { 110 | return new (Function.prototype.bind.apply(this, [null].concat(arg))); 111 | } 112 | [Symbol.iterator]() { 113 | return this.seed[Symbol.iterator](); 114 | } 115 | toArray() { 116 | return [...this]; 117 | } 118 | /// MARK: methods found in Array.prototype //// 119 | /** 120 | * `map` as `Array.prototype.map` 121 | */ 122 | map(fn: transform, thisArg?): Xiterable { 123 | const ctor = this.length.constructor; 124 | const len = ctor(this.length); 125 | const iter = this.seed; 126 | const at = (n: anyint) => { 127 | n = ctor(n); 128 | if (n < 0) n += len; 129 | return n < 0 ? undefined 130 | : len <= n ? undefined 131 | : fn.call(thisArg, this.at(n), n, this.seed); 132 | } 133 | const gen = function* () { 134 | let i = ctor(0); 135 | for (const v of iter) { 136 | yield fn.call(thisArg, v, i++, iter); 137 | } 138 | }; 139 | return new Xiterable(gen, len, at); 140 | } 141 | /** 142 | * `forEach` as `Array.prototype.map` 143 | */ 144 | forEach(fn: transform, thisArg?) { 145 | let i = this.length.constructor(0); 146 | for (const v of this.seed) { 147 | fn.call(thisArg, v, i++, this.seed); 148 | } 149 | } 150 | /** 151 | * `entries` as `Array.prototype.entries` 152 | */ 153 | entries() { 154 | return this.map((v, i) => [i, v]); 155 | } 156 | /** 157 | * `keys` as `Array.prototype.keys` 158 | */ 159 | keys() { 160 | return this.map((v, i) => i); 161 | } 162 | /** 163 | * `values` as `Array.prototype.values` 164 | */ 165 | values() { 166 | return this.map((v, i) => v); 167 | } 168 | /** 169 | * like `filter` but instead of removing elements 170 | * that do not meet the predicate, it replaces them with `undefined`. 171 | */ 172 | mapFilter(fn: predicate, thisArg?): Xiterable { 173 | return this.map((v, i) => fn.call(thisArg, v, i, this.seed) 174 | ? v : undefined) 175 | } 176 | /** 177 | * `filter` as `Array.prototype.filter` 178 | */ 179 | filter(fn: predicate, thisArg?): Xiterable { 180 | const iter = this.seed; 181 | const ctor = this.length.constructor; 182 | const gen = function* () { 183 | let i = ctor(0); 184 | for (const v of iter) { 185 | if (fn.call(thisArg, v, i++, iter)) yield v; 186 | } 187 | }; 188 | return new Xiterable(gen); 189 | } 190 | /** 191 | * `find` as `Array.prototype.find` 192 | */ 193 | find(fn: predicate, thisArg?) { 194 | let i = this.length.constructor(0); 195 | for (const v of this.seed) { 196 | if (fn.call(thisArg, v, i++, this.seed)) return v; 197 | } 198 | } 199 | /** 200 | * `findIndex` as `Array.prototype.find` 201 | */ 202 | findIndex(fn: predicate, thisArg?): anyint { 203 | let i = this.length.constructor(0); 204 | for (const v of this.seed) { 205 | if (fn.call(thisArg, v, i++, this.seed)) return --i; 206 | } 207 | return -1; 208 | } 209 | /** 210 | * `indexOf` as `Array.prototype.indexOf` 211 | */ 212 | indexOf(valueToFind, fromIndex: anyint = 0): anyint { 213 | const ctor = this.length.constructor; 214 | const len = ctor(this.length); 215 | fromIndex = ctor(fromIndex); 216 | if (fromIndex < 0) { 217 | if (this.isEndless) { 218 | throw new RangeError('an infinite iterable cannot go backwards'); 219 | } 220 | fromIndex += len; 221 | if (fromIndex < 0) fromIndex = 0; 222 | } 223 | return this.entries().findIndex( 224 | v => fromIndex <= v[0] && Object.is(v[1], valueToFind) 225 | ); 226 | } 227 | /** 228 | * `lastIndexOf` as `Array.prototype.lastIndexOf` 229 | */ 230 | lastIndexOf(valueToFind, fromIndex?: anyint): anyint { 231 | if (this.isEndless) { 232 | throw new RangeError('an infinite iterable cannot go backwards'); 233 | } 234 | const ctor = this.length.constructor; 235 | const len = ctor(this.length); 236 | if (arguments.length > 1) fromIndex = ctor(fromIndex); 237 | if (typeof fromIndex === 'undefined') fromIndex = len - ctor(1); 238 | if (fromIndex < 0) { 239 | fromIndex += len; 240 | if (fromIndex < 0) fromIndex = 0; 241 | } 242 | fromIndex = len - ctor(1) - ctor(fromIndex); 243 | const idx = this.reversed().indexOf(valueToFind, fromIndex); 244 | if (idx === -1) return -1; 245 | return len - ctor(idx) - ctor(1); 246 | } 247 | /** 248 | * `includes` as `Array.prototype.includes` 249 | */ 250 | includes(valueToFind: T, fromIndex: anyint = 0): boolean { 251 | return this.indexOf(valueToFind, fromIndex) > -1; 252 | } 253 | /** 254 | * `reduce` as `Array.prototype.reduce` 255 | */ 256 | reduce(fn: accumulate, initialValue?: U) { 257 | if (this.isEndless) { 258 | throw new RangeError('an infinite iterable cannot be reduced'); 259 | } 260 | if (arguments.length == 1 && Number(this.length) === 0) { 261 | throw new TypeError('Reduce of empty iterable with no initial value') 262 | } 263 | let a = initialValue, i = 0, it = this.seed; 264 | for (const v of it) { 265 | a = arguments.length == 1 && i == 0 ? v : fn(a, v, i, it); 266 | i++; 267 | } 268 | return a; 269 | } 270 | /** 271 | * `reduceRight` as `Array.prototype.reduceRight` 272 | */ 273 | reduceRight(fn: accumulate, initialValue?: U): U { 274 | let it = this.reversed() 275 | return it.reduce.apply(it, arguments); 276 | } 277 | /** 278 | * `flat` as `Array.prototype.flat` 279 | */ 280 | flat(depth = 1) { 281 | function* _flatten(iter, depth) { 282 | for (const it of iter) { 283 | if (isIterable(it) && depth > 0) { 284 | yield* _flatten(it, depth - 1); 285 | } else { 286 | yield it; 287 | } 288 | } 289 | } 290 | return new Xiterable(() => _flatten(this, depth)); 291 | } 292 | /** 293 | * `flatMap` as `Array.prototype.flatMap` 294 | */ 295 | flatMap(fn: transform, thisArg?) { 296 | return this.map(fn, thisArg).flat(); 297 | } 298 | /** 299 | * `join` as `Array.prototype.join` 300 | * @param {String} separator 301 | * @returns {String} 302 | */ 303 | join(separator: string = ','): string | undefined { 304 | return this.reduce((a, v) => String(a) + separator + String(v)); 305 | } 306 | /** 307 | * `every` as `Array.prototype.every` 308 | */ 309 | every(fn: predicate, thisArg = null) { 310 | let i = this.length.constructor(0); 311 | for (const v of this.seed) { 312 | if (!fn.call(thisArg, v, i++, this.seed)) return false; 313 | } 314 | return true; 315 | 316 | } 317 | /** 318 | * `some` as `Array.prototype.some` 319 | */ 320 | some(fn: predicate, thisArg = null) { 321 | let i = this.length.constructor(0); 322 | for (const v of this.seed) { 323 | if (fn.call(thisArg, v, i++, this.seed)) return true; 324 | } 325 | return false; 326 | } 327 | /** 328 | * `concat` as `Array.prototype.concat` 329 | */ 330 | concat(...args) { 331 | const head = this.seed; 332 | const rest = args.map(v => (Object(v) === v ? v : [v])); 333 | const gen = function* () { 334 | for (const v of head) yield v; 335 | for (const it of rest) { 336 | for (const v of it) yield v; 337 | } 338 | }; 339 | return new Xiterable(gen); 340 | } 341 | /** 342 | * `slice` as `Array.prototype.slice` 343 | */ 344 | slice(start: anyint = 0, end: anyint = Number.POSITIVE_INFINITY): Xiterable { 345 | // return this.drop(start).take(end - start); 346 | const ctor = this.length.constructor; 347 | if (start < 0 || end < 0) { 348 | if (this.isEndless) { 349 | throw new RangeError('negative indexes cannot be used') 350 | } 351 | if (start < 0) start = ctor(this.length) + ctor(start); 352 | if (end < 0) end = ctor(this.length) + ctor(end); 353 | } 354 | if (end <= start) return new Xiterable([]); 355 | let len = end === Number.POSITIVE_INFINITY 356 | ? ctor(this.length) - ctor(start) 357 | : ctor(end) - ctor(start); 358 | if (len < 0) len = ctor(0); 359 | const at = (i) => { 360 | if (i < 0) i += len; 361 | return 0 <= i && i < len ? this.at(start + i) : undefined; 362 | } 363 | const iter = this.seed; 364 | const gen = function* () { 365 | let i = ctor(-1); 366 | for (const v of iter) { 367 | ++i; 368 | if (i < start) continue; 369 | if (end <= i) break; 370 | yield v; 371 | } 372 | }; 373 | return new Xiterable(gen, len, at); 374 | } 375 | //// MARK: functional methods not defined above 376 | /** 377 | * returns an iterable with the first `n` elements from `this`. 378 | */ 379 | take(n: anyint): Xiterable { 380 | const ctor = this.length.constructor; 381 | let len = ctor(n); 382 | if (ctor(this.length) < len) len = ctor(this.length); 383 | const at = (i) => { 384 | if (i < 0) i += len; 385 | return 0 <= i && i < len ? this.at(i) : undefined; 386 | }; 387 | const iter = this.seed; 388 | const gen = function* () { 389 | let i = ctor(0), nn = ctor(n); 390 | for (const v of iter) { 391 | if (nn <= i++) break; 392 | yield v; 393 | } 394 | }; 395 | return new Xiterable(gen, len, at); 396 | } 397 | /** 398 | * returns an iterable without the first `n` elements from `this` 399 | */ 400 | drop(n: anyint): Xiterable { 401 | const ctor = this.length.constructor; 402 | let len = ctor(this.length) - ctor(n); 403 | if (len < 0) len = ctor(0); 404 | const at = (i) => { 405 | if (i < 0) i += len; 406 | return 0 <= i && i < len ? this.at(n + i) : undefined; 407 | } 408 | const iter = this.seed; 409 | const gen = function* () { 410 | let i = ctor(0), nn = ctor(n); 411 | for (const v of iter) { 412 | if (i++ < nn) continue; 413 | yield v; 414 | } 415 | } 416 | return new Xiterable(gen, len, at); 417 | } 418 | /** 419 | * returns an iterable with which iterates `this` till `fn` is no longer `true`. 420 | */ 421 | takeWhile(fn: predicate, thisArg?): Xiterable { 422 | const iter = this.seed; 423 | const ctor = this.length.constructor; 424 | const gen = function* () { 425 | let i = ctor(0); 426 | for (const v of iter) { 427 | if (!fn.call(thisArg, v, i++, iter)) break; 428 | yield v; 429 | } 430 | }; 431 | return new Xiterable(gen); 432 | } 433 | /** 434 | * returns an iterable with all elements replaced with `value` 435 | */ 436 | filled(value: U) { 437 | return this.map(() => value) 438 | } 439 | /** 440 | * reverse the iterable. `this` must be finite and random accessible. 441 | */ 442 | reversed(): Xiterable { 443 | if (this.isEndless || this.at === atError) { 444 | throw new RangeError('cannot reverse an infinite iterable'); 445 | } 446 | let len = this.length; 447 | const ctor = len.constructor; 448 | const at = (n) => { 449 | const i = ctor(n) + ctor(n < 0 ? len : 0); 450 | return 0 <= i && i < len 451 | ? this.at(ctor(len) - i - ctor(1)) : undefined; 452 | }; 453 | const iter = this; 454 | const gen = function* () { 455 | let i = len; 456 | while (0 < i) yield iter.at(--i); 457 | }; 458 | return new Xiterable(gen, len, at); 459 | } 460 | /** 461 | * @returns {Xiterable} 462 | */ 463 | zip(...args: Iterable[]) { 464 | return Xiterable.zip(this, ...args); 465 | } 466 | //// MARK: static methods 467 | /** 468 | * @returns {Xiterable} 469 | */ 470 | static zip(...args: Iterable[]) { 471 | const xargs = args.map(v => new Xiterable(v)); 472 | const len = min(...xargs.map(v => v.length)) 473 | const ctor = this.length.constructor; 474 | const at = (n: anyint) => { 475 | if (n < 0) n = ctor(n) + ctor(len); 476 | if (n < 0 || len <= n) return undefined; 477 | let result: Iterable[] = []; 478 | for (const x of xargs) { 479 | result.push(x.at(n)) 480 | } 481 | return result; 482 | }; 483 | const gen = function* () { 484 | const them = xargs.map(v => v[Symbol.iterator]()); 485 | while (true) { 486 | let elem: Iterable[] = [] 487 | for (const it of them) { 488 | const nx = it.next(); 489 | if (nx.done) return; 490 | elem.push(nx.value); 491 | } 492 | yield elem; 493 | } 494 | }; 495 | return new Xiterable(gen, len, at); 496 | } 497 | /** 498 | * @returns {Xiterable} 499 | */ 500 | static zipWith(fn: anyfunction, ...args) { 501 | if (typeof fn !== 'function') throw TypeError( 502 | `${fn} is not a function.` 503 | ); 504 | return Xiterable.zip(...args).map(a => fn.apply(null, a)); 505 | } 506 | /** 507 | * `xrange` like `xrange()` of Python 2 (or `range()` of Python 3) 508 | */ 509 | static xrange(b: anyint, e: anyint, d: anyint) { 510 | if (typeof b === 'undefined') [b, e, d] = [0, Number.POSITIVE_INFINITY, 1] 511 | if (typeof e === 'undefined') [b, e, d] = [0, b, 1] 512 | if (typeof d === 'undefined') [b, e, d] = [b, e, 1] 513 | const ctor = b.constructor; 514 | const len = typeof b === 'bigint' 515 | ? (BigInt(e) - BigInt(b)) / BigInt(d) 516 | : Math.floor((Number(e) - Number(b)) / Number(d)); 517 | const at = (n: anyint) => { 518 | n = ctor(n); 519 | if (n < 0) n += ctor(len); 520 | return n < 0 ? undefined 521 | : len <= n ? undefined 522 | : ctor(b) + ctor(d) * ctor(n); 523 | } 524 | const gen = function* () { 525 | for (let i = b; i < e; i += ctor(d)) yield i; 526 | }; 527 | return new Xiterable(gen, len, at); 528 | } 529 | /** 530 | */ 531 | static repeat(value, times = Number.POSITIVE_INFINITY) { 532 | const at = (n) => n < 0 ? undefined : value; 533 | const gen = function* () { 534 | for (let i = 0; i < times; i++) yield value; 535 | } 536 | return new Xiterable(gen, times, at); 537 | } 538 | }; 539 | export const xiterable = Xiterable.of.bind(Xiterable); 540 | export const zip = Xiterable.zip.bind(Xiterable); 541 | export const zipWith = Xiterable.zipWith.bind(Xiterable); 542 | export const xrange = Xiterable.xrange.bind(Xiterable); 543 | export const repeat = Xiterable.repeat.bind(Xiterable); 544 | --------------------------------------------------------------------------------