Skip to content

Lazy-loading wrapper for iterable items and common functional programming operations

License

Notifications You must be signed in to change notification settings

dvlsg/iterablejs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

iterablejs

A functional, lazy-loading data manipulation library for Javascript.

let result = new Iterable([1, 2, 3, 4, 5, 4, 3, 2, 1])
    .distinct()       // 1, 2, 3, 4, 5
    .select(x => x*2) // 2, 4, 6, 8, 10
    .reverse()        // 10, 8, 6, 4, 2
    .skip(1)          // 8, 6, 4, 2
    .take(2)          // 8, 6
    .average();       // 7

Installation

npm install iterablejs

Usage

An Iterable can wrap any item which defines Symbol.iterator, including an Array, Map, Set, or Generators, and can also wrap GeneratorFunctions which do not require any arguments.

import Iterable from 'iterablejs';

let iter1 = new Iterable([1, 2, 3]),
    iter2 = new Iterable(new Set([1, 2, 3, 1, 2])),
    iter3 = new Iterable(new Map([['a', 1], ['b', 2]])),
    iter4 = new Iterable(function*() { yield 1; yield 2; yield 3 }),
    iter5 = new Iterable({ *[Symbol.iterator]() { yield 1; yield 2; yield 3; }});

For convenience, iterablejs also exposes a method called iter to assist with constructing Iterable objects.

import { iter } from 'iterablejs';
let iterable = iter([1, 2, 3, 4, 5]);

Default task

  • Install node.js
  • Clone the iterablejs project
  • Run npm install
  • Run gulp
    • Executes tests
    • Cleans dist
    • Lints source
    • Builds source
    • Watches source and tests

API

Links

Iterable

#aggregate()

aggregate(func: Function): Any
aggregate(func: Function, seed: Any): Any

Reduces an Iterable to a single value, using a callback and an optional seed value.

let val = iter([2, 3, 4, 5])
    .aggregate((carry, current) => {
        return carry + current;
    }, 1);
//=> 15

If no seed value is provided, then the first yielded element will be used as the seed.

let val = iter([1, 2, 3, 4, 5])
    .aggregate((carry, current) => {
        return carry + current;
    });
//=> 15

#any()

any(predicate: Function): Boolean

Returns a boolean, indicating whether at least one of the values in the Iterable passes a given predicate.

let passed = iter([1, 2, 3, 4, 5]).any(x => x > 3);
//=> true

let failed = iter([1, 2, 3, 4, 5]).any(x => x < 1);
//=> false

If no predicate is provided, then a check for null and undefined will be used.

let passed = iter([null, undefined, 0]).any();
//=> true

let failed = iter([null, undefined, null]).any();
//=> false

#at()

at(index: Number): Any

Returns the value at the provided zero-based index of yielded elements.

let val = iter([1, 2, 3, 4, 5]).at(3);
//=> 4

If the index provided is beyond the length of the Iterable, then undefined will be returned.

let val = iter([1, 2, 3, 4, 5]).at(6);
//=> undefined

#average()

average(): Number
average(selector: Function): Number

Returns the average of any numbers yielded by the Iterable.

let val = iter([1, 2, 3, 4, 5]).average();
//=> 3

If a selector is provided, it will be used to select which elements are yielded.

let val = iter([
    {a: 1, b: 2},
    {a: 3, b: 4},
    {a: 5, b: 6}
]).average(x => x.a);
//=> 3

If the Iterable is empty, then NaN will be returned.

let val = iter([]).average();
//=> NaN

#concat()

concat(...args): Iterable

Returns an Iterable which will iterate over the original iterable followed by all iterable arguments in sequence.

let iterable = iter([1, 2]).concat(new Set([3, 4]), function*() { yield 5; yield 6; });
//=> 1, 2, 3, 4, 5, 6

#contains()

contains(item: Any): Boolean

Returns a boolean indicating whether or not the Iterable contains a given element.

let passed = iter([1, 2, 3, 4, 5]).contains(5);
//=> true

let failed = iter([1, 2, 3, 4, 5]).contains(0);
//=> false

Note that contains will use a deep equals method for comparison.

let passed = iter([
    {a: 1, b: 2},
    {a: 3, b: 4},
    {a: 5, b: 6}
]).contains({a: 3, b: 4});
//=> true

#distinct()

distinct(): Iterable
distinct(hasher: Function): Iterable

Returns an Iterable which will yield only distinct elements.

let iterable = iter([1, 2, 3, 4, 5, 4, 3, 2, 1]).distinct();
//=> 1, 2, 3, 4, 5

Note that distinct uses a Set to determine if elements are unique. If a hasher is provided, it will be used to hash each element before adding it to the Set.

let iterable = iter([{a: 1}, {a: 1}]).distinct();
//=> {a: 1}, {a: 1}

let iterable = iter([{a: 1}, {a: 1}]).distinct(x => x.a);
//=> {a: 1}

#empty()

empty(): Boolean

Returns a boolean indicating whether or not every element yielded from the Iterable is considered empty.

let passed = iter([
    null,
    undefined,
    false,
    0,
    '',
    [],
    new Set(),
    new Map()
]).empty();
//=> true

let failed = iter([0, null, undefined, 4, '', new Set(), new Map()]).empty();
//=> false

#every()

every(): Boolean
every(predicate: Function): Boolean

Returns a boolean indicating whether or not every element yielded from the Iterable passes a given predicate.

let passed = iter([1, 2, 3, 4, 5]).every(x => typeof x === 'number');
//=> true

let failed = iter([1, 2, '3', 4, 5]).every(x => typeof x === 'number');
//=> false

If no predicate is provided, then a check for null and undefined will be used.

let failed = iter([1, 2, 3, null, 5]).every();
//=> false

#filter()

filter(): Iterable
filter(predicate: Function): Iterable

An alias for where.

#first()

first(): Any
first(predicate: Function): Any

Returns the first non-null, non-undefined element yielded from the Iterable.

let val = iter([1, 2, 3, 4, 5]).first();
//=> 1
let val = iter([null, 2, 3, 4, 5]).first();
//=> 2

If a predicate is provided, then the first element yielded from the Iterable which passes the predicate will be returned.

let val = iter([1, 2, 3, 4, 5]).first(x => x > 2);
//=> 3

If the Iterable does not yield any elements or if no elements pass a given predicate, then undefined will be returned.

let val = iter([1, 2, 3, 4, 5]).first(x => x > 5);
//=> undefined

#firstOrDefault()

firstOrDefault(default: Any): Any
firstOrDefault(predicate: Function, default: Any): Any

Returns the first element yielded from the Iterable, or the default if no element is yielded.

let val = iter([]).firstOrDefault(1);
//=> 1

If a predicate is provided, then the first element yielded from the Iterable which passes the predicate will be returned. If no value passes the predicate, then the default will be returned.

let val = iter([1, 2, 3, 4, 5]).firstOrDefault(x => x > 5, 6);
//=> 6

#flatten()

flatten(): Iterable

Returns an Iterable which yields a flattened list of elements.

let iterable = iter([1, 2, 3, [4, 5, 6], [7, [8, [9]]]]).flatten();
//=> 1, 2, 3, 4, 5, 6, 7, 8, 9

#full()

full(): Boolean

Returns a boolean indicating whether every element yielded from the Iterable is considered non-empty.

let passed = iter([1, 2, 3, 4, 5]).full();
//=> true

let failed = iter([1, 2, 3, '', 5]).full();
//=> false

#group()

group(keySelector: Function): Iterable

Returns an Iterable which yields the iterable grouping of elements based on the provided keySelector.

let invoices = [
    { product: 1, price: 20, qty: 5, customer: 3 },
    { product: 1, price: 20, qty: 2, customer: 1 },
    { product: 1, price: 20, qty: 8, customer: 1 },
    { product: 2, price: 40, qty: 1, customer: 2 },
    { product: 3, price: 60, qty: 3, customer: 3 }
];

let grouped = iter(invoices)
    .group(invoice => invoice.product)
    .select(group => ({
        product: group.key,
        priceSum: group.sum(invoice => invoice.price * invoice.qty),
        qtySum: group.sum(invoice => invoice.qty),
        distinctCustomerCount: group.unique(invoice => invoice.customer).length(),
        recordCount: group.length()
    }))
    .orderBy(processed => processed.product);
