Giter VIP home page Giter VIP logo

itertools's Introduction

npm Build Status Bundle size for itertools

A JavaScript port of Python's awesome itertools standard library.

Usage example:

>>> import { izip, cycle } from 'itertools';
>>>
>>> const xs = [1, 2, 3, 4];
>>> const ys = ['hello', 'there'];
>>> for (const [x, y] of izip(xs, cycle(ys))) {
>>>     console.log(x, y);
>>> }
1 'hello'
2 'there'
3 'hello'
4 'there'

About argument order

In Python, many of the itertools take a function as an argument. In the JS port of these we initially kept these orderings the same to stick closely to the Python functions, but in practice, it turns out to be more pragmatic to flip them, so the function gets to be the second param. Example:

In Python:

map(fn, items)

But in JavaScript:

map(items, fn)

The rationale for this flipping of argument order is because in practice, the function bodies can span multiple lines, in which case the following block will remaing aesthetically pleasing:

import { map } from 'itertools';

const numbers = [1, 2, 3];
const squares = map(numbers, (n) => {
    //
    // Do something wild with these numbers here
    //
    // ...
    return n * n;
});

API

The itertools package consists of a few building blocks:

Ports of builtins

# every(iterable: Iterable<T>, keyFn?: Predicate<T>): boolean <>

Returns true when every of the items in iterable are truthy. An optional key function can be used to define what truthiness means for this specific collection.

Examples:

every([]); // => true
every([0]); // => false
every([0, 1, 2]); // => false
every([1, 2, 3]); // => true

Examples with using a key function:

every([2, 4, 6], (n) => n % 2 === 0); // => true
every([2, 4, 5], (n) => n % 2 === 0); // => false

# some(iterable: Iterable<T>, keyFn?: Predicate<T>): boolean <>

Returns true when some of the items in iterable are truthy. An optional key function can be used to define what truthiness means for this specific collection.

Examples:

some([]); // => false
some([0]); // => false
some([0, 1, null, undefined]); // => true

Examples with using a key function:

some([1, 4, 5], (n) => n % 2 === 0); // => true
some([{ name: 'Bob' }, { name: 'Alice' }], (person) => person.name.startsWith('C')); // => false

# contains(haystack: Iterable<T>, needle: T): boolean <>

Returns true when some of the items in the iterable are equal to the target object.

Examples:

contains([], 'whatever'); // => false
contains([3], 42); // => false
contains([3], 3); // => true
contains([0, 1, 2], 2); // => true

# enumerate(iterable: Iterable<T>, start: number = 0): Iterable<[number, T]> <>

Returns an iterable of enumeration pairs. Iterable must be a sequence, an iterator, or some other object which supports iteration. The elements produced by returns a tuple containing a counter value (starting from 0 by default) and the values obtained from iterating over given iterable.

Example:

import { enumerate } from 'itertools';

console.log([...enumerate(['hello', 'world'])]);
// [0, 'hello'], [1, 'world']]

# filter(iterable: Iterable<T>, predicate: Predicate<T>): T[] <>

Eager version of ifilter.

# iter(iterable: Iterable<T>): Iterator<T> <>

Returns an iterator object for the given iterable. This can be used to manually get an iterator for any iterable datastructure. The purpose and main use case of this function is to get a single iterator (a thing with state, think of it as a "cursor") which can only be consumed once.

# map(iterable: Iterable<T>, mapper: (item: T) => V): V[] <>

Eager version of imap.

# max(iterable: Iterable<T>, keyFn?: (item: T) => number): T | undefined <>

Return the largest item in an iterable. Only works for numbers, as ordering is pretty poorly defined on any other data type in JS. The optional keyFn argument specifies a one-argument ordering function like that used for sorted.

If the iterable is empty, undefined is returned.

If multiple items are maximal, the function returns either one of them, but which one is not defined.

# min(iterable: Iterable<T>, keyFn?: (item: T) => number): T | undefined <>

Return the smallest item in an iterable. Only works for numbers, as ordering is pretty poorly defined on any other data type in JS. The optional keyFn argument specifies a one-argument ordering function like that used for sorted.

If the iterable is empty, undefined is returned.

If multiple items are minimal, the function returns either one of them, but which one is not defined.

# range(stop: number): Iterable<number> <>
# range(start: number, stop: number, step: number = 1): Iterable<number> <>

Returns an iterator producing all the numbers in the given range one by one, starting from start (default 0), as long as i < stop, in increments of step (default 1).

range(a) is a convenient shorthand for range(0, a).

Various valid invocations:

range(5)           // [0, 1, 2, 3, 4]
range(0, 5)        // [0, 1, 2, 3, 4]
range(0, 5, 2)     // [0, 2, 4]
range(5, 0, -1)    // [5, 4, 3, 2, 1]
range(-3)          // []

For a positive step, the iterator will keep producing values n as long as the stop condition n < stop is satisfied.

For a negative step, the iterator will keep producing values n as long as the stop condition n > stop is satisfied.

The produced range will be empty if the first value to produce already does not meet the value constraint.

# reduce(iterable: Iterable<T>, reducer: (O, T, number) => O, start: O): O <>
# reduce(iterable: Iterable<T>, reducer: (T, T, number) => T): T | undefined <>

Apply function of two arguments cumulatively to the items of sequence, from left to right, so as to reduce the sequence to a single value. For example:

reduce([1, 2, 3, 4, 5], (total, x) => total + x, 0);

calculates

(((((0+1)+2)+3)+4)+5)

The left argument, total, is the accumulated value and the right argument, x, is the update value from the sequence.

Without an explicit initializer arg:

reduce([1, 2, 3, 4, 5], (total, x) => total + x);

it calculates

((((1+2)+3)+4)+5)

# sorted(iterable: Iterable<T>, keyFn?: (item: T) => Primitive, reverse?: boolean): T[] <>

Return a new sorted list from the items in iterable.

Has two optional arguments:

  • keyFn specifies a function of one argument providing a primitive identity for each element in the iterable. that will be used to compare. The default value is to use a default identity function that is only defined for primitive types.

  • reverse is a boolean value. If true, then the list elements are sorted as if each comparison were reversed.

# sum(iterable: Iterable<number>): number <>

Sums the items of an iterable from left to right and returns the total. The sum will defaults to 0 if the iterable is empty.

# zip(xs: Iterable<T1>, ys: Iterable<T2>): [T1, T2][] <>
# zip3(xs: Iterable<T1>, ys: Iterable<T2>, zs: Iterable<T3>): [T1, T2, T3][] <>

Eager version of izip / izip3.

Ports of itertools

# chain(...iterables: Iterable<T>[]): Iterable<T> <>

Returns an iterator that returns elements from the first iterable until it is exhausted, then proceeds to the next iterable, until all of the iterables are exhausted. Used for treating consecutive sequences as a single sequence.

# compress(iterable: Iterable<T>, selectors: Iterable<boolean>): T[] <>

Eager version of icompress.

# count(start: number, step: number): Iterable<number> <>

Returns an iterator that counts up values starting with number start (default 0), incrementing by step. To decrement, use a negative step number.

# cycle(iterable: Iterable<T>): Iterable<T> <>

Returns an iterator producing elements from the iterable and saving a copy of each. When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely.

# dropwhile(iterable: Iterable<T>, predicate: (item: T) => boolean): Iterable<T> <>

Returns an iterator that drops elements from the iterable as long as the predicate is true; afterwards, returns every remaining element. Note: the iterator does not produce any output until the predicate first becomes false.

# groupby(iterable: Iterable<T>, keyFcn: (item: T) => Primitive): Iterable<[Primitive, Iterable<T>]> <>

Make an Iterable that returns consecutive keys and groups from the iterable. The key is a function computing a key value for each element. If not specified, key defaults to an identity function and returns the element unchanged. Generally, the iterable needs to already be sorted on the same key function.

The operation of groupby() is similar to the uniq filter in Unix. It generates a break or new group every time the value of the key function changes (which is why it is usually necessary to have sorted the data using the same key function). That behavior differs from SQL’s GROUP BY which aggregates common elements regardless of their input order.

The returned group is itself an iterator that shares the underlying iterable with groupby(). Because the source is shared, when the groupby() object is advanced, the previous group is no longer visible. So, if that data is needed later, it should be stored as an array.

# icompress(iterable: Iterable<T>, selectors: Iterable<boolean>): Iterable<T> <>

Returns an iterator that filters elements from data returning only those that have a corresponding element in selectors that evaluates to true. Stops when either the data or selectors iterables has been exhausted.

# ifilter(iterable: Iterable<T>, predicate: Predicate<T>): Iterable<T> <>

Returns an iterator that filters elements from iterable returning only those for which the predicate is true.

# imap(iterable: Iterable<T>, mapper: (item: T) => V): Iterable<V> <>

Returns an iterator that computes the given mapper function using arguments from each of the iterables.

# islice(iterable: Iterable<T>[start: number], stop: number[, step: number]): Iterable<T> <>

Returns an iterator that returns selected elements from the iterable. If start is non-zero, then elements from the iterable are skipped until start is reached. Then, elements are returned by making steps of step (defaults to 1). If set to higher than 1, items will be skipped. If stop is provided, then iteration continues until the iterator reached that index, otherwise, the iterable will be fully exhausted. islice() does not support negative values for start, stop, or step.

# izip(xs: Iterable<T1>, ys: Iterable<T2>): Iterable<[T1, T2]> <>
# izip3(xs: Iterable<T1>, ys: Iterable<T2>, zs: Iterable<T3>): Iterable<[T1, T2, T3]> <>

Returns an iterator that aggregates elements from each of the iterables. Used for lock-step iteration over several iterables at a time. When iterating over two iterables, use izip2. When iterating over three iterables, use izip3, etc. izip is an alias for izip2.

# izipLongest(xs: Iterable<T1>, ys: Iterable<T2>, filler?: D): Iterable<[T1 | D, T2 | D]> <>
# izipLongest3(xs: Iterable<T1>, ys: Iterable<T2>, zs: Iterable<T3>, filler?: D): Iterable<[T1 | D, T2 | D, T3 | D]> <>

Returns an iterator that aggregates elements from each of the iterables. If the iterables are of uneven length, missing values are filled-in with fillvalue. Iteration continues until the longest iterable is exhausted.

# izipMany(...iters: Iterable<T>[]): Iterable<T[]> <>

Like the other izips (izip, izip3, etc), but generalized to take an unlimited amount of input iterables. Think izip(*iterables) in Python.

# permutations(iterable: Iterable<T>, r: number = undefined): Iterable<T[]> <>

Return successive r-length permutations of elements in the iterable.

If r is not specified, then r defaults to the length of the iterable and all possible full-length permutations are generated.

Permutations are emitted in lexicographic sort order. So, if the input iterable is sorted, the permutation tuples will be produced in sorted order.

Elements are treated as unique based on their position, not on their value. So if the input elements are unique, there will be no repeat values in each permutation.

# repeat(thing: T, times: number = undefined): Iterable<T> <>

Returns an iterator that produces values over and over again. Runs indefinitely unless the times argument is specified.

# takewhile(iterable: Iterable<T>, predicate: (item: T) => boolean): Iterable<T> <>

Returns an iterator that produces elements from the iterable as long as the predicate is true.

# zipLongest(xs: Iterable<T1>, ys: Iterable<T2>, filler?: D): [T1 | D, T2 | D][] <>
# zipLongest3(xs: Iterable<T1>, ys: Iterable<T2>, zs: Iterable<T3>, filler?: D): [T1 | D, T2 | D, T3 | D][] <>

Eager version of izipLongest and friends.

# zipMany(...iters: Iterable<T>[]): T[][] <>

Eager version of izipMany.

Ports of more-itertools

# chunked(iterable: Iterable<T>, size: number): Iterable<T[]> <>

Break iterable into lists of length size:

>>> [...chunked([1, 2, 3, 4, 5, 6], 3)]
[[1, 2, 3], [4, 5, 6]]

If the length of iterable is not evenly divisible by size, the last returned list will be shorter:

>>> [...chunked([1, 2, 3, 4, 5, 6, 7, 8], 3)]
[[1, 2, 3], [4, 5, 6], [7, 8]]

# flatten(iterableOfIterables: Iterable<Iterable<T>>): Iterable<T> <>

Return an iterator flattening one level of nesting in a list of lists:

>>> [...flatten([[0, 1], [2, 3]])]
[0, 1, 2, 3]

# intersperse(value: T, iterable: Iterable<T>): Iterable<T> <>

Intersperse filler element value among the items in iterable.

>>> [...intersperse(-1, range(1, 5))]
[1, -1, 2, -1, 3, -1, 4]

# itake(n: number, iterable: Iterable<T>): Iterable<T> <>

Returns an iterable containing only the first n elements of the given iterable.

# pairwise(iterable: Iterable<T>): Iterable<[T, T]> <>

Returns an iterator of paired items, overlapping, from the original. When the input iterable has a finite number of items n, the outputted iterable will have n - 1 items.

>>> pairwise([8, 2, 0, 7])
[(8, 2), (2, 0), (0, 7)]

# partition(iterable: Iterable<T>, predicate: Predicate<T>): [T[], T[]] <>

Returns a 2-tuple of arrays. Splits the elements in the input iterable into either of the two arrays. Will fully exhaust the input iterable. The first array contains all items that match the predicate, the second the rest:

>>> const isOdd = x => x % 2 !== 0;
>>> const iterable = range(10);
>>> const [odds, evens] = partition(iterable, isOdd);
>>> odds
[1, 3, 5, 7, 9]
>>> evens
[0, 2, 4, 6, 8]

# roundrobin(...iterables: Iterable<T>[]): Iterable<T> <>

Yields the next item from each iterable in turn, alternating between them. Continues until all items are exhausted.

>>> [...roundrobin([1, 2, 3], [4], [5, 6, 7, 8])]
[1, 4, 5, 2, 6, 3, 7, 8]

# heads(...iterables: Iterable<T>[]): Iterable<T[]> <>

Like roundrobin(), but will group the output per "round".

>>> [...heads([1, 2, 3], [4], [5, 6, 7, 8])]
[[1, 4, 5], [2, 6], [3, 7], [8]]

# take(n: number, iterable: Iterable<T>): T[] <>

Eager version of itake.

# uniqueEverseen(iterable: Iterable<T>, keyFn?: (item: T) => Primitive): Iterable<T> <>

Yield unique elements, preserving order.

>>> [...uniqueEverseen('AAAABBBCCDAABBB')]
['A', 'B', 'C', 'D']
>>> [...uniqueEverseen('AbBCcAB', s => s.toLowerCase())]
['A', 'b', 'C']

# uniqueJustseen(iterable: Iterable<T>, keyFn?: (item: T) => Primitive): Iterable<T> <>

Yields elements in order, ignoring serial duplicates.

>>> [...uniqueJustseen('AAAABBBCCDAABBB')]
['A', 'B', 'C', 'D', 'A', 'B']
>>> [...uniqueJustseen('AbBCcAB', s => s.toLowerCase())]
['A', 'b', 'C', 'A', 'B']

# dupes(iterable: Iterable<T>, keyFn?: (item: T) => Primitive): Iterable<T[]> <>

Yield only elements from the input that occur more than once. Needs to consume the entire input before being able to produce the first result.

>>> [...dupes('AAAABCDEEEFABG')]
[['A', 'A', 'A', 'A', 'A'], ['E', 'E', 'E'], ['B', 'B']]
>>> [...dupes('AbBCcAB', s => s.toLowerCase())]
[['b', 'B', 'B'], ['C', 'c'], ['A', 'A']]

Additions

# compact(iterable: Iterable<T | null | undefined>): T[] <>

Eager version of icompact.

# compactObject(obj: Record<K, V | null | undefined>): Record<K, V> <>

Removes all "nullish" values from the given object. Returns a new object.

>>> compactObject({ a: 1, b: undefined, c: 0, d: null })
{ a: 1, c: 0, d: null }

# find(iterable: Iterable<T>, keyFn?: Predicate<T>): T | undefined <>

Returns the first item in the iterable for which the predicate holds, if any. If no such item exists, undefined is returned. If no default predicate is given, the first value from the iterable is returned.

# first(iterable: Iterable<T>, keyFn?: Predicate<T>): T | undefined <>

Almost the same as find(), except when no explicit predicate function is given. find() will always return the first value in the iterable, whereas first() will return the first non-undefined value in the iterable.

Prefer using find(), as its behavior is more intuitive and predictable.

# flatmap(iterable: Iterable<T>, mapper: (item: T) => Iterable<S>): Iterable<S> <>

Returns 0 or more values for every value in the given iterable. Technically, it's just calling map(), followed by flatten(), but it's a very useful operation if you want to map over a structure, but not have a 1:1 input-output mapping. Instead, if you want to potentially return 0 or more values per input element, use flatmap():

For example, to return all numbers n in the input iterable n times:

>>> const repeatN = n => repeat(n, n);
>>> [...flatmap([0, 1, 2, 3, 4], repeatN)]
[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]  // note: no 0

# icompact(iterable: Iterable<T | null | undefined>): Iterable<T> <>

Returns an iterable, filtering out any "nullish" values from the input iterable.

>>> compact([1, 2, undefined, 3])
[1, 2, 3]

itertools's People

Contributors

dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar ghickman avatar nvie avatar palra avatar quangloc99 avatar refi64 avatar verkholantsev avatar zumpiez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

itertools's Issues

Please add Typescript support

I'd bet there's a big intersection between users who like iterator-style (functional-style) programming and those who like static types, so please add support for Typescript!
I'd love to use this library, as I really like Python's itertools API, but instead I'll have to use iter-tools 😑

Proposal: Add `find` and `findValue` functions

NOTE: There is an upcoming TC39 proposal, proposal-iterator-helpers, which will provide its own way to run a find function like the one(s) below, but until then, the below function(s) seek to accomplish the same with full safety.

In line with the other iterable functions, having a built-in function that can retrieve the first matched entry or value in an iterable based on an accessor and an optional final parameter for the desired return value would be very helpful.

Some iterables have a values() method to retrieve the values for that iterable. I think it could be useful to expose a separate method that accounts for that.

Definitions

Background helper functions and type helpers

There is probably something already like this used elsewhere in this package to use instead, but including it for the sake of completeness in my definitions and examples

type TransformFunction<T, U> = (params: T) => U;
type KeyOrTransform<T, U> = keyof T | TransformFunction<T, U>;
type PossiblyValuesIterable<T> = { values(): IterableIterator<T> } | Iterable<T>;
export function createObjectSearcher<T, U>(searchFnOrKey?: null): null;
export function createObjectSearcher<T, U>(
  searchFnOrKey: KeyOrTransform<T, U>
): TransformFunction<T, U>;
export function createObjectSearcher<T, U>(
  searchFnOrKey?: null | KeyOrTransform<T, U>
) {
  return typeof searchFnOrKey !== 'function' && searchFnOrKey != null
    ? (data: T) => data[searchFnOrKey as keyof T] as U
    : searchFnOrKey;
}

find definition

/** Find first matching element from any iterable
 * @param iterable - The iterable to search
 * @param predicate - A function or key to extract the value to search for
 * @param useValues - Whether to use `iterable.values()` instead of the iterable itself
 * @returns The first matching element or undefined
 *
 * @example retrieving a value (from `iterable.values()`) where useValues=false
 * find(values, v => value[1])[1]
 * @example retrieving a value (from `iterable.values()`) where useValues=true
 * find(values, v => value, true)
 */
export const find = <T, U extends (value: T) => unknown, K extends keyof T>(
  iterable: PossiblyValuesIterable<T>,
  predicate: U | K,
  useValues: boolean = false
): T | undefined => {
  const values =
    useValues && 'values' in iterable
      ? iterable.values()
      : (iterable as Iterable<T>);
  const accessor = createObjectSearcher(predicate);
  for (const item of values) {
    if (accessor(item)) {
      return item;
    }
  }
  return undefined;
};

findValue definition

findValue is essentially a light abstraction around find for better readability

/** Same as find but presets the useValue param to true, as a decorator
 * @see find
 * @example BEFORE: using `find` to retrieve a value using `useValues=true` param
 * find(values, v => value, true)
 * @example AFTER: using `findValue` to retrieve a value
 * findValue(values, v => value)
 *
 * ? About the same length to type `true` directly, but this is more readable
 */
export const findValue = <T, U extends (value: T) => unknown, K extends keyof T>(
  iterable: PossiblyValuesIterable<T>,
  predicate: U | K
): T | undefined => {
  return find(iterable, predicate, true);
};

Note: when using find with useValues=true or using findValue, this function will use the .values() iterator for its iteration rather than the original iterator parameter value itself, if the iterator has a .values() iterator.

Usage

To use the find and findValue function(s), pass in the iterable, an accessor function or a string representing a property name, and optionally a desired value function or a boolean. The function will iterate over the iterable and return the first matched value based on the accessor function or property.

Here are some examples:

Example 1: Using a property name as accessor

const data = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];

// `find` and `findValue` would yield the same result in this example

const result = findValue(data, 'name');
console.log(result); // { id: 1, name: "Alice" }

Example 2: Using a custom accessor function

const data = [
  { id: 1, score: 95 },
  { id: 2, score: 85 },
  { id: 3, score: 75 },
];

// `find` and `findValue` would yield the same result in this example

const result = findValue(data, (d) => d.score >= 90);
console.log(result); // { id: 1, score: 95 }

Example 3: Specifying a desired value function

const data = [
  { id: 1, score: 95 },
  { id: 2, score: 85 },
  { id: 3, score: 75 },
];

// `find` and `findValue` would yield the same result in this example

const result = findValue(data, (d) => d.score >= 90)?.id;
console.log(result); // 1

Example 4: Specifying a desired value property name

const data = [
  { id: 1, score: 95 },
  { id: 2, score: 85 },
  { id: 3, score: 75 },
];

// `find` and `findValue` would yield the same result in this example

const result = findValue(data, (d) => d.score >= 90)?.id;
console.log(result); // 1

Example 5: Comparing find and findValue when iterating over a Map

const data = new Map([
  [1, { name: "Alice", age: 30 }],
  [2, { name: "Bob", age: 25 }],
  [3, { name: "Charlie", age: 28 }],
]);

// `find` and `findValue` would NOT yield the same result in this example

// using `find` with a Map will iterate over the Map entries directly, with `useValues=false`
const result = find(data, (d) => d[1].age >= 30)?.[1].name;

// using `find` with `useValues=true` will iterate over the .values() iterator
const result = find(data, (d) => d.age >= 30, true)?.name;

// `findValue` is equivalent to—but arguably more readbale than—using `find` with `useValues=true`
const result = findValue(data, (d) => d.age >= 30)?.name;
console.log(result); // "Alice"

Adding a tee

