├── .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 | [](http://www.ecma-international.org/ecma-262/6.0/) 2 | [](LICENSE) 3 | [](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 |