/*=> { product: 1, priceSum: 300, qtySum: 15, distinctCustomerCount: 2, recordCount: 3 },
     { product: 2, priceSum: 40,  qtySum: 1,  distinctCustomerCount: 1, recordCount: 1 },
     { product: 3, priceSum: 180, qtySum: 3,  distinctCustomerCount: 1, recordCount: 1 } */

#groupBy()

groupBy(keySelector: Function): Iterable

An alias for group.

#intersect()

intersect(iter: Any): Iterable
intersect(iter: Any, selector: Function): Iterable

Returns an Iterable which yields the elements which exist in both Iterables.

let iterable = iter([1, 2, 3, 4, 5]).intersect([1, 3, 5]);
//=> 1, 3, 5

If a selector is provided, then it will be used to determine the intersection.

let iterable = iter([{a: 1}, {a: 2}, {a: 3}]).intersect([{a: 1}, {a: 3}], x => x.a);
//=> {a: 1}, {a: 3}

#join()

join(...args): MultiIterable

Returns a MultiIterable which yields the cartesian product of all elements.

let iterable = iter([1, 2]).join([3, 4], [5, 6]);
/*=> [ 1, 3, 5 ],
     [ 1, 3, 6 ],
     [ 1, 4, 5 ],
     [ 1, 4, 6 ],
     [ 2, 3, 5 ],
     [ 2, 3, 6 ],
     [ 2, 4, 5 ],
     [ 2, 4, 6 ] */

A MultiIterable can also call #join() with additional iterable items at any point.

let iterable = iter([1, 2]).join([3, 4]);
/*=> [ 1, 3 ],
     [ 1, 4 ],
     [ 2, 3 ],
     [ 2, 4 ] */

iterable = iterable.join([5, 6]);
/*=> [ 1, 3, 5 ],
     [ 1, 3, 6 ],
     [ 1, 4, 5 ],
     [ 1, 4, 6 ],
     [ 2, 3, 5 ],
     [ 2, 3, 6 ],
     [ 2, 4, 5 ],
     [ 2, 4, 6 ] */

#last()

last(): Any
last(predicate: Function): Any

Returns the last element yielded from the Iterable.

let val = iter([1, 2, 3, 4, 5]).last();
//=> 5

If a predicate is provided, then the last element yielded from the Iterable which passes the predicate will be returned.

let val = iter([5, 4, 3, 2, 1]).last(x => x > 2);
//=> 3

If the Iterable does not yield any elements or if no elements pass a given predicate, then undefined will be returned.

let val = iter([1, 2, 3, 4, 5]).last(x => x > 5);
//=> undefined

#lastOrDefault()

lastOrDefault(default: Any): Any
lastOrDefault(predicate: Function, default: Any): Any

Returns the last element yielded from the Iterable, or the default if no element is yielded.

let val = iter([]).lastOrDefault(1);
//=> 1

If a predicate is provided, then the last element yielded from the Iterable which passes the predicate will be returned.

let val = iter([1, 2, 3, 4, 5]).lastOrDefault(x => x < 3, 6);
//=> 2

If no value passes the predicate, then the default will be returned.

let val = iter([1, 2, 3, 4, 5]).lastOrDefault(x => x > 5, 6);
//=> 6

#length()

length(): Number

Returns the length of the Iterable.

let val = iter([1, 2, 3, 4, 5]).length();
//=> 5

Note that if length is a property defined on the internal data and is a Number, then it will be used directly. If length is not available as a property, then the Iterable will be fully enumerated to determine length.

#map()

map(selector: Function): Iterable

An alias for select.

#max()

max(): Number
max(selector: Function): Number

Returns the max of any numbers yielded by the Iterable.

let val = iter([1, 2, 3, 4, 5]).max();
//=> 5

If a selector is provided, it will be used to select values for the comparison.

let val = iter([
    {a: 1, b: 2},
    {a: 3, b: 4},
    {a: 5, b: 6}
]).max(x => x.b);
//=> 6

#merge()

merge(iter: Any): Iterable

An alias for zip.

#min()

min(): Number
min(selector: Function): Number

Returns the min of any numbers yielded by the Iterable.

let val = iter([1, 2, 3, 4, 5]).min();
//=> 1

If a selector is provided, it will be used to select values for the comparison.

let val = iter([
    {a: 1, b: 2},
    {a: 3, b: 4},
    {a: 5, b: 6}
]).min(x => x.b);
//=> 2

#orderBy()

orderBy(selector: Function): OrderedIterable
orderBy(selector: Function, comparer: Function): OrderedIterable
orderBy(selector: Function, comparer: Function, descending: Boolean): OrderedIterable

Returns an OrderedIterable which yields items in ascending order by using a stable quicksort.

let iterable = iter([
    {a: 3, b: 4},
    {a: 1, b: 2},
    {a: 5, b: 6}
]).orderBy(x => x.a);
//=> {a: 1, b: 2}, {a: 3, b: 4}, {a: 5, b: 6}

If no comparer is provided, then the selected elements will be compared using < and >.

If a comparer is provided, it will be used instead.

let comparer = (x, y) => {
    if (x > y)
        return -1;
    if (x < y)
        return 1;
    return 0;
};
let iterable = iter([
    {a: 3, b: 4},
    {a: 1, b: 2},
    {a: 5, b: 6}
]).orderBy(x => x.a, comparer);
//=> {a: 5, b: 6}, {a: 3, b: 4}, {a: 1, b: 2}

By default, orderBy will yield elements in an ascending order. If the OrderedIterable needs to yield elements in a reverse order, a boolean flag can be passed to flip the order.

let iterable = iter([
    {a: 3, b: 4},
    {a: 1, b: 2},
    {a: 5, b: 6}
]).orderBy(x => x.a, undefined, true);
//=> {a: 5, b: 6}, {a: 3, b: 4}, {a: 1, b: 2}

#orderByDescending()

orderByDescending(selector: Function): OrderedIterable
orderByDescending(selector: Function, comparer: Function): OrderedIterable

Returns an OrderedIterable which yields elements in descending order.

let iterable = iter([
    {a: 3, b: 4},
    {a: 1, b: 2},
    {a: 5, b: 6}
]).orderByDescending(x => x.a);
//=> {a: 5, b: 6}, {a: 3, b: 4}, {a: 1, b: 2}

#reduce()

reduce(func: Function): Any
reduce(func: Function, seed: Any): Any

An alias for aggregate.

#reverse()

reverse(): Iterable

Returns an Iterable which yields elements in a reverse order.

let iterable = iter([1, 2, 3, 4, 5]).reverse();
//=> 5, 4, 3, 2, 1

#select()

select(selector: Function): Iterable

Returns an Iterable which yields mapped elements from the given selector.

let iterable = iter([
    {a: 1, b: 2},
    {a: 3, b: 4},
    {a: 5, b: 6}
]).select(x => x.a);
//=> 1, 3, 5

#skip()

skip(): Iterable
skip(count: Number): Iterable

Returns an Iterable which bypasses the given number of elements and then yields the remainder.

let iterable = iter([1, 2, 3, 4, 5]).skip(2);
//=> 3, 4, 5

If the given number is greater than the number of elements, then no items are yielded.

let iterable = iter([1, 2, 3, 4, 5]).skip(6);
//=> (empty)

If no number is provided, then the next element will not be yielded from the Iterable.

let iterable = iter([1, 2, 3, 4, 5]).skip();
//=> 2, 3, 4, 5

#sum()

sum(): Number
sum(selector: Function): Number

Returns the sum of any numbers yielded by the Iterable.

let val = iter([1, 2, 3, 4, 5]).sum();
//=> 15

If a selector is provided, it will be used to select values for the comparison.

let val = iter([
    {a: 1, b: 2},
    {a: 3, b: 4},
    {a: 5, b: 6}
]).sum(x => x.b);
//=> 12

#take()

take(): Iterable
take(count: Number): Iterable

Returns an Iterable which only yields the given number of elements.

let iterable = iter([1, 2, 3, 4, 5]).take(3);
//=> 1, 2, 3

If the given number is greater than the number of elements, then all items are yielded.

let iterable = iter([1, 2, 3, 4, 5]).take(6);
//=> 1, 2, 3, 4, 5

If no number is provided, then only the next element will be yielded from the Iterable.

let iterable = iter([1, 2, 3, 4, 5]).take();
//=> 1

#takeWhile()

takeWhile(): Iterable
takeWhile(predicate: Function): Iterable

An alias for while.

#toArray()

toArray(): Array

Returns an Array containing every element yielded from the Iterable.

let arr = iter(function*() { yield 1; yield 2; yield 3; }).toArray();
//=> [ 1, 2, 3 ]

#union()

union(...args): Iterable
union(...args, hasher: Function): Iterable

Returns an Iterable which yields a union of all given iterables.

let iterable1 = iter([ 1, 2, 3 ]);
let iterable2 = iter([ 3, 4, 5 ]);
let union = iterable1.union(iterable2);
//=> 1, 2, 3, 4, 5

If hasher callback is provided, then it will be used to determine uniqueness of the elements.

let iterable1 = new Iterable([{ val: 1 }, { val: 2 }, { val: 3 }]);
let iterable2 = new Iterable([{ val: 3 }, { val: 4 }, { val: 5 }]);
let union = iterable1.union(iterable2, x => x.val);
/*=> { val: 1 },
     { val: 2 },
     { val: 3 },
     { val: 4 },
     { val: 5 } */

Important: Due to the dynamic nature of the arguments, avoid passing an iterable Function or GeneratorFunction as the final argument to #union().

If you intend to use Function or GeneratorFunction as an iterable argument for #union(), consider either using #concat() followed by #distinct() instead, or wrapping each argument as an Iterable first.

#unionAll()

unionAll(...args): Iterable

An alias for concat.

#unwind()

unwind(selector: Function): Iterable

Uses the selector provided to pluck a sub-iterable out of a sequence of iterables, iterating over the selected piece and yielding a pair of the base and sub element for each of the sub elements.

let iterable = new Iterable([
    { id: 1, arr: [ 'a', 'b' ] },
    { id: 2, arr: [ 'c', 'd' ] },
    { id: 3, arr: [ 'e', 'f' ] },
    { id: 4, arr: [ 'g', 'h' ] },
    { id: 5, arr: [ 'i', 'j' ] }
])
.unwind(x => x.arr);
/*=> [ { id: 1, arr: [ 'a', 'b' ] }, 'a' ],
     [ { id: 1, arr: [ 'a', 'b' ] }, 'b' ],
     [ { id: 2, arr: [ 'c', 'd' ] }, 'c' ],
     [ { id: 2, arr: [ 'c', 'd' ] }, 'd' ],
     [ { id: 3, arr: [ 'e', 'f' ] }, 'e' ],
     [ { id: 3, arr: [ 'e', 'f' ] }, 'f' ],
     [ { id: 4, arr: [ 'g', 'h' ] }, 'g' ],
     [ { id: 4, arr: [ 'g', 'h' ] }, 'h' ],
     [ { id: 5, arr: [ 'i', 'j' ] }, 'i' ],
     [ { id: 5, arr: [ 'i', 'j' ] }, 'j' ] */

No mutation or mapping of the base element is assumed. Typically, this method would be used with a further aggregation like select() in order to map elements as in the following example:

let iterable = new Iterable([
    { id: 1, arr: [ 'a', 'b' ] },
    { id: 2, arr: [ 'c', 'd' ] },
    { id: 3, arr: [ 'e', 'f' ] },
    { id: 4, arr: [ 'g', 'h' ] },
    { id: 5, arr: [ 'i', 'j' ] }
])
.unwind(x => x.arr)
.select(([base, sub]) => ({
    id: base.id,
    letter: sub
}));
/*=> { id: 1, letter: 'a' },
     { id: 1, letter: 'b' },
     { id: 2, letter: 'c' },
     { id: 2, letter: 'd' },
     { id: 3, letter: 'e' },
     { id: 3, letter: 'f' },
     { id: 4, letter: 'g' },
     { id: 4, letter: 'h' },
     { id: 5, letter: 'i' },
     { id: 5, letter: 'j' } */

#where()

where(): Iterable
where(predicate: Function): Iterable

Returns an Iterable which only yields elements which return true from the given predicate.

let iterable = iter([1, 2, 3, 4, 5]).where(x => x % 2 === 0);
//=> 2, 4

If no predicate is provided, then null and undefined will not be yielded.

let iterable = iter(['', 2, 0, 4, null]).where();
//=> '', 2, 0, 4

#while()

while(): Iterable
while(predicate: Function): Iterable

Returns an Iterable which yields elements until a predicate returns false.

let iterable = iter([1, 2, 3, 4, 5]).while(x => x < 4);
//=> 1, 2, 3

If no predicate is provided, then a check for null and undefined will be used.

let iterable = iter([1, 2, 3, null, 5]).while();
//=> 1, 2, 3

#zip()

zip(iter: Any, selector: Function): Iterable

Returns an Iterable which applies a function to both Iterables, yielding a sequence of the results.

let iterable = iter([1, 2, 3, 4, 5]).zip([1, 2, 3, 4, 5], (x, y) => x + y);
//=> 2, 4, 6, 8, 10

.empty()

Iterable.empty()

Returns an Iterable that does not yield any elements.

let iterable = Iterable.empty();
//=> (empty)

.iter()

iter(...args: any)

Constructs an Iterable from the given arguments.

let iterable = Iterable.iter([1, 2, 3, 4, 5]);
//=> 1, 2, 3, 4, 5

If no arguments are provided, an empty Iterable is returned.

let iterable = Iterable.iter();
//=> (empty)

If more than one argument is provided, a MultiIterable is returned.

let iterable = Iterable.iter([1, 2], [3, 4]);
/*=> [1, 3],
     [1, 4],
     [2, 3],
     [2, 4] */

OrderedIterable

#thenBy()

thenBy(selector: Function): OrderedIterable
thenBy(selector: Function, comparer: Function): OrderedIterable
thenBy(selector: Function, comparer: Function, descending: Boolean): OrderedIterable

Returns an OrderedIterable with a subsequent level of sorting.

let iterable = iter([
    { id: 1, num: 11, num2: 44, num3: 88 },
    { id: 2, num: 22, num2: 44, num3: 66 },
    { id: 3, num: 22, num2: 33, num3: 77 },
    { id: 4, num: 11, num2: 33, num3: 99 },
    { id: 5, num: 22, num2: 44, num3: 55 }
])
.orderBy(x => x.num)
.thenBy(x => x.num2);
/*=> { id: 4, num: 11, num2: 33, num3: 99 },
     { id: 1, num: 11, num2: 44, num3: 88 },
     { id: 3, num: 22, num2: 33, num3: 77 },
     { id: 2, num: 22, num2: 44, num3: 66 },
     { id: 5, num: 22, num2: 44, num3: 55 } */

This method is only available on an OrderedIterable, and can make use of a custom comparer and descending flag in the same way as orderBy().

#thenByDescending()

thenByDescending(selector: Function): OrderedIterable
thenByDescending(selector: Function, comparer: Function): OrderedIterable

Returns an OrderedIterable with a subsequent level of sorting in descending order.

let iterable = iter([
    { id: 1, num: 11, num2: 44, num3: 88 },
    { id: 2, num: 22, num2: 44, num3: 66 },
    { id: 3, num: 22, num2: 33, num3: 77 },
    { id: 4, num: 11, num2: 33, num3: 99 },
    { id: 5, num: 22, num2: 44, num3: 55 }
])
.orderBy(x => x.num)
.thenByDescending(x => x.num2);
/*=> { id: 1, num: 11, num2: 44, num3: 88 },
     { id: 4, num: 11, num2: 33, num3: 99 },
     { id: 2, num: 22, num2: 44, num3: 66 },
     { id: 5, num: 22, num2: 44, num3: 55 },
     { id: 3, num: 22, num2: 33, num3: 77 } */

About

Lazy-loading wrapper for iterable items and common functional programming operations

Resources

License

Stars

Watchers

Forks

Packages

No packages published