Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jungomi/iterfn
Functions to work with iterators and iterables
https://github.com/jungomi/iterfn
generator iterable iterator javascript
Last synced: 2 days ago
JSON representation
Functions to work with iterators and iterables
- Host: GitHub
- URL: https://github.com/jungomi/iterfn
- Owner: jungomi
- License: mit
- Created: 2017-02-28T23:47:47.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2017-04-08T16:10:38.000Z (over 7 years ago)
- Last Synced: 2024-10-14T09:18:18.479Z (about 1 month ago)
- Topics: generator, iterable, iterator, javascript
- Language: JavaScript
- Homepage:
- Size: 1.27 MB
- Stars: 1
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# iterfn
[![Build Status][travis-badge]][travis]
[![npm][npm-badge]][npm-link][Documentation][docs]
A collection of functions to work with iterators similar to Rust's [Iterator
trait][iter-trait].Iterators are very useful to work with collections and it's worth knowing that
not everything needs to be an array. A very good and detailed explanation of
iterators and iterables can be found in
[Chapter 21. Iterables and iterators][es-iter] of the book
[Exploring ES6][exploringjs].## Motivation
In JavaScript the most well known iterables are arrays. Whenever you want to use
methods of [Array.prototype][array-prototype] like `map` or `filter`, you'd
usually create an array from your data type. For instance a [Set][set] object is
iterable, so you would expect it to have the same functions as array. Sadly
that's not the case. This library makes it very convenient to work with
iterables and iterators.Another great aspect of iterators is their laziness. This makes it possible to
use infinite iterators and also allows you to chain multiple methods without
having to evaluate all values before reaching the next step in the chain. This
also means that you won't allocate a new array after each transformation.## Usage
```javascript
import iter from 'iterfn';const sum = iter([1, 2, 3, 4]).sum(); // 10
const doubledIter = iter([1, 2, 3]).map(x => x * 2);
// Iterator needs to be consumed
doubledIter.next(); // { value: 2, done: false }
doubledIter.next(); // { value: 4, done: false }
doubledIter.next(); // { value: 6, done: false }
doubledIter.next(); // { value: undefined, done: true }const composedIter = iter([1, 2, 3, 4, 5, 6])
.filter(x => x % 2 === 0)
.skip(2)
.chain([7, 8])
.reverse();
for (const val of composedIter) {
console.log(val);
}
// Output:
// 8
// 7
// 6// Any iterable works
const map = new Map();
map.set(1, 1);
map.set(5, 8);
// Sum keys and values of a Map
const sum = iter(map).fold(0, (acc, [key, value]) => key + value); // 15// Generator functions
function* generator() {
yield 1;
yield 12;
yield 5;
}
// Calling the generator function creates an iterable iterator
const count = iter(generator()).count(); // 3
// iter also accepts a generator function directly
const lastSquare = iter(generator).map(x => x * x).last(); // 25
```An object can be an iterator or an iterable or both at the same time. `iter`
will create an iterable iterator whenever possible, that is an iterator that is
also iterable, so you don't have to worry about distinguishing them.### Using functions directly
It is also possible to directly use the functions that are present on the
extended iterators. This removes any overhead of extending the iterators but
requires the user to supply a correct iterable. This also makes chaining more
cumbersome.```javascript
import { map, filter, sum } from 'iterfn';const regularSum = sum([1, 2, 3, 4]); // 10
const sumSquaredEven = sum(map(filter([1, 2, 3, 4], x => x % 2 === 0), x => x * x)); // 20// Step by step
const evenNumbers = filter([1, 2, 3, 4], x => x % 2 === 0); // 2, 4
const squared = map(evenNumbers, x => x * x); // 4, 16
const finalSum = sum(squared); // 20
```### Infinite iterators
It is possible to operate on infinite iterators, but be aware that consumers
require to fully traverse the iterator unless they are short-circuiting. Also
the adapter `reverse()` requires to fully consume an iterator as well. The
following examples show how you can operate on infinite iterators, which would
not be possible by just creating an array from an iterator. On the other hand
`cycle()` creates an infinite iterator.```javascript
import iter from 'iterfn';function* positiveIntegers() {
let i = 1;
while (true) {
yield i;
i += 1;
}
}const sumFirstTen = iter(positiveIntegers).take(10).sum(); // 45
// First number that is divisible by 2 and 11
const divisibleByTwoAndEleven = iter(positiveIntegers)
.find(x => x % 2 === 0 && x % 11 === 0); // 22// Use the composed iterator directly
const multiplesOfThree = iter(positiveIntegers).map(x => x * 3);
multiplesOfThree.next(); // { value: 3, done: false }
multiplesOfThree.next(); // { value: 6, done: false }const smallSquares = iter(positiveIntegers)
.map(x => x * x)
.takeWhile(x => x < 50)
.collect(); // [1, 4, 9, 16, 25, 36, 49]
```### Adapters
An adapter is a function which takes an iterator and returns another iterator.
Two of the most common adapters are `map()` and `filter()`. In JavaScript these
functions are available for arrays, which return a new array and therefore it is
possible to chain them. All the adapters can be chained as well.```javascript
import iter from 'iterfn';const evenNumbers = iter([1, 2, 3, 4]).filter(x => x % 2 === 0);
const doubled = iter([1, 2, 3, 4]).map(x => x * 2);// Advancing the iterator with next()
evenNumbers.next(); // { value: 2, done: false }
evenNumbers.next(); // { value: 4, done: false }
evenNumbers.next(); // { value: undefined, done: true }// Using in a for-of loop
for (const num of doubled) {
console.log(num);
}// An iterator which will yield the values 4, 8 and 12
const doubledEven = iter([1, 2, 3, 4, 5, 6])
.filter(x => x % 2 === 0)
.map(x => x * 2);```
#### Difference to Array
A notable difference to using arrays, is that iterator adapters create a new
iterator without consuming the values of the composed iterators. This means that
any side effects in the functions will appear for any value at once when the
iterator is being consumed instead of per adapter function. Although such
functions should usually not have any side effects, it is still important to
know the evaluation order.```javascript
import iter from 'iterfn';const doubledEven = iter([1, 2, 3])
.filter(x => {
console.log(`Iter - filtering ${x}`);
return x % 2 === 0;
})
.map(x => {
console.log(`Iter - mapping ${x}`);
return x * 2;
});// Consuming the iterator
for (const x of doubledEven) {
console.log(`Iter - result ${x}`);
}const array = [1, 2, 3]
.filter(x => {
console.log(`Array - filtering ${x}`);
return x % 2 === 0;
})
.map(x => {
console.log(`Array - mapping ${x}`);
return x * 2;
});for (const x of array) {
console.log(`Array - result ${x}`);
}
```Output:
```
Iter - filtering 1
Iter - filtering 2
Iter - mapping 2
Iter - result 4
Iter - filtering 3
Array - filtering 1
Array - filtering 2
Array - filtering 3
Array - mapping 2
Array - result 4
```***The above code is for illustration purposes, if you would like to debug the
iterator steps you should use inspect() instead of modifying the map or filter
functions directly***.```javascript
const doubledEvenDebug = iter([1, 2, 3])
.inspect(x => console.log(`Iter - about to filter ${x}`))
.filter(x => x % 2 === 0)
.inspect(x => console.log(`Iter - about to map ${x}`))
.map(x => x * 2);
```#### Advancing composed iterators
An iterator can only be used once and advancing a composed iterator also
advances the underlying iterators. It is also possible to advance the underlying
iterators directly, without triggering the composed one. Keep in mind that
arrays are iterable but not iterators, where `[Symbol.iterator]()` creates an
iterator from the array.```javascript
import iter from 'iterfn';const arr = [1, 2, 3, 4];
const arrIter = arr[Symbol.iterator]();
const doubledIter = iter(arrIter).map(x => x * 2);doubledIter.next(); // { value: 2, done: false }
doubledIter.next(); // { value: 4, done: false }
arrIter.next(); // { value: 3, done: false }
doubledIter.next(); // { value: 8, done: false }
doubledIter.next(); // { value: undefined, done: true }
arrIter.next(); // { value: undefined, done: true }
```### Consumers
A consumer is a function which takes an iterator and consumes it to return
a value. They are the last method called in a chain of iterators. The most
common consumer for arrays is `reduce()` which for instance can be used to sum
all values.```javascript
import iter from 'iterfn';const sumReduce = iter([1, 2, 3, 4]).reduce((acc, x) => acc + x); // 10
const sum = iter([1, 2, 3, 4]).sum(); // 10
const sumEven = iter([1, 2, 3, 4]).filter(x => x % 2 === 0).sum(); // 6
const max = iter([9, 2, 13, 4]).max(); // 13
```Some consumers seem rather useless for arrays, but are very nice to have for
other iterables, that don't have properties like `length`.```javascript
import iter from 'iterfn';function* numbers() {
yield 1;
yield 12;
yield 5;
}const length = iter(numbers).count(); // 3
const second = iter(numbers).nth(1); // 12, index starts at 0
const sum = iter(numbers).sum(); // 18
const last = iter(numbers).last(); // 5
```#### Collect
`collect()` is a special consumer which creates a collection from an iterator.
By default it creates an array, but it accepts a function that will be used to
create such a collection. An array can easily be created with the spread
operator, without using this function. But it can definitely improve the
readability of a long iterator chain.```javascript
import iter from 'iterfn';function* helloWorld() {
yield 'hello';
yield ' ';
yield 'world';
}const array = iter(helloWorld).collect(); // ['hello', ' ', 'world']
function concatStr(iter) {
let str = '';
for (const val of iter) {
str += val;
}
return str;
}
const string = iter(helloWorld).collect(concatStr); // 'hello world'const longChain = iter(helloWorld)
.map(x => x.length)
.filter(x => x === 5)
.chain([2, 4])
.reverse()
.collect(); // [4, 2, 5, 5]// Not immediately obvious that it creates an array with the iterators values,
// especially when just seeing the end, even without using complex blocks.
const longSpread = [
...iter(helloWorld)
.map(x => x.length)
.filter(x => x === 5)
.chain([2, 4])
.reverse()
]; // [4, 2, 5, 5]
```[array-prototype]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype
[docs]: https://jungomi.github.io/iterfn/
[es-iter]: http://exploringjs.com/es6/ch_iteration.html
[exploringjs]: http://exploringjs.com/es6.html
[iter-trait]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
[npm-badge]: https://img.shields.io/npm/v/iterfn.svg?style=flat-square
[npm-link]: https://www.npmjs.com/package/iterfn
[set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
[travis]: https://travis-ci.org/jungomi/iterfn
[travis-badge]: https://img.shields.io/travis/jungomi/iterfn/master.svg?style=flat-square