An api for lazy querying of iterables, implemented in TypeScript and inspired by .NET's LINQ methods.
To implement a lazy API similar by using iterators in order to simplify data-oriented workflows greatly and to provide an API C# developers familiar with the LINQ extension methods.
- where
- select
- selectMany
- distinct
- zip
- groupBy
- join
- orderBy
- orderByDescending
- reverse
- skip
- skipWhile
- take
- takeWhile
- except
- intersect
- concat
- union
- aggregate
- windowed
- batch
- any
- all
- min
- max
- average
- sequenceEquals
- indexOf
- elementAt
- first
- firstOrDefault
- last
- lastOrDefault
- forEach
- toArray
- count
- seq
- id
The API any objects which are iterable in JavaScript. In order to use the method it is required to call linq
with the object that we want to iterate as a parameter. The result of linq
is Linqable
object which supports the api. The linq module exports linq
, seq
and id
.
import { linq } from "./linq";
interface IPerson {
name: string;
age: number;
}
let people: IPerson[] = [
{ name: "Ivan", age: 24 },
{ name: "Deyan", age: 25 }
];
linq(people)
.where(p => p.age > 22)
.select(p => p.name)
.forEach(name => console.log(name))
Ivan
Deyan
Where filters the iterable based on a predicate function.
A sequence of the elements for which the predicate returns true
will be returned.
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evenNumbers = linq(numbers).where(i => i % 2 == 0);
for (let number of evenNumbers) {
console.log(number)
}
2
4
6
8
10
Each element of an iterable is trasnformed into another value - the return value of the function passed to select
.
let numbers = [1, 2, 3, 4, 5];
let numbersTimes10 = linq(numbers).select(i => i * 10);
for (let number of numbersTimes10) {
console.log(number)
}
10
20
30
40
50
Flattens iterable elements into a single iterable sequence.
selectMany
expects a function which takes an element from the sequence returns an iterable. All of the results are flattent into a single sequence.
let numbers = [{
inner: [1, 2, 3]
}, {
inner: [4, 5, 6]
}];
let flattened = linq(numbers).selectMany(x => x.inner);
for (let number of flattened) {
console.log(number)
}
1
2
3
4
5
6
Gets the distinct elements of a sequence based on a selector function. If a selector function is not passed, it will get the distinct elements by reference.
let numbers = [{ value: 1 }, { value: 1 }, { value: 2 }, { value: 2 }, { value: 3 }, { value: 3 }];
let distinct = linq(numbers).distinct(el => el.value);
for (let number of distinct) {
console.log(number)
}
{ value: 1 }
{ value: 2 }
{ value: 3 }
Applies a transformation function to each corresponding pair of elements from the iterables. The paring ends when the shorter sequence ends, the remaining elements of the other sequence are ignored.
let odds = [1, 3, 5, 7];
let evens = [2, 4, 6, 8];
let oddEvenPairs = linq(odds)
.zip(evens, (odd, even) => ({ odd, even }));
for (let element of oddEvenPairs) {
console.log(element);
}
{ odd: 1, even: 2 }
{ odd: 3, even: 4 }
{ odd: 5, even: 6 }
{ odd: 7, even: 8 }
Groups elements based on a selector function. The function returns a sequence of arrays with the group key as the first element and an array of the group elements as the second element.
let groups = linq([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).groupBy(i => i % 2);
for (let group of groups) {
console.log(group);
}
[ 1, [ 1, 3, 5, 7, 9 ] ]
[ 0, [ 2, 4, 6, 8, 10 ] ]
Performs a join on objects matching property values according to the provided leftSelector and rightSelector. The matching objects are merged into another value by resultSelector.
let first = [{ name: "Ivan", age: 21 }];
let second = [{ name: "Ivan", phone: "0123456789" }];
let joined = linq(first).join(second, f => f.name, s => s.name, (f, s) => ({ name: f.name, age: f.age, phone: s.phone }));
for (let group of joined) {
console.log(group);
}
{ name: 'Ivan', age: 21, phone: '0123456789' }
Orders elements in asceding order based on a selector function.
let people = [
{ id: 1, age: 18 },
{ id: 2, age: 29 },
{ id: 3, age: 8 },
{ id: 4, age: 20 },
{ id: 5, age: 18 },
{ id: 6, age: 32 },
{ id: 7, age: 5 },
];
let ordered = linq(people).orderBy(p => p.age)
for (let element of ordered) {
console.log(element);
}
{ id: 7, age: 5 }
{ id: 3, age: 8 }
{ id: 1, age: 18 }
{ id: 5, age: 18 }
{ id: 4, age: 20 }
{ id: 2, age: 29 }
{ id: 6, age: 32 }
Equivalent of orderBy
.
Orders elements in descending order based on a selector function.
Reverses the order of the sequence, e.g. reverse (1, 2, 3) -> (3, 2, 1)
let reversed = linq([1, 2, 3, 4, 5, 6, 7, 8])
.reverse()
for (let element of reversed) {
console.log(element);
}
8
7
6
5
4
3
2
1
Skips a specific number of elements.
let people = [
{ id: 1, age: 18 },
{ id: 2, age: 29 },
{ id: 3, age: 8 },
{ id: 4, age: 20 },
{ id: 5, age: 18 },
{ id: 6, age: 32 },
{ id: 7, age: 5 },
];
let elements = linq(people).skip(3);
for (let element of elements) {
console.log(element);
}
{ id: 4, age: 20 }
{ id: 5, age: 18 }
{ id: 6, age: 32 }
{ id: 7, age: 5 }
Skips the elements in the sequence while the predicate returns true
.
let people = [
{ id: 1, age: 18 },
{ id: 2, age: 20 },
{ id: 3, age: 30 },
{ id: 4, age: 25 },
{ id: 5, age: 18 },
{ id: 6, age: 32 },
{ id: 7, age: 5 },
];
let elements = linq(people).skipWhile(p => p.age % 2 === 0);
for (let element of elements) {
console.log(element);
}
{ id: 4, age: 25 }
{ id: 5, age: 18 }
{ id: 6, age: 32 }
{ id: 7, age: 5 }
Takes a specific number of elements.
let people = [
{ id: 1, age: 18 },
{ id: 2, age: 20 },
{ id: 3, age: 30 },
{ id: 4, age: 25 },
{ id: 5, age: 18 },
{ id: 6, age: 32 },
{ id: 7, age: 5 },
];
let elements = linq(people).take(4);
for (let element of elements) {
console.log(element);
}
{ id: 1, age: 18 },
{ id: 2, age: 20 },
{ id: 3, age: 30 },
{ id: 4, age: 25 }
Takes elements from the sequence while the predicate returns true
.
let people = [
{ id: 1, age: 18 },
{ id: 2, age: 20 },
{ id: 3, age: 30 },
{ id: 4, age: 25 },
{ id: 5, age: 18 },
{ id: 6, age: 32 },
{ id: 7, age: 5 },
];
let elements = linq(people).takeWhile(p => p.age % 2 === 0);
for (let element of elements) {
console.log(element);
}
{ id: 1, age: 18 },
{ id: 2, age: 20 },
{ id: 3, age: 30 }
Returns a sequence of elements which are not present in the sequence passed to except
.
let elements = linq([1, 2, 3, 4, 5, 6]).except([3, 5, 6]);
for (let element of elements) {
console.log(element);
}
1
2
4
Returns a sequence representing the intersection of the sequences - elements present in both sequences.
let elements = linq([1, 2, 3, 4, 5, 6]).intersect([3, 5, 6, 7, 8]);
for (let element of elements) {
console.log(element);
}
3
5
6
Concatenates the sequences together.
let elements = linq([1, 2, 3]).concat([4, 5, 6]);
for (let element of elements) {
console.log(element);
}
1
2
3
4
5
6
Performs a union operation on the current sequence and the provided sequence and returns a sequence of unique elements present in the both sequences.
let elements = linq([1, 2, 3, 3, 4, 5]).union([4, 5, 5, 6]);
for (let element of elements) {
console.log(element);
}
1
2
3
4
5
6
Reduces the sequence into a value using an accumulator function.
let people = [
{ name: "Ivan", age: 20 },
{ name: "Deyan", age: 22 }
];
let sumOfAges = linq(people).aggregate(0, (total, person) => total += person.age);
console.log(sumOfAges);
42
Provides a sliding window of elements from the sequence. By default the windows slides 1 element over. A second parameter may be provided to change the number of elements being skipped.
let windows = linq([1, 2, 3, 4, 5, 6]).windowed(3, 2);
for (let window of windows) {
console.log(window);
}
[ 1, 2, 3 ]
[ 3, 4, 5 ]
[ 5, 6 ]
Splits the sequence into batches/cunks of the specified size.
let batches = linq([1, 2, 3, 4, 5, 6, 7, 8]).batch(3);
for (let batch of batches) {
console.log(batch);
}
[ 1, 2, 3 ]
[ 4, 5, 6 ]
[ 7, 8 ]
Checks if any of the elements match the provided predicate.
let containsEven = linq([1, 2, 4, 6]).any(n => n % 2 === 0);
console.log(containsEven);
true
Checks if all of the elements match the provided predicate.
let areAllEvent = linq([1, 2, 4, 6]).all(n => n % 2 === 0);
console.log(areAllEvent);
false
Gets the min element in a sequence according to a transform function.
let people = [
{ name: "Ivan", age: 25 },
{ name: "Deyan", age: 22 }
];
let youngest = linq(people).min(p => p.age);
console.log(youngest);
{ name: 'Deyan', age: 22 }
Gets the max element in a sequence according to a transform function.
let people = [
{ name: "Ivan", age: 25 },
{ name: "Deyan", age: 22 }
];
let oldest = linq(people).max(p => p.age);
console.log(oldest);
{ name: "Ivan", age: 25 }
Gets the averege value for a sequence.
let people = [
{ name: "Ivan", age: 25 },
{ name: "Deyan", age: 22 }
];
let averageAge = linq(people).average(p => p.age);
console.log(averageAge);
23.5
Tests the equality of two seuqneces by checking each corresponding pair of elements against the provided predicate. If a predicate is not provided the elements will be compared using the strict equality (===) operator.
let first = [1, 2, 3];
let second = [1, 2, 3];
let areEqual = linq(first).sequenceEquals(second);
console.log(areEqual);
true
Gets the index of the element in the sequence.
let numbers = [1, 2, 3];
let indexOfTwo = linq(numbers).indexOf(2);
console.log(indexOfTwo);
1
Gets the element at an index.
let numbers = [1, 2, 3];
let elementAtIndexOne = linq(numbers).elementAt(1);
console.log(elementAtIndexOne);
2
Gets the first element of the iterable.
let numbers = [1, 2, 3];
let firstElement = linq(numbers).first();
console.log(firstElement);
1
Gets the first element of the sequence. If a predicate is provided the first element matching the predicated will be returned. If there aren't any matching elements or if the sequence is empty a default value provided by the defaultInitializer will be returned.
let numbers = [1, 2, 3];
let firstEvenElement = linq(numbers).firstOrDefault(n => n % 2 === 0);
let firstElementLargerThanFive = linq(numbers).firstOrDefault(n => n > 5, () => -1);
console.log(firstEvenElement);
console.log(firstElementLargerThanFive);
2
-1
Gets the last element of the iterable.
let numbers = [1, 2, 3];
let lastElement = linq(numbers).last();
console.log(lastElement);
3
Gets the last element of the sequence. If a predicate is provided the last element matching the predicated will be returned. If there aren't any matching elements or if the sequence is empty a default value provided by the defaultInitializer will be returned.
let numbers = [1, 2, 3, 4];
let lastEvenElement = linq(numbers).lastOrDefault(n => n % 2 === 0);
let lastElementLargerThanFive = linq(numbers).lastOrDefault(n => n > 5, () => -1);
console.log(lastEvenElement);
console.log(lastElementLargerThanFive);
4
-1
Calls a function for each element of the sequence. The function receives the element and its index in the seqeunce as parameters.
linq([1, 2, 3, 4]).forEach(console.log);
1 0
2 1
3 2
4 3
Turns the sequence to an array.
let array = linq([1, 2, 3, 4])
.concat([5, 6, 7])
.toArray();
console.log(array);
[ 1, 2, 3, 4, 5, 6, 7 ]
Counts the number of elements in the sequence.
let count = linq([1, 2, 3, 4]).count();
console.log(count);
4
Generates a sequence of numbers from start to end (if specified), increasing by the speficied step.
let limited = seq(1, 2, 10).toArray();
console.log(limited);
let unlimited = seq(1, 2).take(15).toArray();
console.log(unlimited);
[ 1, 3, 5, 7, 9 ]
[ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29 ]
The identity function (x => x). It takes an element and returns it. It can be useful for operaions like min, max, average, and in general in cases where we want the transform function to return the same element.
let average = linq([1, 2, 3, 4, 5, 6]).average(id);
console.log(average);
3.5