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 IterableYou 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 generatescountnumbers. Example:chain.range(0, 10)repeat-<T>(value: T, count: number) => IterableChain<T>- Create a new iterable object that repeatvaluecounttimes 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:
mapCreates 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:
filterCreates 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:
flatMapCreates 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:
appendCreates 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:
prependCreates 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:
concatCreates 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:
skipCreates 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:
takeCreates 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:
distinctCreates 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:
unionCreates a new iterable object that iterates over all previous values, and after only those items of theotherIterable 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:
zipCreates 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:
reverseCreates 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:
exceptCreates a new iterable object that iterates only those items that are not inotherIterable. 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
otheriterable - 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:
intersectCreates a new iterable object that iterates only those items that are inotherIterable. 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
otheriterable - 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:
groupByCreates a new iterable object that groups all items and iterates the groups. The groups are created by keys that provide by thekeySelectorfunction. 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 ifvalueSelectoris passed, result of thevalueSelectoris used as the group value. The method uses {} for grouping inside, so for non-primitives values of keys is usedtoStringmethod. IfkeyStringifieris 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:
joinCreates a new iterable object that iterates joined items with the other Iterable. The items are joined by keys. ThekeySelectorand theotherKeySelectorfunction 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:
sortCreate a new iterable object that iterates items in the sorted order. Ifcompareris 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 sortchain([4, 1, 10, 2]).sort((a, b) => a - b) // [1, 2, 4, 10]
- Signature:
Methods that iterate after call:
toArrayConverts the iterable into an array.- Signature:
<T>() => T[] - Example:
chain([1, 2, 3, 4]).toArray() // -> [1, 2, 3, 4]
- Signature:
toSetConverts the iterable into a set- Signature:
<T>() => Set<T> - Example:
chain([1, 2, 2, 1]).toSet() // -> Set([1, 2])
- Signature:
toMapConverts the iterable into a Map. The Map is created by keys that provide by thekeySelectorfunction. By default, the iterable item value is used as the map value, but ifvalueSelectoris passed, result of thevalueSelectoris 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:
toObjectConverts the iterable into an object. The object is created by keys that provide by thekeySelectorfunction. By default, the iterable item value is used as the object value, but ifvalueSelectoris passed, result of thevalueSelectoris 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:
reduceEnumerates Iterable and returns reduced value. The method usescallbackto define how to reduce the Iterable. By default, the first item of the Iterable is used as initial value of the reduced value. Ifinitialparam is passed, it is used as initial value. Ifinitialis not passed and the Iterable is empty, the method throws an error.- Signature:
- Examples:
chain([1, 2, 3, 4]).reduce((acc, cur) => acc + cur); // -> 10chain([1, 2, 3, 4]).reduce((acc, cur) => acc + cur, 1); // -> 11chain([1, 2, 3, 4]).reduce((acc, cur) => acc + cur, ""); // -> "1234"
firstEnumerates 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() // -> 1chain([1, 2, 3, 4]).first(x => x > 2) // -> 3chain([1, 2, 3, 4]).first(x => x > 4) // throw Error
- Signature:
firstOrDefaultEnumerates 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) // -> 1chain([1, 2, 3, 4]).firstOrDefault(5, x => x > 2) // -> 3chain([1, 2, 3, 4]).firstOrDefault(5, x => x > 4) // -> 5
- Signature:
singleEnumerates 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 Errorchain([1, 2, 3, 4]).single(x => x > 3) // -> 4chain([1, 2, 3, 4]).single(x => x > 4) // throw Error
- Signature:
singleOrDefaultEnumerates 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 Errorchain([1, 2, 3, 4]).singleOrDefault(5, x => x > 3) // -> 4chain([1, 2, 3, 4]).singleOrDefault(5, x => x > 4) // -> 5
- Signature:
lastEnumerates 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() // -> 4chain([1, 2, 3, 4]).last(x => x < 3) // -> 2chain([1, 2, 3, 4]).last(x => x < 0) // throw Error
- Signature:
lastOrDefaultEnumerates 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) // -> 4chain([1, 2, 3, 4]).lastOrDefault(5, x => x < 3) // -> 2chain([1, 2, 3, 4]).lastOrDefault(5, x => x < 0) // -> 5
- Signature:
countEnumerates 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() // -> 4chain([1, 2, 3, 4]).count(x => x > 2) // -> 2chain([1, 2, 3, 4]).count(x => x < 0) // -> 0
- Signature:
containsEnumerates 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) // -> truechain([{a: 10}, {b: 10}]).contains({a: 10}) // -> falsechain([{a: 10}, {b: 10}]).contains({a: 10}, (a, b) => a.a === b.a) // -> true
- Signature:
someEnumerates 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() // -> truechain([1, 2, 3, 4]).some(x => x > 2) // -> truechain([1, 2, 3, 4]).some(x => x < 0) // -> false
- Signature:
everyEnumerates 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) // -> falsechain([1, 2, 3, 4]).every(x => x < 5) // -> true
- Signature:
minEnumerate Iterable and returns the minimum item. Works only with a numeric Iterable.- Signature:
() => number - Example:
chain([1, 2, 3, 4]).min() // -> 1
- Signature:
maxEnumerate Iterable and returns the maximum item. Works only with a numeric Iterable.- Signature:
() => number - Example:
chain([1, 2, 3, 4]).max() // -> 4
- Signature:
sumEnumerate 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: