1.27.1 • Published 2 months ago

itertools-ts v1.27.1

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

IterTools for TypeScript and JavaScript

npm npm Coverage Status Build and test Minified Size License: MIT

IterTools Logo

Inspired by Python — designed for TypeScript.

Features

IterTools makes you an iteration superstar by providing two types of tools:

  • Loop iteration tools
  • Stream iteration tools

Loop Iteration Tools Example

import { multi } from 'itertools-ts';

for (const [letter, number] of multi.zip(['a', 'b'], [1, 2])) {
  console.log(`${letter}${number}`);  // a1, b2
}

// Async example
for await (const [letter, number] of multi.zipAsync(['a', 'b'], [1, 2])) {
  console.log(`${letter}${number}`);  // a1, b2
}

Stream Iteration Tools Example

import { Stream, AsyncStream } from 'itertools-ts';

const result1 = Stream.of([1, 1, 2, 2, 3, 4, 5])
  .distinct()             // [1, 2, 3, 4, 5]
  .map((x) => x**2)       // [1, 4, 9, 16, 25]
  .filter((x) => x < 10)  // [1, 4, 9]
  .toSum();               // 14

// Async example
const result2 = await AsyncStream.of([1, 1, 2, 2, 3, 4, 5])
  .distinct()             // [1, 2, 3, 4, 5]
  .map((x) => x**2)       // [1, 4, 9, 16, 25]
  .filter((x) => x < 10)  // [1, 4, 9]
  .toSum();               // 14

More about Streams

All functions work on iterable collections and iterators:

  • Array
  • Set
  • Map
  • String
  • Generator
  • Iterable
  • Iterator

Every function have an analog with "Async"-suffixed name for working with async iterable and iterators (e.g. zip and zipAsync):

  • AsyncIterable
  • AsyncIterator

If an asynchronous function takes other functions as input, they can also be asynchronous.

import { single } from 'itertools-ts';

const starWarsEpisodes = [1, 2, 3, 4, 5, 6, 7, 8, 9];

for await (const goodMovie of single.filterAsync(
  starWarsEpisodes,
  async (episode) => {
    return Promise.resolve(episode > 3 && episode < 8);
  }
)) {
  console.log(goodMovie);
}
// 4, 5, 6, 7

Setup

npm i itertools-ts

Similar Libraries in Other Languages

IterTools functionality is not limited to TypeScript and Python. Other languages have similar libraries. Familiar functionality is available when working in other languages.

Quick Reference

Loop Iteration Tools

Multi Iteration

IteratorDescriptionSync Code SnippetAsync Code Snippet
chainChain multiple iterables togethermulti.chain(list1, list2, ...)multi.chainAsync(list1, list2, ...)
zipIterate multiple collections simultaneously until the shortest iterator completesmulti.zip(list1, list2, ...)multi.zipAsync(list1, list2, ...)
zipEqualIterate multiple collections of equal length simultaneously, error if lengths not equalmulti.zipEqual(list1, list2, ...)multi.zipEqualAsync(list1, list2, ...)
zipFilledIterate multiple collections simultaneously until the longest iterator completes (with filler for uneven lengths)multi.zipFilled(filler, list1, list2, ...)multi.zipFilledAsync(filler, list1, list2, ...)
zipLongestIterate multiple collections simultaneously until the longest iterator completesmulti.zipLongest(list1, list2, ...)multi.zipLongestAsync(list1, list2, ...)

Single Iteration

IteratorDescriptionSync Code SnippetAsync Code Snippet
chunkwiseIterate by chunkssingle.chunkwise(data, chunkSize)single.chunkwiseAsync(data, chunkSize)
chunkwiseOverlapIterate by overlapped chunkssingle.chunkwiseOverlap(data, chunkSize, overlapSize)single.chunkwiseOverlapAsync(data, chunkSize, overlapSize)
compressFilter out elements not selectedsingle.compress(data, selectors)single.compressAsync(data, selectors)
dropWhileDrop elements while predicate is truesingle.dropWhile(data, predicate)single.dropWhileAsync(data, predicate)
enumerateEnumerates elements of collectionsingle.enumerate(data)single.enumerateAsync(data)
filterFilter for elements where predicate is truesingle.filter(data, predicate)single.filterAsync(data, predicate)
flatMapMap function onto items and flatten resultsingle.flatMap(data, mapper)single.flatMapAsync(data, mapper)
flattenFlatten multidimensional iterablesingle.flatten(data, [dimensions])single.flattenAsync(data, [dimensions])
groupByGroup data by a common elementsingle.groupBy(data, groupKeyFunction, [itemKeyFunc])single.groupByAsync(data, groupKeyFunction, [itemKeyFunc])
limitIterate up to a limitsingle.limit(data, limit)single.limitAsync(data, limit)
keysIterate keys of key-value pairssingle.keys(data)single.keysAsync(data)
mapMap function onto each itemsingle.map(data, mapper)single.mapAsync(data, mapper)
pairwiseIterate successive overlapping pairssingle.pairwise(data)single.pairwiseAsync(data)
repeatRepeat an item a number of timessingle.repeat(item, repetitions)single.repeatAsync(item, repetitions)
skipIterate after skipping elementssingle.skip(data, count, [offset])single.skipAsync(data, count, [offset])
sliceExtract a slice of the iterablesingle.slice(data, [start], [count], [step])single.sliceAsync(data, [start], [count], [step])
sortIterate a sorted collectionsingle.sort(data, [comparator])single.sortAsync(data, [comparator])
takeWhileIterate elements while predicate is truesingle.takeWhile(data, predicate)single.takeWhileAsync(data, predicate)
valuesIterate values of key-value pairssingle.values(data)single.valuesAsync(data)

Infinite Iteration

IteratorDescriptionCode Snippet
countCount sequentially foreverinfinite.count([start], [step])
cycleCycle through a collectioninfinite.cycle(iterable)
repeatRepeat an item foreverinfinite.repeat(item)

Math Iteration

IteratorDescriptionSync Code SnippetAsync Code Snippet
runningAverageRunning average accumulationmath.runningAverage(numbers, [initialValue])math.runningAverageAsync(numbers, [initialValue])
runningDifferenceRunning difference accumulationmath.runningDifference(numbers, [initialValue])math.runningDifferenceAsync(numbers, [initialValue])
runningMaxRunning maximum accumulationmath.runningMax(numbers, [initialValue])math.runningMax(numbers, [initialValue])
runningMinRunning minimum accumulationmath.runningMin(numbers, [initialValue])math.runningMinAsync(numbers, [initialValue])
runningProductRunning product accumulationmath.runningProduct(numbers, [initialValue])math.runningProductAsync(numbers, [initialValue])
runningTotalRunning total accumulationmath.runningTotal(numbers, [initialValue])math.runningTotalAsync(numbers, [initialValue])

Reduce

ReducerDescriptionSync Code SnippetAsync Code Snippet
toAverageMean average of elementsreduce.toAverage(numbers)reduce.toAverageAsync(numbers)
toCountReduce to length of iterablereduce.toCount(data)reduce.toCountAsync(data)
toFirstReduce to its first valuereduce.toFirst(data)reduce.toFirstAsync(data)
toFirstAndLastReduce to its first and last valuesreduce.toFirstAndLast(data)reduce.toFirstAndLastAsync(data)
toLastReduce to its last valuereduce.toLast(data)reduce.toLastAsync(data)
toMaxReduce to its greatest elementreduce.toMax(numbers, [compareBy])reduce.toMaxAsync(numbers, [compareBy])
toMinReduce to its smallest elementreduce.toMin(numbers, [compareBy])reduce.toMinAsync(numbers, [compareBy])
toMinMaxReduce to its lower and upper boundsreduce.toMinMax(numbers, [compareBy])reduce.toMinMaxAsync(numbers, [compareBy])
toProductReduce to the product of its elementsreduce.toProduct(numbers)reduce.toProductAsync(numbers)
toRangeReduce to difference of max and min valuesreduce.toRange(numbers)reduce.toRangeAsync(numbers)
toSumReduce to the sum of its elementsreduce.toSum(numbers)reduce.toSumAsync(numbers)
toValueReduce to value using callable reducerreduce.toValue(data, reducer, initialValue)reduce.toValueAsync(data, reducer, initialValue)

Set and multiset Iteration

IteratorDescriptionSync Code SnippetAsync Code Snippet
cartesianProductIterate cartesian product of iterablesset.cartesianProduct(...iterables)set.cartesianProductAsync(...iterables)
distinctIterate only distinct itemsset.distinct(data)set.distinctAsync(data)
intersectionIntersection of iterablesset.intersection(...iterables)set.intersectionAsync(...iterables)
partialIntersectionPartial intersection of iterablesset.partialIntersection(minCount, ...iterables)set.partialIntersectionAsync(minCount, ...iterables)
symmetricDifferenceSymmetric difference of iterablesset.symmetricDifference(...iterables)set.symmetricDifferenceAsync(...iterables)
unionUnion of iterablesset.union(...iterables)set.unionAsync(...iterables)

Summary

SummaryDescriptionSync Code SnippetAsync Code Snippet
allMatchTrue if all items are true according to predicatesummary.allMatch(data, predicate)summary.allMatchAsync(data, predicate)
allUniqueTrue if all elements in collection are uniquesummary.allUnique(data)summary.allUniqueAsync(data)
anyMatchTrue if any item is true according to predicatesummary.anyMatch(data, predicate)summary.anyMatchAsync(data, predicate)
exactlyNTrue if exactly n items are true according to predicatesummary.exactlyN(data, n, predicate)summary.exactlyNAsync(data, n, predicate)
isAsyncIterableTrue if given data is async iterablesummary.isAsyncIterable(data)
isIterableTrue if given data is iterablesummary.isIterable(data)
isIteratorTrue if given data is iteratorsummary.isIterator(data)
isReversedTrue if iterable reverse sortedsummary.isReversed(data)summary.isReversedAsync(data)
isSortedTrue if iterable sortedsummary.isSorted(data)summary.isSortedAsync(data)
isStringTrue if given data is stringsummary.isString(data)summary.isStringAsync(data)
noneMatchTrue if none of items true according to predicatesummary.noneMatch(data, predicate)summary.noneMatchAsync(data, predicate)
sameTrue if collections are the samesummary.same(...collections)summary.sameAsync(...collections)
sameCountTrue if collections have the same lengthssummary.sameCount(...collections)summary.sameCountAsync(...collections)

Transform

IteratorDescriptionSync Code SnippetAsync Code Snippet
teeIterate duplicate iterablestransform.tee(data, count)transform.teeAsync(data, count)
toArrayTransforms collection to arraytransform.toArray(data)transform.toArrayAsync(data)
toAsyncIterableTransforms collection to async iterabletransform.toAsyncIterable(data)
toAsyncIteratorTransforms collection to async iteratortransform.toAsyncIterator(data)
toIterableTransforms collection to iterabletransform.toIterable(data)
toIteratorTransforms collection to iteratortransform.toIterator(data)
toMapTransforms collection to maptransform.toMap(pairs)transform.toMapAsync(pairs)
toSetTransforms collection to settransform.toSet(data)transform.toSetAsync(data)

Stream and AsyncStream Iteration Tools

Stream Sources

SourceDescriptionSync Code SnippetAsync Code Snippet
ofCreate a stream from an iterableStream.of(iterable)AsyncStream.of(iterable)
ofEmptyCreate an empty streamStream.ofEmpty()AsyncStream.ofEmpty()
ofCountCreate an infinite count streamStream.ofCount([start], [step])AsyncStream.ofCount([start], [step])
ofCycleCreate an infinite cycle streamStream.ofCycle(iterable)AsyncStream.ofCycle(iterable)
ofRepeatCreate an infinite repeating streamStream.ofRepeat(item)AsyncStream.ofRepeat(item)

Stream Operations

OperationDescriptionCode Snippet
cartesianProductWithIterate cartesian product of iterable source with another iterable collectionsstream.cartesianProductWith(...iterables)
chainWithChain iterable source withs given iterables together into a single iterationstream.chainWith(...iterables)
chunkwiseIterate by chunksstream.chunkwise(chunkSize)
chunkwiseOverlapIterate by overlapped chunksstream.chunkwiseOverlap(chunkSize, overlap)
compressCompress source by filtering out data not selectedstream.compress(selectors)
distinctFilter out elements: iterate only unique itemsstream.distinct()
dropWhileDrop elements from the iterable source while the predicate function is truestream.dropWhile(predicate)
enumerateEnumerates elements of streamstream.enumerate()
filterFilter for only elements where the predicate function is truestream.filter(predicate)
flatMapMap function onto elements and flatten resultstream.flatMap(mapper)
flattenFlatten multidimensional streamstream.flatten([dimensions])
intersectionWithIntersect stream and given iterablesstream.intersectionWith(...iterables)
groupByGroup stram data by a common data elementstream.groupBy(groupKeyFunction, [itemKeyFunc])
keysIterate keys of key-value pairs from streamstream.keys()
limitLimit the stream's iterationstream.limit(limit)
mapMap function onto elementsstream.map(mapper)
pairwiseReturn pairs of elements from iterable sourcestream.pairwise()
partialIntersectionWithPartially intersect stream and given iterablesstream.partialIntersectionWith(minIntersectionCount, ...iterables)
runningAverageAccumulate the running average (mean) over iterable sourcestream.runningAverage([initialValue])
runningDifferenceAccumulate the running difference over iterable sourcestream.runningDifference([initialValue])
runningMaxAccumulate the running max over iterable sourcestream.runningMax([initialValue])
runningMinAccumulate the running min over iterable sourcestream.runningMin([initialValue])
runningProductAccumulate the running product over iterable sourcestream.runningProduct([initialValue])
runningTotalAccumulate the running total over iterable sourcestream.runningTotal([initialValue])
skipSkip some elements of the streamstream.skip(count, [offset])
sliceExtract a slice of the streamstream.slice([start], [count], [step])
sortSorts the streamstream.sort([comparator])
symmetricDifferenceWithSymmetric difference of stream and given iterablesstream.symmetricDifferenceWith(...iterables)
takeWhileReturn elements from the iterable source as long as the predicate is truestream.takeWhile(predicate)
unionWithUnion of stream and given iterablesstream.union(...iterables)
valuesIterate values of key-value pairs from streamstream.values()
zipWithIterate iterable source with another iterable collections simultaneouslystream.zipWith(...iterables)
zipEqualWithIterate iterable source with another iterable collections of equal lengths simultaneouslystream.zipEqualWith(...iterables)
zipFilledWithIterate iterable source with another iterable collections simultaneously (with filler)stream.zipFilledWith(filler, ...iterables)
zipLongestWithIterate iterable source with another iterable collections simultaneouslystream.zipLongestWith(...iterables)

Stream Terminal Operations

Transformation Terminal Operations
Terminal OperationDescriptionCode Snippet
teeReturns array of multiple identical Streamsstream.tee(count)
toArrayReturns array of stream elementsstream.toArray()
toMapReturns map of stream elements (key-value pairs)stream.toMap()
toSetReturns set of stream elementsstream.toSet()
Reduction Terminal Operations
Terminal OperationDescriptionCode Snippet
toAverageReduces stream to the mean average of its itemsstream.toAverage()
toCountReduces stream to its lengthstream.toCount()
toFirstReduces stream to its first valuestream.toFirst()
toFirstAndLastReduces stream to its first and last valuesstream.toFirstAndLast()
toLastReduces stream to its last valuestream.toLast()
toMaxReduces stream to its max valuestream.toMax([compareBy])
toMinReduces stream to its min valuestream.toMin([compareBy])
toMinReduce stream to its lower and upper boundsstream.toMinMax([compareBy])
toProductReduces stream to the product of its itemsstream.toProduct()
toRangeReduces stream to difference of max and min valuesstream.toRange()
toSumReduces stream to the sum of its itemsstream.toSum()
toValueReduces stream like array.reduce() functionstream.toValue(reducer, initialValue)
Summary Terminal Operations
Terminal OperationDescriptionCode Snippet
allMatchReturns true if all items in stream match predicatestream.allMatch(predicate)
allUniqueReturns true if all elements of stream are uniquestream.allUnique(predicate)
anyMatchReturns true if any item in stream matches predicatestream.anyMatch(predicate)
exactlyNReturns true if exactly n items are true according to predicatestream.exactlyN(n, predicate)
isReversedReturns true if stream is sorted in reverse descending orderstream.isReversed()
isSortedReturns true if stream is sorted in ascending orderstream.isSorted()
noneMatchReturns true if none of the items in stream match predicatestream.noneMatch(predicate)
sameWithReturns true if stream and all given collections are the samestream.sameWith(...collections)
sameCountWithReturns true if stream and all given collections have the same lengthsstream.sameCountWith(...collections)

Stream Debug Operations

Debug OperationDescriptionCode Snippet
peekPeek at each element between stream operationsstream.peek(peekFunc)
peekStreamPeek at the entire stream between operationsstream.peekStream(peekFunc)

Usage

Multi Iteration

Chain

Chain multiple iterables together into a single continuous sequence.

function* chain<T>(
  ...iterables: Array<Iterable<T> | Iterator<T>>
): Iterable<T>
import { multi } from 'itertools-ts';

const prequels = ['Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith'];
const originals = ['A New Hope', 'Empire Strikes Back', 'Return of the Jedi'];

for (const movie of multi.chain(prequels, originals)) {
  console.log(movie);
}
// 'Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith', 'A New Hope', 'Empire Strikes Back', 'Return of the Jedi'

Zip

Iterate multiple iterable collections simultaneously.

function* zip<T extends Array<Iterable<unknown> | Iterator<unknown>>>(
  ...iterables: T
): Iterable<ZipTuple<T, never>>
import { multi } from 'itertools-ts';

const languages = ['PHP', 'Python', 'Java', 'Go'];
const mascots = ['elephant', 'snake', 'bean', 'gopher'];

for (const [language, mascot] of multi.zip(languages, mascots)) {
  console.log(`The ${language} language mascot is an ${mascot}.`);
}
// The PHP language mascot is an elephant.
// ...

Zip works with multiple iterable inputs - not limited to just two.

import { multi } from 'itertools-ts';

const names          = ['Ryu', 'Ken', 'Chun Li', 'Guile'];
const countries      = ['Japan', 'USA', 'China', 'USA'];
const signatureMoves = ['hadouken', 'shoryuken', 'spinning bird kick', 'sonic boom'];

for (const [name, country, signatureMove] of multi.zip(names, countries, signatureMoves)) {
  const streetFighter = new StreetFighter(name, country, signatureMove);
}

Note: For uneven lengths, iteration stops when the shortest iterable is exhausted.

Zip Filled

Iterate multiple iterable collections simultaneously.

function* zipFilled<T extends Array<Iterable<unknown> | Iterator<unknown>>, F>(
  filler: F,
  ...iterables: T
): Iterable<ZipTuple<T, F>>

For uneven lengths, the exhausted iterables will produce filler value for the remaining iterations.

import { multi } from 'itertools-ts';

const letters = ['A', 'B', 'C'];
const numbers = [1, 2];

