iterable-chain v2.0.1
https://github.com/byTimo/iterable-chain
Iterable-chain
This library contains a wrapper for productive and convenient manipulation of data collections. This solution uses similar principles as LINQ library in C#.
The principles
The library combines several manipulations over the source data collection in such a way that new collections will be allocated and iterated as few times as possible. For example, in the code below the native js array's methods are used.
const array = [1, 2, 3, 4, 5, 6].filter(x => x % 2 === 1).map(x => x + 1).slice(0, 2);
This code allocates a new array when filter
is called and a new array for map
. The code also completely iterates
over the source array and the array after filter
, but the optimal way uses only the first three items of the source
array. Using iterable-chain
library this code can be rewritten as
import {chain} from 'iterable-chain';
const array = chain([1, 2, 3, 4, 5, 6]).filter(x => x % 2 === 1).map(x => x + 1).take(2).toArray();
In this code chain
function is used to create a wrapper that allows to create a chain of iterators over the source
collection. filter
, map
and take
internally create iterable objects.
Each next iterable uses the previous iterable as a source. toArray
method iterates over the chain and returns the result
array. When toArray
is called, it takes an item from take
iterable, which takes an item from map
iterable, which
takes an item from filter
iterable, which takes an item from the source array. In the first iteration the first item is taken.
The first item passes filter
, is modified by map
, passes take
and is stored as the first item in the result array.
The second item of the source array is skipped by filter
iterable and the iterable immediately takes the next one. The
third item passes all conditions, is modified by map
and is stored as the second item of the result array. After that,
the iteration ends - no more items are taken. If your data collection has thousands of items, you will feel the difference.
Methods
Create the wrapper
For creating the wrapper it's used chain
function. You can use any an iterable object as the parameter.
import {chain} from 'iterable-chain';
chain([1, 2, 3, 4, 5])
chain(new Set([1, 2, 3, 4, 5]))
chain(new Map([[1, 1], [2, 2], [3, 3]]))
// or custom Iterable
You can also use an object as the parameter. In this case the object is interpreted as Map
and the result iterable
contains tuples [key, value]
as items.
import {chain} from 'iterable-chain';
chain({a: 10, b: 20}).toArray() // -> [["a", 10], ["b", 10]]
As an object chain
has two special methods for creating iterable object - range
and repeat
.
range
-(start: number, count: number) => IterableChain<number>
- Creates a new iterable object that iterates the range of numbers fromstart
. Each next number is the increment of the previous number. The method generatescount
numbers. Example:chain.range(0, 10)
repeat
-<T>(value: T, count: number) => IterableChain<T>
- Create a new iterable object that repeatvalue
count
times during the iteration. Example:chain.repeat("some", 5)
Wrapper's methods
There are two types of methods. The first type is methods that use a generator and don't iterate the chain of iterables
after call. These methods create a new iterable object. These are methods such as map
, filter
, skip
, take
, etc.
The second type is methods that iterate the chain after call and return certain result. These are methods such as
toArray
, toMap
, first
, every
, sum
, etc.
Most methods that don't iterate the chain after call iterate the source one by one. But there are methods that iterate
the entire previous chain before they can pass on their first item. For example, the method reverse
returns iterable object
after call, but it will be able to pass on the first item only after it receives the last item of the previous chain.
Methods that don't iterate after call and don't iterate the entire previous chain:
map
Creates a new iterable object that, during iteration, maps each item of the previous iterable object using the passed function.- Signature:
<T, R>(selector: (item: T, index: number) => R) => Iterable<R>
- Example:
chain([1, 2, 3, 4]).map(x => x - 1).toArray() // -> [0, 1, 2, 3]
- Signature:
filter
Creates a new iterable object. During the iteration, it leaves only those items for which the condition returns true.- Signature:
<T>(condition: (item: T, index: number) => boolean) => Iterable<T>
- Example:
chain([1, 2, 3, 4]).filter(x => x > 2).toArray() // -> [1]
- Signature:
flatMap
Creates a new iterable object that, maps each item of the previous Iterable to other Iterable and iterates each item of the resulting Iterable.- Signature:
<T, R>(selector: (item: T, index: number) => Iterable<R>) => Iterable<R>
- Example:
chain(["foo", "bar"]).flatMap(x => x.split("")).toArray() // -> ["f", "o", "o", "b", "a", "r"]
- Signature:
append
Creates a new iterable object that iterates all previous values and adds the passed item to the end of the iteration.- Signature:
<T>(element: T) => Iterable<T>
- Example:
chain([1, 2, 3, 4]).append(7).toArray() // -> [1, 2, 3, 4, 7]
- Signature:
prepend
Creates a new iterable object that iterates all previous values and adds the passed item to the beginning of the iteration.- Signature:
<T>(element: T) => Iterable<T>
- Example:
chain([1, 2, 3, 4]).prepand(0).toArray() // -> [0, 1, 2, 3, 4]
- Signature:
concat
Creates a new iterable object that iterates over all previous values, and after all the values of the passed Iterable.- Signature:
<T, S>(other: Iterable<S>) => Iterable<T | S>
- Example:
chain([1, 2]).concat([3, 4]).toArray() // -> [1, 2, 3, 4]
- Signature:
skip
Creates a new iterable object that skips the specified number of the items during the iteration. If the Iterable has fewer item than the skipped number, an empty Iterable are returned.- Signature:
<T>(count: number) => Iterable<T>
- Example:
chain([1, 2, 3, 4]).skip(2).toArray() // -> [3, 4]
- Signature:
take
Creates a new iterable object that takes the specified number of the items during the iteration. If the Iterable has fewer item than the taken number, all items of the Iterable are iterated.- Signature:
<T>(count: number) => Iterable<T>
- Example:
chain([1, 2, 3, 4]).take(2).toArray() // -> [1, 2]
- Signature:
distinct
Creates a new iterable object that iterates only unique items. The method uses Set inside, so non-primitives values are compared by references. If the stringifier is passed, the method uses its results as a values for comparison.- Signature:
<T>(stringifier?: (item: T) => string) => Iterable<T>
- Examples:
chain([1, 2, 2, 1]).distinct().toArray() // -> [1, 2]
chain([{a: 10}, {a: 10}]).distinct(x => x.a).toArray() // -> [{a: 10}]
- Signature:
union
Creates a new iterable object that iterates over all previous values, and after only those items of theother
Iterable that are not in the previous values. The method uses Set inside, so non-primitives values are compared by references. If the stringifier is passed, the method uses its results as a values for comparison.- Signature:
<T>(other: Iterable<T>, stringifier?: (item: T) => string) => Iterable<T>
- Examples:
chain([1, 2, 3, 4]).union([5, 4, 3]).toArray() // -> [1, 2, 3, 4, 5]
chain([{a: 10}, {a: 20}]).union([{a: 20}, {a: 30}], x => x.a).toArray() // -> [{a: 10}, {a: 20}, {a: 30}]
- Signature:
zip
Creates a new iterable object that iterates pairs of the previous Iterable items and the other Iterable. If the previous Iterable and the other Iterable have different number of items, the method uses the number of items the smallest Iterable. If mapper is passed, the method does not iterate pairs, but the results of the mapper.- Signature:
<T1, T2, R = [T1, T2]>(other: Iterable<T2>, mapper?: (a: T1, b: T2) => R) => Iterable<R>
- Examples:
chain([1, 2, 3]).zip([3, 2, 1]).toArray() // -> [[1, 3], [2, 2], [3, 1]]
chain([1, 2, 3]).zip([3, 2, 1], (a, b) => a + b).toArray() // -> [4, 4, 4]
- Signature:
Methods that don't iterate after call, but either iterate the entire previous chain or do other side effect:
reverse
Creates a new iterable object that reverses the order of the items.- Signature:
<T>() => Iterable<T>
- Side effect: iterates the entire previous chain before passes on the first item
- Example:
chain([1, 2, 3, 4]).reverse().toArray() // -> [4, 3, 2, 1]
- Signature:
except
Creates a new iterable object that iterates only those items that are not inother
Iterable. The method uses Set inside, so non-primitives values are compared by references. If the stringifier is passed, the method uses its results as a values for comparison.- Signature:
<T>(other: Iterable<T>, stringifier?: (item: T) => string) => Iterable<T>
- Side effect: iterates the
other
iterable - Examples:
chain([1, 2, 3, 4]).except([2, 3, 5]).toArray() // -> [1, 4]
chain([{a: 10}, {a: 20}, {a: 30}]).except([{a: 10}, {a: 50}], x => x.a).toArray() // -> [{a: 20}, {a: 30}]
- Signature:
intersect
Creates a new iterable object that iterates only those items that are inother
Iterable. The method uses Set inside, so non-primitives values are compared by references. If the stringifier is passed, the method uses its results as a values for comparison.- Signature:
<T>(other: Iterable<T>, stringifier?: (item: T) => string) => Iterable<T>
- Side effect: iterates the
other
iterable - Examples:
chain([1, 2, 3, 4]).intersect([2, 3, 5]).toArray() // -> [2, 3]
chain([{a: 10}, {a: 20}, {a: 30}]).intersect([{a: 10}, {a: 50}], x => x.a).toArray() // -> [{a: 10}]
- Signature:
groupBy
Creates a new iterable object that groups all items and iterates the groups. The groups are created by keys that provide by thekeySelector
function. Each group may have multiple values, so each group is represented by a tuple[key, value[]]
. By default, the iterable item value is used as the group value, but ifvalueSelector
is passed, result of thevalueSelector
is used as the group value. The method uses {} for grouping inside, so for non-primitives values of keys is usedtoString
method. IfkeyStringifier
is passed, the method uses its results for grouping inside, but does not change outgoing keys.- Signature:
<T, TKey, TValue = T>(keySelector: (item: T) => TKey, valueSelector?: (item: T) => TValue, keyStringifier?: (key: TKey) => Keyable) => Iterable<KeyValue<TKey, TValue[]>>
- Side effect: iterates the entire previous chain before passes on the first item
- Examples:
chain([{type: "a", value: 10}, {type: "b", value: 10}]).groupBy(x => x.type).toArray() // -> [["a", [{type: "a", value: 10}]], ["b", [{type: "b", value: 10}]]]
chain([{type: "a", value: 10}, {type: "b", value: 10}]).groupBy(x => x.type, x => x.value).toArray() // -> [["a", [10]], ["b", [10]]]
chain([{type: "a", value: 10}, {type: "b", value: 10}]).groupBy(x => x, x => x.value, key => key.type).toArray() // -> [[{type: "a", value: 10}, [10]], [{type: "b", value: 10}, [10]]]
- Signature:
join
Creates a new iterable object that iterates joined items with the other Iterable. The items are joined by keys. ThekeySelector
and theotherKeySelector
function select a key from the items of both Iterables. Joined value of the items of both Iterables is created bymapper
.- Signature:
<T1, T2, TKey extends Keyable, R>(other: Iterable<T2>, sourceKeySelector: (item: T1) => TKey, otherKeySelector: (item: T2) => TKey, mapper: (a: T1, b: T2) => R) => Iterable<R>
- Side effect: iterates the entire previous chain before passes on the first item
- Examples:
chain([{a: 10}, {a: 20}]).join([{b: 10}, {b: 30}], f => f.a, s => s.b, (f, s) => {...f, ...s}).toArray() // -> [{a: 10, b: 10}]
- Signature:
sort
Create a new iterable object that iterates items in the sorted order. Ifcomparer
is passed, it is used to define the order of the result Iterable by comparing two items.- Signature:
<T>(comparer?: (a: T, b: T) => number) => Iterable<T>
- Side effect: iterates the entire previous chain before passes on the first item
- Examples:
chain([4, 3, 1, 2]).sort().toArray() // -> [1, 2, 3, 4]
chain([4, 1, 10, 2]).sort().toArray() // -> [1, 10, 2, 4] like js sort
chain([4, 1, 10, 2]).sort((a, b) => a - b) // [1, 2, 4, 10]
- Signature:
Methods that iterate after call:
toArray
Converts the iterable into an array.- Signature:
<T>() => T[]
- Example:
chain([1, 2, 3, 4]).toArray() // -> [1, 2, 3, 4]
- Signature:
toSet
Converts the iterable into a set- Signature:
<T>() => Set<T>
- Example:
chain([1, 2, 2, 1]).toSet() // -> Set([1, 2])
- Signature:
toMap
Converts the iterable into a Map. The Map is created by keys that provide by thekeySelector
function. By default, the iterable item value is used as the map value, but ifvalueSelector
is passed, result of thevalueSelector
is used as the map value.- Signature:
<TKey extends Keyable, TValue = T>(keySelector: (item: T) => TKey, valueSelector?: (item: T) => TValue) => Map<TKey, TValue>
- Examples:
chain([{type: "a", value: 10}, {type: "b", value: 20]).toMap(x => x.type) // -> Map(a -> {type: "a", value: 10}, b -> {type: "b", value: 20})
chain([{type: "a", value: 10}, {type: "b", value: 20]).toMap(x => x.type, x => x.value) // -> Map(a -> 10, b -> 20)
- Signature:
toObject
Converts the iterable into an object. The object is created by keys that provide by thekeySelector
function. By default, the iterable item value is used as the object value, but ifvalueSelector
is passed, result of thevalueSelector
is used as the object value.- Signature:
<TKey extends Keyable, TValue = T>(keySelector: (item: T) => TKey, valueSelector?: (item: T) => TValue) => Record<TKey, TValue>
- Examples:
chain([{type: "a", value: 10}, {type: "b", value: 20]).toObject(x => x.type) // -> {a: {type: "a", value: 10}, b: {type: "b", value: 20}}
chain([{type: "a", value: 10}, {type: "b", value: 20]).toObject(x => x.type, x => x.value) // -> {a: 10, b: 20}
- Signature:
reduce
Enumerates Iterable and returns reduced value. The method usescallback
to define how to reduce the Iterable. By default, the first item of the Iterable is used as initial value of the reduced value. Ifinitial
param is passed, it is used as initial value. Ifinitial
is not passed and the Iterable is empty, the method throws an error.- Signature:
- Examples:
chain([1, 2, 3, 4]).reduce((acc, cur) => acc + cur); // -> 10
chain([1, 2, 3, 4]).reduce((acc, cur) => acc + cur, 1); // -> 11
chain([1, 2, 3, 4]).reduce((acc, cur) => acc + cur, ""); // -> "1234"
first
Enumerates Iterable and returns the first item that satisfies the condition. If the condition is not passed, the method returns the first item of the Iterable. If there is no item that satisfies the condition or the condition is not passed and the Iterable is empty, the method throws an error.- Signature:
<T>(condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).first() // -> 1
chain([1, 2, 3, 4]).first(x => x > 2) // -> 3
chain([1, 2, 3, 4]).first(x => x > 4) // throw Error
- Signature:
firstOrDefault
Enumerates Iterable and returns the first item that satisfies the condition. If the condition is not passed, the method returns the first item of the Iterable. If there is no item that satisfies the condition or the condition is not passed and the Iterable is empty, the method returns thedefaultValue
.- Signature:
<T>(defaultValue: T, condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).firstOrDefault(5) // -> 1
chain([1, 2, 3, 4]).firstOrDefault(5, x => x > 2) // -> 3
chain([1, 2, 3, 4]).firstOrDefault(5, x => x > 4) // -> 5
- Signature:
single
Enumerates Iterable and returns the single item that satisfies the condition. If the condition is not passed, the method returns the first item of the Iterable. If there is no item that satisfies the condition, the condition is not passed and the Iterable is empty or there are several items that satisfy the condition, the method throws an error.- Signature:
<T>(condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).single() // throw Error
chain([1, 2, 3, 4]).single(x => x > 3) // -> 4
chain([1, 2, 3, 4]).single(x => x > 4) // throw Error
- Signature:
singleOrDefault
Enumerates Iterable and returns the single item that satisfies the condition. If the condition is not passed, the method returns the first item of the Iterable. If there is no item that satisfies the condition or the condition is not passed and the Iterable is empty, the method returns thedefaultValue
. But if there are several items that satisfy the condition, the method throws an error.- Signature:
<T>(defaultValue: T, condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).singleOrDefault(5) // throw Error
chain([1, 2, 3, 4]).singleOrDefault(5, x => x > 3) // -> 4
chain([1, 2, 3, 4]).singleOrDefault(5, x => x > 4) // -> 5
- Signature:
last
Enumerates Iterable and returns the last item that satisfies the condition. If the condition is not passed, the method returns the last item of the Iterable. If there is no item that satisfies the condition or the condition is not passed and the Iterable is empty, the method throws an error.- Signature:
<T>(condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).last() // -> 4
chain([1, 2, 3, 4]).last(x => x < 3) // -> 2
chain([1, 2, 3, 4]).last(x => x < 0) // throw Error
- Signature:
lastOrDefault
Enumerates Iterable and returns the last item that satisfies the condition. If the condition is not passed, the method returns the last item of the Iterable. If there is no item that satisfies the condition or the condition is not passed and the Iterable is empty, the method returns thedefaultValue
.- Signature:
<T>(defaultValue: T, condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).lastOrDefault(5) // -> 4
chain([1, 2, 3, 4]).lastOrDefault(5, x => x < 3) // -> 2
chain([1, 2, 3, 4]).lastOrDefault(5, x => x < 0) // -> 5
- Signature:
count
Enumerates iterator and returns the number of items that satisfy the condition. If the condition is not passed, returns the number of items in the Iterable.- Signature:
<T>(condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).count() // -> 4
chain([1, 2, 3, 4]).count(x => x > 2) // -> 2
chain([1, 2, 3, 4]).count(x => x < 0) // -> 0
- Signature:
contains
Enumerates Iterable and returns true if the Iterable contains the passed element. The method uses===
for comparison by default, but if the comparer is passed, the method uses it.- Signature:
<T>(element: T, comparer?: (a: T, b: T) => boolean) => boolean
- Examples:
chain([1, 2, 3, 4]).contains(3) // -> true
chain([{a: 10}, {b: 10}]).contains({a: 10}) // -> false
chain([{a: 10}, {b: 10}]).contains({a: 10}, (a, b) => a.a === b.a) // -> true
- Signature:
some
Enumerates iterator and returns true if at least one item satisfies the condition. If the condition is not passed, returns true if the Iterable has at least one item.- Signature:
<T>(condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).some() // -> true
chain([1, 2, 3, 4]).some(x => x > 2) // -> true
chain([1, 2, 3, 4]).some(x => x < 0) // -> false
- Signature:
every
Enumerates iterator and returns true if all items satisfy the condition.- Signature:
<T>(condition?: (item: T, index: number) => boolean) => T
- Examples:
chain([1, 2, 3, 4]).every(x => x > 2) // -> false
chain([1, 2, 3, 4]).every(x => x < 5) // -> true
- Signature:
min
Enumerate Iterable and returns the minimum item. Works only with a numeric Iterable.- Signature:
() => number
- Example:
chain([1, 2, 3, 4]).min() // -> 1
- Signature:
max
Enumerate Iterable and returns the maximum item. Works only with a numeric Iterable.- Signature:
() => number
- Example:
chain([1, 2, 3, 4]).max() // -> 4
- Signature:
sum
Enumerate Iterable and returns the sum of the items. Works only with a numeric Iterable.- Signature:
() => number
- Example:
chain([1, 2, 3, 4]).sum() // -> 10
- Signature: