1 | # iterare
|
2 |
|
3 | > lat. _to repeat, to iterate_
|
4 |
|
5 | [![npm](https://img.shields.io/npm/v/iterare.svg)](https://www.npmjs.com/package/iterare)
|
6 | [![downloads](https://img.shields.io/npm/dt/iterare.svg)](https://www.npmjs.com/package/iterare)
|
7 | [![build](https://travis-ci.org/felixfbecker/iterare.svg?branch=master)](https://travis-ci.org/felixfbecker/iterare)
|
8 | [![codecov](https://codecov.io/gh/felixfbecker/iterare/branch/master/graph/badge.svg)](https://codecov.io/gh/felixfbecker/iterare)
|
9 | [![dependencies](https://david-dm.org/felixfbecker/iterare/status.svg)](https://david-dm.org/felixfbecker/iterare)
|
10 | ![node](http://img.shields.io/node/v/iterare.svg)
|
11 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
|
12 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
|
13 | [![license](https://img.shields.io/npm/l/iterare.svg)](https://github.com/felixfbecker/iterare/blob/master/LICENSE.txt)
|
14 | [![chat: on gitter](https://badges.gitter.im/felixfbecker/iterare.svg)](https://gitter.im/felixfbecker/iterare?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
15 |
|
16 | ES6 Iterator library for applying multiple transformations to a collection in a single iteration.
|
17 |
|
18 | ## [API Documentation](http://iterare.surge.sh/)
|
19 |
|
20 | ## Motivation
|
21 |
|
22 | Ever wanted to iterate over ES6 collections like `Map` or `Set` with `Array`-built-ins like `map()`, `filter()`, `reduce()`?
|
23 | Lets say you have a large `Set` of URIs and want to get a `Set` back that contains file paths from all `file://` URIs.
|
24 |
|
25 | The loop solution is very clumsy and not very functional:
|
26 |
|
27 | ```javascript
|
28 | const uris = new Set(['file:///foo.txt', 'http:///npmjs.com', 'file:///bar/baz.txt'])
|
29 | const paths = new Set()
|
30 | for (const uri of uris) {
|
31 | if (!uri.startsWith('file://')) {
|
32 | continue
|
33 | }
|
34 | const path = uri.substr('file:///'.length)
|
35 | paths.add(path)
|
36 | }
|
37 | ```
|
38 |
|
39 | Much more readable is converting the `Set` to an array, using its methods and then converting back:
|
40 |
|
41 | ```javascript
|
42 | new Set(
|
43 | Array.from(uris)
|
44 | .filter(uri => uri.startsWith('file://'))
|
45 | .map(uri => uri.substr('file:///'.length))
|
46 | )
|
47 | ```
|
48 |
|
49 | But there is a problem: Instead of iterating once, you iterate 4 times (one time for converting, one time for filtering, one time for mapping, one time for converting back).
|
50 | For a large Set with thousands of elements, this has significant overhead.
|
51 |
|
52 | Other libraries like RxJS or plain NodeJS streams would support these kinds of "pipelines" without multiple iterations, but they work only asynchronously.
|
53 |
|
54 | With this library you can use many methods you know and love from `Array` and lodash while only iterating once - thanks to the ES6 [iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols):
|
55 |
|
56 | ```javascript
|
57 | import iterate from 'iterare'
|
58 |
|
59 | iterate(uris)
|
60 | .filter(uri => uri.startsWith('file://'))
|
61 | .map(uri => uri.substr('file:///'.length))
|
62 | .toSet()
|
63 | ```
|
64 |
|
65 | `iterate` accepts any kind of Iterator or Iterable (arrays, collections, generators, ...) and returns a new Iterator object that can be passed to any Iterable-accepting function (collection constructors, `Array.from()`, `for of`, ...).
|
66 | Only when you call a method like `toSet()`, `reduce()` or pass it to a `for of` loop will each value get pulled through the pipeline, and only once.
|
67 |
|
68 | This library is essentially
|
69 |
|
70 | - RxJS, but fully synchronous
|
71 | - lodash, but with first-class support for ES6 collections.
|
72 |
|
73 | ## Performance
|
74 |
|
75 | Benchmarks based on the examples above:
|
76 |
|
77 | ### [`map` + `filter`](https://github.com/felixfbecker/iterare/blob/master/src/benchmarks/map_filter_set.ts)
|
78 |
|
79 | Simulate iterating over a very lage Set of strings and applying a filter and a map on it.
|
80 |
|
81 | | Method | ops/sec |
|
82 | | ------------------ | -----------------------------------: |
|
83 | | Loop | 466 ops/sec ±1.31% (84 runs sampled) |
|
84 | | **iterare** | 397 ops/sec ±2.01% (81 runs sampled) |
|
85 | | RxJS | 339 ops/sec ±0.77% (83 runs sampled) |
|
86 | | Array method chain | 257 ops/sec ±1.73% (79 runs sampled) |
|
87 | | Lodash | 268 ops/sec ±0.84% (81 runs sampled) |
|
88 | | IxJS (ES6) | 216 ops/sec ±0.81% (81 runs sampled) |
|
89 | | IxJS (ES5) | 141 ops/sec ±0.87% (77 runs sampled) |
|
90 |
|
91 | ### [`filter` + `take`](https://github.com/felixfbecker/iterare/blob/master/src/benchmarks/filter_take_set.ts)
|
92 |
|
93 | Simulate iterating over a very lage Set of strings and applying a filter on it, then taking only the first 1000 elements.
|
94 | A smart implementations should only apply the filter predicate to the first 5 elements.
|
95 |
|
96 | | Method | ops/sec |
|
97 | | ------------------ | -----------------------------------------: |
|
98 | | Loop | 3,059,466 ops/sec ±0.75% (88 runs sampled) |
|
99 | | **iterare** | 963,257 ops/sec ±0.68% (89 runs sampled) |
|
100 | | IxJS (ES6) | 424,488 ops/sec ±0.63% (89 runs sampled) |
|
101 | | RxJS | 168,853 ops/sec ±2.58% (86 runs sampled) |
|
102 | | IxJS (ES5) | 107,961 ops/sec ±1.88% (78 runs sampled) |
|
103 | | Lodash | 41.71 ops/sec ±1.15% (54 runs sampled) |
|
104 | | Array method chain | 24.74 ops/sec ±3.69% (45 runs sampled) |
|
105 |
|
106 | ## Lazy Evaluation
|
107 |
|
108 | Going a step further, if you only care about a specific number of elements in the end, only these elements will run through the pipeline:
|
109 |
|
110 | ```javascript
|
111 | iterate(collection)
|
112 | .filter(uri => uri.startsWith('file://'))
|
113 | .take(5)
|
114 | ```
|
115 |
|
116 | In this example, the filter predicate is called only until 5 elements have been found.
|
117 | The alternative with an array would call it for every element in the collection:
|
118 |
|
119 | ```javascript
|
120 | Array.from(collection)
|
121 | .filter(uri => uri.startsWith('file://'))
|
122 | .slice(0, 5)
|
123 | ```
|
124 |
|
125 | ## Contributing
|
126 |
|
127 | The source is written in TypeScript.
|
128 |
|
129 | - `npm run build` compiles TS
|
130 | - `npm run watch` compiles on file changes
|
131 | - `npm test` runs tests
|
132 | - `node lib/benchmarks/____` runs a benchmark
|