for (const [letter, number] of multi.zipFilled('filler', letters, numbers)) {
  // ['A', 1], ['B', 2], ['C', 'filler']
}

Zip Longest

Iterate multiple iterable collections simultaneously.

function* zipLongest<T extends Array<Iterable<unknown> | Iterator<unknown>>>(
  ...iterables: T
): Iterable<ZipTuple<T, undefined>>

For uneven lengths, the exhausted iterables will produce undefined for the remaining iterations.

import { multi } from 'itertools-ts';

const letters = ['A', 'B', 'C'];
const numbers = [1, 2];

for (const [letter, number] of multi.zipLongest(letters, numbers)) {
  // ['A', 1], ['B', 2], ['C', undefined]
}

Zip Equal

Iterate multiple iterable collections with equal lengths simultaneously.

Throws LengthException if lengths are not equal, meaning that at least one iterator ends before the others.

function* zipEqual<T extends Array<Iterable<unknown> | Iterator<unknown>>>(
  ...iterables: T
): Iterable<ZipTuple<T, never>>
import { multi } from 'itertools-ts';

const letters = ['A', 'B', 'C'];
const numbers = [1, 2, 3];

for (const [letter, number] of multi.zipEqual(letters, numbers)) {
    // ['A', 1], ['B', 2], ['C', 3]
}

Single Iteration

Chunkwise

Return elements in chunks of a certain size.

function* chunkwise<T>(
  data: Iterable<T>|Iterator<T>,
  chunkSize: number,
): Iterable<Array<T>>

Chunk size must be at least 1.

import { single } from 'itertools-ts';

const movies = [
    'Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith',
    'A New Hope', 'Empire Strikes Back', 'Return of the Jedi',
    'The Force Awakens', 'The Last Jedi', 'The Rise of Skywalker',
];
const trilogies = [];

for (const trilogy of single.chunkwise(movies, 3)) {
    trilogies.push(trilogy);
}
// [
//     ['Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith'],
//     ['A New Hope', 'Empire Strikes Back', 'Return of the Jedi'],
//     ['The Force Awakens', 'The Last Jedi', 'The Rise of Skywalker]',
// ]

Chunkwise Overlap

Return overlapped chunks of elements.

function* chunkwiseOverlap<T>(
  data: Iterable<T>|Iterator<T>,
  chunkSize: number,
  overlapSize: number,
  includeIncompleteTail: boolean = true,
): Iterable<Array<T>>
  • Chunk size must be at least 1.
  • Overlap size must be less than chunk size.
import { single } from 'itertools-ts';

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

for (const chunk of single.chunkwiseOverlap(numbers, 3, 1)) {
  // [1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]
}

Compress

Compress an iterable by filtering out data that is not selected.

function* compress<T>(
  data: Iterable<T> | Iterator<T>,
  selectors: Iterable<number|boolean> | Iterator<number|boolean>
): Iterable<T>
import { single } from 'itertools-ts';

const movies = [
  'Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith',
  'A New Hope', 'Empire Strikes Back', 'Return of the Jedi',
  'The Force Awakens', 'The Last Jedi', 'The Rise of Skywalker'
];
const goodMovies = [0, 0, 0, 1, 1, 1, 1, 0, 0];

for (const goodMovie of single.compress(movies, goodMovies)) {
  console.log(goodMovie);
}
// 'A New Hope', 'Empire Strikes Back', 'Return of the Jedi', 'The Force Awakens'

Drop While

Drop elements from the iterable while the predicate function is true.

Once the predicate function returns false once, all remaining elements are returned.

function* dropWhile<T>(
  data: Iterable<T>|Iterator<T>,
  predicate: (item: T) => boolean
): Iterable<T>
import { single } from 'itertools-ts';

const scores    = [50, 60, 70, 85, 65, 90];
const predicate = (x) => x < 70;

for (const score of single.dropWhile(scores, predicate)) {
  console.log(score);
}
// 70, 85, 65, 90

Enumerate

Enumerates elements of given collection.

function* enumerate<T>(data: Iterable<T>|Iterator<T>): Iterable<[number, T]>
import { single } from 'itertools-ts';

const letters = ['a', 'b', 'c', 'd', 'e'];

for (const item of single.enumerate(letters)) {
  // [[0, 'a'], [1, 'b'], [2, 'c'], [3, 'd'], [4, 'e']]
}

Filter

Filter out elements from the iterable only returning elements where the predicate function is true.

function* filter<T>(
  data: Iterable<T>|Iterator<T>,
  predicate: (datum: T) => boolean,
): Iterable<T>
import { single } from 'itertools-ts';

const starWarsEpisodes = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const goodMoviePredicate = (episode) => episode > 3 && episode < 8;

for (const goodMovie of single.filter(starWarsEpisodes, goodMoviePredicate)) {
  console.log(goodMovie);
}
// 4, 5, 6, 7

Flat Map

Map a function only the elements of the iterable and then flatten the results.

function* flatMap<TInput, TOutput>(
  data: Iterable<TInput>|Iterator<TInput>,
  mapper: FlatMapper<TInput, TOutput>,
): Iterable<TOutput>
import { single } from 'itertools-ts';

const data = [1, 2, 3, 4, 5];
const mapper = (item) => [item, -item];

for (number of single.flatMap(data, mapper)) {
  console.log(number);
}
// 1 -1 2 -2 3 -3 4 -4 5 -5

Flatten

Flatten a multidimensional iterable.

function* flatten(
  data: Iterable<unknown>|Iterator<unknown>,
  dimensions: number = Infinity,
): Iterable<unknown>
import { single } from 'itertools-ts';

const multidimensional = [1, [2, 3], [4, 5]];

const flattened = [];
for (const number of single.flatten(multidimensional)) {
    flattened.push(number);
}
// [1, 2, 3, 4, 5]

Group By

Group data by a common data element.

Iterate pairs of group name and collection of grouped items.

function* groupBy<T>(
  data: Iterable<T> | Iterator<T>,
  groupKeyFunction: (item: T) => string,
  itemKeyFunction?: (item: T) => string,
): Iterable<[string, Array<T>] | [string, Record<string, T>]>
  • The groupKeyFunction determines the key to group elements by.
  • The optional itemKeyFunction allows custom indexes within each group member.
  • Collection of grouped items may be an array or an object (depends on presence of itemKeyFunction param).
import { single } from 'itertools-ts';

