Small library of iterator-related utility functions for JavaScript.
Fully annotated source: GitHub Pages.
Ittr doesn't try to be a comprehensive hub of all array methods -- there are lots of NPM libraries will do that very well. This is just a smaller collection of just the essential (by my definition of essential) array/iterator methods I've learned comes up very, very often in UI development, including many that aren't a part of the JavaScript standard.
- Works with any JavaScript iterator, not just arrays
- Nice API
- Tiny, simple to use
- Relatively fast, compared to native JavaScript array methods
Import it with a <script> tag...
<script src="https://unpkg.com/ittr/dist/index.min.js"></script>... and you'll find all the API functions exposed under the Ittr global object.
You can also npm install ittr, which will allow you to use it in both Node.js and client-side projects.
Please see the documentation for detailed API information.
You can build step with npm build, and run unit tests in test/tests.js with npm test.
ittr works with iterables in its own representation, to allow chaining calls. Create an Ittr object from a JavaScript iterable (generators, arrays, custom iterators) with the iter() function.
const arr = [1, 2, 3, 4, 5];
const myIterable = iter(arr);We can convert the Ittr object back to an array using iter.toArray(), or by iterating through them.
myIterable.toArray();
// [1, 2, 3, 4, 5]
[...myIterable]
// [1, 2, 3, 4, 5]
const arr2 = []
for (const x of myIterable) {
arr2.push(x);
}
// arr == [1, 2, 3, 4, 5]iter has a variety of commonly used list methods with reasonable defaults.
iter([1, 2, 3, 4, 5]).map(x => x * x).toArray();
// [1, 4, 9, 16, 25]Map over each item in the iterator.
Returns a new iter object, where the predicate was called with each each item in the iterator. The behavior is conceptually similar to native Array.prototype.map.
iter([1, 2, 3, 4, 5]).filter(x => x % 2).toArray();
// [1, 3, 5]Filter the iterator's items with a given predicate function.
Returns a new iter object that only contains items from the original iterator, where passing it into the predicate function yielded a truthy value.
If the predicate is omitted, an identity function (x => x) will be used by default. This allows you to write iter(arr).filter() to filter out falsy values.
iter([1, 2, 3, 4, 5]).reduce((last, cur) => last * cur);
// 25, because 1 * 2 * 3 * 4 * 5 == 25Reduce from the left over the iterator, with a given predicate and initial value.
The behavior is conceptually similar to the native Array.prototype.reduce.
iter([1, 2, 3, 4, 5]).every(x => x > 0);
// true
iter([1, 2, 3, 4, 5]).every(x => x % 2 === 0);
// false
iter([true, true, true, true]).every();
// trueReports true only if calling the predicate function with every item in the iterator returns a truthy value. If called with an empty iterator, it returns true;
If no predicate function is passed, it'll default to the identity function (x => x);
iter([1, 2, 3, -4, -5]).some(x => x > 0);
// true
iter([1, 2, 3, 4, 5]).some(x => x < 0);
// false
iter([true, true, true, false]).some();
// trueReports true if calling the predicate with one or more item(s) of the iterator returns a truthy value. If called with an empty iterator, it returns false.
If no predicate function is passed, it'll default to the identity function (x => x);
const strings = ['foo bar', 'baz', 'hello javascript world'];
iter(strings).flatMap(str => str.split(' ')).toArray();
// ['foo', 'bar', 'baz', 'hello', 'javascript', 'world']
iter([[1, 2], [3], [4, 5, 6]]).flatMap();
// [1, 2, 3, 4, 5, 6]flatMap() returns a new iter object. From each item in the original iterator, the predicate is called on each item, and the returned array is concatenated into the resulting list. This results in an effective map, then "flattening" with a depth of 1.
When the predicate function is not specified, an identity function (x => x) is used.
iter.flatMap() also obviates the need for a iter.flatten(), since calling flatMap() with no arguments or an identity function will just flatten the array to a depth of 1. (If you don't know how deep your arrays are nested, you either 1. will benefit from writing a more custom function or 2. should consider restructuring your code to be more deterministic.)
iter([1, 2, 3, 4, 5, 6, 7, 8]).partition(3).toArray();
// [[1, 2, 3], [4, 5, 6], [7, 8]]Returns a new iter object, where each item is an array containing maxSize number of items from the original iterator, in order from the original iterator. If there are any items "left over", the last item in the returned iterator may have less than maxSize number of items.
iter([
{age: 3},
{age: 15},
{age: 9},
]).sortBy(obj => obj.age).toArray();
// [
// {age: 3},
// {age: 9},
// {age: 15},
// ]Returns a new iter object containing all the items from the original iterator, sorted by the comparator that predicate returns when it's called with each item. Internally, it just uses Array.prototype.sort.
When no predicate is specified, it defaults to the identity function, which results in identical behavior to Array.prototype.sort.
Because each iter method call returns another iter object, we can chain method calls together to easily perform sophisticated list manipulations.
iter(range(2, 20))
.filter(x => x % 2 === 0) // only evens, [2 .. 18]
.partition(3) // group by 3, [[2, 4, 6], [8, 10, 12], ...]
.map(([a, b, c]) => `${a} < ${b} < ${c}`)
.toArray().join(', ');
// '2 < 4 < 6, 8 < 10 < 12, 14 < 16 < 18'
iter(users)
.filter(user => user.isAdmin)
.sortBy(user => user.name)
.map(user => new UserView(user))
.partition(2)
.toArray();
// list of admin user views, sorted by name, grouped into pairs// one argument
range(5);
// [0, 1, 2, 3, 4]
// two arguments
range(2, 8.5);
// [2, 3, 4, 5, 6, 7, 8]
// three arguments
range(0, 3, 0.5);
// [0, .5, 1, 1.5, 2, 2.5]range() is conceptually identical to Python's range() function. It's variadic and has three modes.
range(start, end, step): Return an array of numbers, starting withstart, and addingstepeach time, until the value exceedsend. The returned array never incldues a value equal to or greater thanend.range(start, end): Whenstepis omitted, it's assumed to be 1.range(end): Whenstartis omitted, it's assumed to be 0. This means callingrange(n)wherenis a positive integer results in an array of lengthn, with increasing integer items.
zip(
[1, 2, 3],
['a', 'b', 'c'],
['X', 'Y', 'Z']
);
// [
// [1, 'a', 'X'],
// [2, 'b', 'Y'],
// [3, 'c', 'Z'],
// ]zip() is conceptually identical to Python's zip(). Given a variable number of arrays, it returns an array whose length is equal to the length of the longest array given to it. In the returned array, the nth item is an array with the nth item from each argument array, in order.
zip() can also be called with a spread array (...someArray) to "unzip" a zipped array into its parts.
const zippedArray = [
[1, 'a', 'X'],
[2, 'b', 'Y'],
[3, 'c', 'Z'],
];
zip(...zippedArray);
// [
// [1, 2, 3],
// ['a', 'b', 'c'],
// ['X', 'Y', 'Z'],
// ]