Hi, sorry if this is a FAQ, but is there anything preventing the addtion of a tee function whithin this library (mimicking itertool's own tee : https://docs.python.org/3.9/library/itertools.html#itertools.tee) ?

import { tee } from itertools;

const iterable= {
  *[Symbol.iterator]() {
    yield "a";
    yield "b";
    yield "c";
    yield "d";
    yield "e";
  },
};

const [iter1, iter2, iter3] = tee(iterable, 3)

console.log(...iter1);
console.log(...iter2);
console.log(...iter3);

// output: 
// "a"   "b"   "c"  "d"  "e"
// "a"   "b"   "c"  "d"  "e"
// "a"   "b"   "c"  "d"  "e"

`islice` not stopping after end

Consider these examples:

import { islice } from 'itertools';

function* positiveWholeNumbers() {
  let n = 1;
  while (true) {
    yield n++;
  }
}

const first5 = islice(positiveWholeNumbers(), 5);
console.log(Array.from(first5));

Expected behaviour: [1, 2, 3, 4, 5]
Current behaviour: Infinite loop

Add groupby to itertools

I really like your package! I have implemented the groupby method on my side, I might actually do a PR at some point, but until then you can just reuse this if you want to (i.e. and type, test and document).

const groupby = function* groupby(iterable, key = x => x) {
  const it = iter(iterable);

  let currentValue;
  let currentKey = {};
  let targetKey = currentKey;

  const grouper = function* grouper(tgtKey) {
    while (currentKey === tgtKey) {
      yield currentValue;

      const { done, value } = it.next();
      currentValue = value;
      if (done) return;
      currentKey = key(currentValue);
    }
  };

  for (;;) {
    while (currentKey === targetKey) {
      const { done, value } = it.next();
      currentValue = value;
      if (done) return;
      currentKey = key(currentValue);
    }

    targetKey = currentKey;
    yield [currentKey, grouper(targetKey)];
  }
};

console.log([...map(groupby('aaabbbbcddddAA'), ([k, v]) => [k, [...v].length])]);
/* [
  [ "a", 3 ],
  [ "b", 4 ],
  [ "c", 1 ],
  [ "d", 4 ],
  [ "A", 2 ]
] */

islice does not work for infinite iterators

The current implementation of islice gets stuck in an infinite loop when the for...of tries to reach the end of the infinite iterator.

const pred = slicePredicate(start, stop, step); for (const [i, value] of enumerate(iterable)) { if (pred(i)) { yield value; } }

import as module does not work

Following code does not work for me

import groupby from 'itertools';

I got error:

TS7016: Could not find a declaration file for module 'itertools'. 'MY_PROJECT_PATH/node_modules/itertools/index.js' implicitly has an 'any' type.   Try `npm install @types/itertools` if it exists or add a new declaration (.d.ts) file containing `declare module 'itertools';`

Proposal for `takeuntil` method

I was looking for a "takeuntil" method across existing utils,
and the closest I found was takewhile.

While takewhile "produces elements from the iterable as long as the predicate is true",
I'd expect takeuntil to "produce elements from the iterable as long as the predicate is true".

E.g.

test('takeWhile', () => {
  expect([...takewhile([1, 2, -1, 3], x => x > 0)]).toStrictEqual([1, 2]);
});

test('takeuntil', () => {
  expect([...takeuntil([1, 2, -1, 3], x => x < 0)]).toStrictEqual([1, 2, -1]);
});

Would it make sense to add this function to the library?

I.e. having

function* takeuntil(iterable, predicate) {
  for (const value of iterable) {
    yield value;
    if (predicate(value)) return;
  }
}

compared to

export function* takewhile<T>(iterable: Iterable<T>, predicate: Predicate<T>): Iterable<T> {
    for (const value of iterable) {
        if (!predicate(value)) return;
        yield value;
    }
}

regenerator-runtime dependency missing

The current package.json is assuming that since babel-runtime has a dependency on regenerator-runtime, it will be available for itertools to use. That is correct when using NPM or Yarn, but pnpm uses a different structure where the node_modules tree is not flat. In my case (webpack compiling a UI), I get:

spa/ui_main test:  WEBPACK  Compiling...
spa/ui_main test:  WEBPACK  Failed to compile with 4 error(s)
spa/ui_main test: Error in /home/jimp/dtr_code/momentum/node_modules/.registry.npmjs.org/itertools/1.3.2/node_modules/itertools/more-itertools.js
spa/ui_main test:   Module not found: 'regenerator-runtime' in '/home/jimp/dtr_code/momentum/node_modules/.registry.npmjs.org/itertools/1.3.2/node_modules/itertools'

Operating on async-iterables

It seems the helpers in this package do not work when run on async-iterables.
E.g. the following will complain about missing iterable[Symbol.iterator]?

Uncaught TypeError: iterable[Symbol.iterator] is not a function

Is the lack for support for async-iterables a known issue?
Maybe the documentation should state this?

Also, is this just a matter of extending the logic
to deal with the specifics of the async-iterables vs normal iterables?
Or is there a deeper constraint/issue here preventing support for those?

islice don't break execution after reach end

function * gen() {
  for (let i = 0; ; i ++) {
    yield i;
  }
}

const generator = gen();

console.log([...islice(generator, 10)]);

Expected behavior: islice break execution after reach 10 elements
Current behavior: infinity loop

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.