const cartoonCharacters = [
    ['Garfield', 'cat'],
    ['Tom', 'cat'],
    ['Felix', 'cat'],
    ['Heathcliff', 'cat'],
    ['Snoopy', 'dog'],
    ['Scooby-Doo', 'dog'],
    ['Odie', 'dog'],
    ['Donald', 'duck'],
    ['Daffy', 'duck'],
];

const charactersGroupedByAnimal = {};
for (const [animal, characters] of single.groupBy(cartoonCharacters, (x) => x[1])) {
    charactersGroupedByAnimal[animal] = characters;
}
/*
{
  cat: [
    ['Garfield', 'cat'],
    ['Tom', 'cat'],
    ['Felix', 'cat'],
    ['Heathcliff', 'cat'],
  ],
  dog: [
    ['Snoopy', 'dog'],
    ['Scooby-Doo', 'dog'],
    ['Odie', 'dog'],
  ],
  duck: [
    ['Donald', 'duck'],
    ['Daffy', 'duck'],
  ],
}
*/

Keys

Iterate keys of key-value pairs.

function* keys<TKey, TValue>(
  collection: Iterable<[TKey, TValue]>|Iterator<[TKey, TValue]>,
): Iterable<TKey>
import { single } from 'itertools-ts';

const dict = new Map([['a', 1], ['b', 2], ['c', 3]]);

for (const key of single.keys(dict)) {
  console.log(key);
}
// 'a', 'b', 'c'

Limit

Iterate up to a limit.

Stops even if more data available if limit reached.

function* limit<T>(data: Iterable<T>|Iterator<T>, count: number): Iterable<T>
import { single } from 'itertools-ts';

const matrixMovies = ['The Matrix', 'The Matrix Reloaded', 'The Matrix Revolutions', 'The Matrix Resurrections'];
const limit = 1;

for (const goodMovie of single.limit(matrixMovies, limit)) {
    console.log(goodMovie);
}
// 'The Matrix' (and nothing else)

Map

Map a function onto each element.

function* map<TInput, TOutput>(
  data: Iterable<TInput>|Iterator<TInput>,
  mapper: (datum: TInput) => TOutput,
): Iterable<TOutput>
import { single } from 'itertools-ts';

const grades = [100, 99, 95, 98, 100];
const strictParentsOpinion = (g) => (g === 100) ? 'A' : 'F';

for (const actualGrade of single.map(grades, strictParentsOpinion)) {
  console.log(actualGrade);
}
// A, F, F, F, A

Pairwise

Returns successive overlapping pairs.

Returns empty generator if given collection contains fewer than 2 elements.

function* pairwise<T>(data: Iterable<T>|Iterator<T>): Iterable<Pair<T>>
import { single } from 'itertools-ts';

const friends = ['Ross', 'Rachel', 'Chandler', 'Monica', 'Joey', 'Phoebe'];

for (const [leftFriend, rightFriend] of single.pairwise(friends)) {
  console.log(`${leftFriend} and ${rightFriend}`);
}
// Ross and Rachel, Rachel and Chandler, Chandler and Monica, ...

Repeat

Repeat an item.

function* repeat<T>(item: T, repetitions: number): Iterable<T>
import { single } from 'itertools-ts';

data = 'Beetlejuice';
repetitions = 3;

for (const repeated of single.repeat(data, repetitions)) {
  console.log(repeated);
}
// 'Beetlejuice', 'Beetlejuice', 'Beetlejuice'

Skip

Skip n elements in the iterable after optional offset offset.

function* skip<T>(
  data: Iterable<T> | Iterator<T>,
  count: number,
  offset: number = 0
): Iterable<T>
import { single } from 'itertools-ts';

const movies = [
    'The Phantom Menace', 'Attack of the Clones', 'Revenge of the Sith',
    'A New Hope', 'The Empire Strikes Back', 'Return of the Jedi',
    'The Force Awakens', 'The Last Jedi', 'The Rise of Skywalker'
];

const prequelsRemoved = [];
for (const nonPrequel of Single.skip(movies, 3)) {
  prequelsRemoved.push(nonPrequel);
} // Episodes IV - IX

const onlyTheBest = [];
for (const nonSequel of Single.skip(prequelsRemoved, 3, 3)) {
  onlyTheBest.push(nonSequel);
}
// 'A New Hope', 'The Empire Strikes Back', 'Return of the Jedi'

Slice

Extract a slice of the iterable.

function* slice<T>(
  data: Iterable<T>|Iterator<T>,
  start: number = 0,
  count?: number,
  step: number = 1,
): Iterable<T>
import { single } from 'itertools-ts';

const olympics = [1992, 1994, 1996, 1998, 2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2020, 2022];
const winterOlympics = [];

for (const winterYear of single.slice(olympics, 1, 8, 2)) {
    winterOlympics.push(winterYear);
}
// [1994, 1998, 2002, 2006, 2010, 2014, 2018, 2022]

Sort

Iterate the collection sorted.

function* sort<T>(
  data: Iterable<T> | Iterator<T>,
  comparator?: Comparator<T>,
): Iterable<T>

Uses default sorting if optional comparator function not provided.

import { single } from 'itertools-ts';

const data = [3, 4, 5, 9, 8, 7, 1, 6, 2];

for (const datum of single.sort(data)) {
  console.log(datum);
}
// 1, 2, 3, 4, 5, 6, 7, 8, 9

Take While

Return elements from the iterable as long as the predicate is true.

Stops iteration as soon as the predicate returns false, even if other elements later on would eventually return true (different from filterTrue).

function* takeWhile<T>(
  data: Iterable<T> | Iterator<T>,
  predicate: (item: T) => boolean
): Iterable<T>
import { single } from 'itertools-ts';

const prices = [0, 0, 5, 10, 0, 0, 9];
const isFree = (price) => price == 0;

for (const freePrice of single.takeWhile(prices, isFree)) {
  console.log(freePrice);
}
// 0, 0

Values

Iterate values of key-value pairs.

function* values<TKey, TValue>(
  collection: Iterable<[TKey, TValue]>|Iterator<[TKey, TValue]>,
): Iterable<TValue>
import { single } from 'itertools-ts';

const dict = new Map([['a', 1], ['b', 2], ['c', 3]]);

for (const value of single.keys(dict)) {
  console.log(value);
}
// 1, 2, 3

Infinite Iteration

Count

Count sequentially forever.

function* count(start: number = 1, step: number = 1): Iterable<number>
import { infinite } from 'itertools-ts';

for (const i of infinite.count()) {
  console.log(i);
}
// 1, 2, 3, 4, 5, ...

Cycle

Cycle through the elements of a collection sequentially forever.

function* cycle<T>(iterable: Iterable<T> | Iterator<T>): Iterable<T>
import { infinite } from 'itertools-ts';

for (const item of infinite.cycle(['rock', 'paper', 'scissors'])) {
  console.log(item);
}
// 'rock', 'paper', 'scissors', 'rock', 'paper', 'scissors', 'rock', ...

Repeat

Repeat an item forever.

function* repeat<T>(item: T): Iterable<T>
import { infinite } from 'itertools-ts';

for (const item of infinite.repeat('bla')) {
  console.log(item);
}
// bla, bla, bla, bla, bla, ...

Math Iteration

Running Average

Accumulate the running average over a list of numbers.

function* runningAverage<T>(
  numbers: Iterable<T> | Iterator<T>,
  initialValue?: number
): Iterable<number>
import { math } from 'itertools-ts';

const grades = [100, 80, 80, 90, 85];

for (const runningAverage of math.runningAverage(grades)) {
  console.log(runningAverage);
}
// 100, 90, 86.667, 87.5, 87

Running Difference

Accumulate the running difference over a list of numbers.

function* runningDifference<T>(
  numbers: Iterable<T> | Iterator<T>,
  initialValue?: number
): Iterable<number>
import { math } from 'itertools-ts';

const credits = [1, 2, 3, 4, 5];

for (const runningDifference of math.runningDifference(credits)) {
    console.log(runningDifference);
}
// -1, -3, -6, -10, -15

Provide an optional initial value to lead off the running difference.

import { math } from 'itertools-ts';

const dartsScores   = [50, 50, 25, 50];
const startingScore = 501;

for (const runningScore of math.runningDifference(dartsScores, startingScore)) {
  console.log(runningScore);
}
// 501, 451, 401, 376, 326

Running Max

Accumulate the running maximum over a list of numbers.

function* runningMax<T>(
  numbers: Iterable<T> | Iterator<T>,
  initialValue?: number
): Iterable<number>
import { math } from 'itertools-ts';

const numbers = [1, 2, 1, 3, 5];

for (const runningMax of math.runningMax(numbers)) {
  console.log(runningMax);
}
// 1, 2, 2, 3, 5

Running Min

Accumulate the running minimum over a list of numbers.

function* runningMin<T>(
  numbers: Iterable<T> | Iterator<T>,
  initialValue?: number
): Iterable<number>
import { math } from 'itertools-ts';

const numbers = [3, 4, 2, 5, 1];

for (const runningMin of math.runningMin(numbers)) {
    console.log(runningMin);
}
// 3, 3, 2, 2, 1

Running Product

Accumulate the running product over a list of numbers.

function* runningProduct<T>(
  numbers: Iterable<T> | Iterator<T>,
  initialValue?: number
): Iterable<number>
import { math } from 'itertools-ts';

const numbers = [1, 2, 3, 4, 5];

for (const runningProduct of math.runningProduct(numbers)) {
  console.log(runningProduct);
}
// 1, 2, 6, 24, 120

Provide an optional initial value to lead off the running product.

import { math } from 'itertools-ts';

const numbers = [1, 2, 3, 4, 5];
const initialValue = 5;

for (const runningProduct of math.runningProduct(numbers, initialValue)) {
  console.log(runningProduct);
}
// 5, 5, 10, 30, 120, 600

Running Total

Accumulate the running total over a list of numbers.

function* runningTotal<T>(
  numbers: Iterable<T> | Iterator<T>,
  initialValue?: number
): Iterable<number>
import { math } from 'itertools-ts';

const prices = [1, 2, 3, 4, 5];

for (const runningTotal of math.runningTotal(prices)) {
    console.log(runningTotal);
}
// 1, 3, 6, 10, 15

Provide an optional initial value to lead off the running total.

import { math } from 'itertools-ts';

const prices = [1, 2, 3, 4, 5];
const initialValue = 5;

for (const runningTotal of math.runningTotal(prices, initialValue)) {
  console.log(runningTotal);
}
// 5, 6, 8, 11, 15, 20

Reduce

To Average

Reduces to the mean average.

Returns undefined if collection is empty.

function toAverage(
  data: Iterable<number> | Iterator<number>,
): number | undefined
import { reduce } from 'itertools-ts';

const grades = [100, 90, 95, 85, 94];

const finalGrade = reduce.toAverage(numbers);
// 92.8

To Count

Reduces iterable to its length.

function toCount(data: Iterable<unknown>|Iterator<unknown>): number
import { reduce } from 'itertools-ts';

const data = [1, 2, 3];

const length = reduce.toCount(data);
// 3

To First

Reduces iterable to its first element.

function toFirst<T>(data: Iterable<T> | Iterator<T>): T

Throws LengthException if collection is empty.

import { reduce } from 'itertools-ts';

const medals = ['gold', 'silver', 'bronze'];

const first = reduce.toFirst(medals);
// gold

To First And Last

Reduces iterable to its first and last elements.

function toFirstAndLast<T>(data: Iterable<T> | Iterator<T>): [T, T]

Throws LengthException if collection is empty.

import { reduce } from 'itertools-ts';

const medals = ['gold', 'silver', 'bronze'];

const result = reduce.toFirstAndLast(medals);
// [gold, bronze]

To Last

Reduces iterable to its last element.

function toLast<T>(data: Iterable<T> | Iterator<T>): T

Throws LengthException if collection is empty.

import { reduce } from 'itertools-ts';

const medals = ['gold', 'silver', 'bronze'];

const first = reduce.toFirst(medals);
// bronze

To Max

Reduces to the max value.

function toMax<TValue>(
  data: Iterable<TValue>|Iterator<TValue>,
  compareBy?: (datum: TValue) => Comparable,
): TValue|undefined
  • Optional callable param compareBy must return comparable value.
  • If compareBy is not provided then items of given collection must be comparable.
  • Returns undefined if collection is empty.
import { reduce } from 'itertools-ts';

const numbers = [5, 3, 1, 2, 4];

const result = reduce.toMax(numbers);
// 1

const movieRatings = [
  {
    title: 'The Matrix',
    rating: 4.7,
  },
  {
    title: 'The Matrix Reloaded',
    rating: 4.3,
  },
  {
    title: 'The Matrix Revolutions',
    rating: 3.9,
  },
  {
    title: 'The Matrix Resurrections',
    rating: 2.5,
  },
];
const compareBy = (movie) => movie.rating;

const lowestRatedMovie = reduce.toMin(movieRatings, compareBy);
// {
//   title: 'The Matrix',
//   rating: 4.7,
// }

To Min

Reduces to the min value.

function toMin<TValue>(
  data: Iterable<TValue>|Iterator<TValue>,
  compareBy?: (datum: TValue) => Comparable,
): TValue|undefined
  • Optional callable param compareBy must return comparable value.
  • If compareBy is not provided then items of given collection must be comparable.
  • Returns undefined if collection is empty.
import { reduce } from 'itertools-ts';

const numbers = [5, 3, 1, 2, 4];

const result = reduce.toMin(numbers);
// 1

const movieRatings = [
  {
    title: 'The Matrix',
    rating: 4.7,
  },
  {
    title: 'The Matrix Reloaded',
    rating: 4.3,
  },
  {
    title: 'The Matrix Revolutions',
    rating: 3.9,
  },
  {
    title: 'The Matrix Resurrections',
    rating: 2.5,
  },
];
const compareBy = (movie) => movie.rating;

const lowestRatedMovie = reduce.toMin(movieRatings, compareBy);
// {
//   title: 'The Matrix Resurrections',
//   rating: 2.5,
// }

To Min Max

Reduces collection to its lower and upper bounds.

function toMinMax<T>(
  data: Iterable<T> | Iterator<T>,
  compareBy?: (item: T) => Comparable
): [T?, T?]
  • Optional callable param compareBy must return comparable value.
  • If compareBy is not provided then items of given collection must be comparable.
  • Returns [undefined, undefined] if collection is empty.
import { reduce } from 'itertools-ts';

const numbers = [5, 3, 1, 2, 4];

const result = reduce.toMinMax(numbers);
// [1, 5]

const movieRatings = [
  {
    title: 'The Matrix',
    rating: 4.7,
  },
  {
    title: 'The Matrix Reloaded',
    rating: 4.3,
  },
  {
    title: 'The Matrix Revolutions',
    rating: 3.9,
  },
  {
    title: 'The Matrix Resurrections',
    rating: 2.5,
  },
];
const compareBy = (movie) => movie.rating;

const lowestRatedMovie = reduce.toMin(movieRatings, compareBy);
// [{
//   title: 'The Matrix Resurrections',
//   rating: 2.5,
// },
// {
//   title: 'The Matrix',
//   rating: 4.7,
// }]

To Product

Reduces to the product of its elements.

Returns undefined if collection is empty.

function toProduct(data: Iterable<number>|Iterator<number>): number|undefined
import { reduce } from 'itertools-ts';

const primeFactors = [5, 2, 2];

const number = reduce.toProduct(primeFactors);
// 20

To Range

Reduces given collection to its range (difference between max and min).

function toRange(numbers: Iterable<number|string> | Iterator<number|string>): number

Returns 0 if iterable source is empty.

import { reduce } from 'itertools-ts';

const grades = [100, 90, 80, 85, 95];

const range = reduce.toRange(numbers);
// 20

To Sum

Reduces to the sum of its elements.

function toSum(data: Iterable<number>|Iterator<number>): number
import { reduce } from 'itertools-ts';

const parts = [10, 20, 30];

const sum = reduce.toSum(parts);
// 60

To Value

Reduce elements to a single value using reducer function.

function toValue<TInput, TOutput>(
  data: Iterable<TInput>|Iterator<TInput>,
  reducer: (carry: TOutput|undefined, datum: TInput) => TOutput,
  initialValue?: TOutput,
): TOutput|undefined
import { reduce } from 'itertools-ts';

const input = [1, 2, 3, 4, 5];
const sum = (carry, item) => carry + item;

const result = reduce.toValue(input, sum, 0);
// 15

Set and multiset

Cartesian Product

Iterates cartesian product of given iterables.

function* cartesianProduct<T extends Array<Iterable<unknown> | Iterator<unknown>>>(
  ...iterables: T
): Iterable<ZipTuple<T, never>>
import { set } from 'itertools-ts';

const numbers = [1, 2];
const letters = ['a', 'b'];
const chars = ['!', '?'];

for (const tuple of set.cartesianProduct(numbers, letters, chars)) {
  console.log(tuple);
}
/*
  [1, 'a', '!'],
  [1, 'a', '?'],
  [1, 'b', '!'],
  [1, 'b', '?'],
  [2, 'a', '!'],
  [2, 'a', '?'],
  [2, 'b', '!'],
  [2, 'b', '?'],
*/

Distinct

Filter out elements from the iterable only returning distinct elements.

function* distinct<T>(
  data: Iterable<T>|Iterator<T>,
  compareBy?: (datum: T) => Comparable
): Iterable<T>

Always treats different instances of objects and arrays as unequal.

import { set } from 'itertools-ts';

const chessSet = ['rook', 'rook', 'knight', 'knight', 'bishop', 'bishop', 'king', 'queen', 'pawn', 'pawn'];

for (const chessPiece of set.distinct(chessSet)) {
  console.log(chessPiece);
}
// rook, knight, bishop, king, queen, pawn


const users = [
  { 'name': 'John', 'id': 1 },
  { 'name': 'Mary', 'id': 2 },
  { 'name': 'Mary', 'id': 3 },
  { 'name': 'John', 'id': 4 },
  { 'name': 'Jane', 'id': 5 },
];

for (const user of set.distinct(users, (item) => item['name'])) {
  console.log(user);
}
// { 'name': 'John', 'id': 1 }, { 'name': 'Mary', 'id': 2 }, { 'name': 'Jane', 'id': 5 }

Intersection

Iterates intersection of iterables.

function* intersection<T>(...iterables: Array<Iterable<T> | Iterator<T>>): Iterable<T>
  • Always treats different instances of objects and arrays as unequal.
  • If input iterables produce duplicate items, then multiset intersection rules apply.
import { set } from 'itertools-ts';

const chessPieces = ['rook', 'knight', 'bishop', 'queen', 'king', 'pawn'];
const shogiPieces = ['rook', 'knight', 'bishop', 'king', 'pawn', 'lance', 'gold general', 'silver general'];

for (const commonPiece of set.intersection(chessPieces, shogiPieces)) {
    console.log(commonPiece);
}
// rook, knight, bishop, king, pawn

Partial Intersection

Iterates M-partial intersection of iterables.

function* partialIntersection<T>(
  minIntersectionCount: number,
  ...iterables: Array<Iterable<T> | Iterator<T>>
): Iterable<T>
  • Always treats different instances of objects and arrays as unequal.
  • If input iterables produce duplicate items, then multiset intersection rules apply.
import { set } from 'itertools-ts';

const staticallyTyped    = ['c++', 'java', 'c#', 'go', 'haskell'];
const dynamicallyTyped   = ['php', 'python', 'javascript', 'typescript'];
const supportsInterfaces = ['php', 'java', 'c#', 'typescript'];

for (const language of set.partialIntersection(2, staticallyTyped, dynamicallyTyped, supportsInterfaces)) {
    console.log(language);
}
// c++, java, c#, go, php

Symmetric difference

Iterates the symmetric difference of iterables.

function* symmetricDifference<T>(
  ...iterables: Array<Iterable<T> | Iterator<T>>
): Iterable<T>
  • Always treats different instances of objects and arrays as unequal.
  • If input iterables produce duplicate items, then multiset difference rules apply.
import { set } from 'itertools-ts';

const a = [2, 3, 4, 7];
const b = [2, 3, 5, 8];
const c = [2, 3, 6, 9];

for (const item of set.symmetricDifference(a, b, c)) {
  console.log(item);
}
// 4, 5, 6, 7, 8, 9

Union

Iterates the union of iterables.

function* union<T>(...iterables: Array<Iterable<T> | Iterator<T>>): Iterable<T>
  • Always treats different instances of objects and arrays as unequal.
  • If input iterables produce duplicate items, then multiset difference rules apply.
import { set } from 'itertools-ts';

const a = [1, 2, 3];
const b = [2, 3, 4];
const c = [3, 4, 5];

for (const item of set.symmetricDifference(a, b, c)) {
    console.log(item);
}
// 1, 2, 3, 4, 5

Summary

All Match

Returns true if all elements match the predicate function.

function allMatch<T>(
  data: Iterable<T> | Iterator<T>,
  predicate: (item: T) => boolean
): boolean

Empty collections return true.

import { summary } from "itertools-ts";

const finalFantasyNumbers = [4, 5, 6];
const isOnSuperNintendo   = (ff) => ff >= 4 && ff <= 6;

const trueResult = summary.allMatch(finalFantasyNumbers, isOnSuperNintendo);
// true

const isOnPlaystation = (ff) => ff >= 7 && ff <= 9;

const falseResult = summary.allMatch(finalFantasyNumbers, isOnPlaystation);
// false

All Unique

Return true if all elements in given collection are unique.

function allUnique(data: Iterable<unknown> | Iterator<unknown>): boolean

Empty collections return true.

Considers different instances of data containers to be different, even if they have the same content.

import { summary } from "itertools-ts";

const uniqueNumbers = [1, 2, 3, 4, 5];
summary.allUnique(uniqueNumbers);
// true

const notUniqueNumbers = [1, 1, 2, 2, 3];
summary.allUnique(notUniqueNumbers);
// false

Any Match

Returns true if any element matches the predicate function.

function anyMatch<T>(
  data: Iterable<T> | Iterator<T>,
  predicate: (item: T) => boolean
): boolean

Empty collections return false.

import { summary } from "itertools-ts";

const answers          = ['fish', 'towel', 42, "don't panic"];
const isUltimateAnswer = (a) => a == 42;

const trueResult = summary.anyMatch(answers, isUltimateAnswer);
// true

Exactly N

Returns true if exactly n items are true according to a predicate function.

  • Predicate is optional.
  • Default predicate is boolean value of each item.
function exactlyN<T>(
  data: Iterable<T> | Iterator<T>,
  n: number,
  predicate?: (item: T) => boolean,
): boolean
import { summary } from "itertools-ts";

const twoTruthsAndALie = [true, true, false];
const n = 2;

const trueResult = summary.exactlyN(twoTruthsAndALie, n);
// true

const ages = [18, 21, 24, 54];
const m = 4;
const predicate = (age) => age >= 21;

const falseResult = Summary::exactlyN(ages, m, predicate);
// false

Is Async Iterable

Returns true if given data is an AsyncIterable instance.

function isAsyncIterable(input: unknown): boolean
import { summary } from "itertools-ts";

const input = [1, 2, 3, 4, 5];

summary.isIterable(input); // false
summary.isIterable(input[Symbol.asyncIterator]()) // false
summary.isIterable(1); // false

Is Iterable

Returns true if given data is an Iterable instance.

function is
1.27.1

2 months ago

1.25.0

6 months ago

1.27.0

5 months ago

1.26.0

5 months ago

1.24.0

6 months ago

1.23.0

11 months ago

1.21.0

1 year ago

1.22.0

1 year ago

1.20.0

1 year ago

1.19.0

1 year ago

1.18.0

1 year ago

1.17.0

1 year ago

1.16.1

1 year ago

1.16.0

1 year ago

1.15.0

1 year ago

1.14.0

1 year ago

1.13.0

1 year ago

1.12.0

1 year ago

1.11.0

1 year ago

1.10.0

1 year ago

1.9.0

1 year ago

1.8.0

1 year ago

1.7.0

1 year ago

1.6.1

1 year ago

1.6.0

1 year ago

1.5.0

1 year ago

1.4.0

1 year ago

1.3.0

1 year ago

1.2.0

1 year ago

1.1.0

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.17.3

1 year ago

0.17.2

1 year ago

0.17.1

1 year ago

0.17.0

1 year ago

0.16.1

1 year ago

0.16.0

1 year ago

0.15.0

1 year ago

0.14.0

1 year ago

0.13.0

1 year ago

0.12.0

1 year ago

0.11.0

1 year ago

0.10.0

1 year ago

0.9.0

1 year ago

0.8.0

1 year ago

0.7.2

1 year ago

0.7.1

1 year ago

0.7.0

1 year ago

0.6.0

1 year ago

0.5.0

1 year ago

0.4.0

1 year ago

0.3.3

1 year ago

0.3.2

1 year ago

0.3.1

1 year ago

0.3.0

1 year ago

0.2.0

1 year ago

0.1.25

1 year ago

0.1.24

1 year ago

0.1.23

1 year ago

0.1.22

1 year ago

0.1.21

1 year ago

0.1.20

1 year ago

0.1.19

1 year ago

0.1.18

1 year ago

0.1.17

1 year ago

0.1.16

1 year ago

0.1.15

1 year ago

0.1.14

1 year ago

0.1.12

1 year ago

0.1.11

1 year ago

0.1.10

1 year ago

0.1.9

1 year ago

0.1.8

1 year ago

0.1.7

1 year ago

0.1.6

1 year ago

0.1.5

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago