itertools-ts
Version:
Extended itertools port for TypeScript and JavaScript. Provides a huge set of functions for working with iterable collections (including async ones)
524 lines (482 loc) • 11.4 kB
text/typescript
import { zipEqual, zipEqualAsync } from "./multi";
import { mapAsync, pairwise, pairwiseAsync } from "./single";
import { toCount, toCountAsync } from "./reduce";
import { toArrayAsync, toAsyncIterable, toIterable } from "./transform";
import { Comparable } from "./types";
/**
* Returns true if all elements match the predicate function.
*
* Empty collections return true.
*
* @param data
* @param predicate
*/
export function allMatch<T>(
data: Iterable<T> | Iterator<T>,
predicate: (item: T) => boolean
): boolean {
for (const datum of toIterable(data)) {
if (!predicate(datum)) {
return false;
}
}
return true;
}
/**
* Returns true if all elements of async collection match the predicate function.
*
* Empty collections return true.
*
* @param data
* @param predicate
*/
export async function allMatchAsync<T>(
data: AsyncIterable<T> | AsyncIterator<T> | Iterable<T> | Iterator<T>,
predicate: (item: T) => Promise<boolean> | boolean
): Promise<boolean> {
for await (const datum of toAsyncIterable(data)) {
if (!(await predicate(datum))) {
return false;
}
}
return true;
}
/**
* Return true if all elements in given collection are unique.
*
* Empty collections return true.
*
* Considers different instances of data containers to be different, even if they have the same content.
*
* @param data
*/
export function allUnique(
data: Iterable<unknown> | Iterator<unknown>
): boolean {
const usages = new Set();
for (const datum of toIterable(data)) {
if (usages.has(datum)) {
return false;
}
usages.add(datum);
}
return true;
}
/**
* Return true if all elements in given async collection are unique.
*
* Empty collections return true.
*
* Considers different instances of data containers to be different, even if they have the same content.
*
* @param data
*/
export async function allUniqueAsync(
data:
| AsyncIterable<unknown>
| AsyncIterator<unknown>
| Iterable<unknown>
| Iterator<unknown>
): Promise<boolean> {
const usages = new Set();
for await (const datum of toAsyncIterable(data)) {
if (usages.has(datum)) {
return false;
}
usages.add(datum);
}
return true;
}
/**
* Returns true if any element matches the predicate function.
*
* Empty collections return false.
*
* @param data
* @param predicate
*/
export function anyMatch<T>(
data: Iterable<T> | Iterator<T>,
predicate: (item: T) => boolean
): boolean {
for (const datum of toIterable(data)) {
if (predicate(datum)) {
return true;
}
}
return false;
}
/**
* Returns true if any element of async collection matches the predicate function.
*
* Empty collections return false.
*
* @param data
* @param predicate
*/
export async function anyMatchAsync<T>(
data: AsyncIterable<T> | AsyncIterator<T> | Iterable<T> | Iterator<T>,
predicate: (item: T) => Promise<boolean> | boolean
): Promise<boolean> {
for await (const datum of toAsyncIterable(data)) {
if (await predicate(datum)) {
return true;
}
}
return false;
}
/**
* Returns true if exactly n items in the iterable are true where the predicate function is true.
*
* Default predicate if not provided is the boolean value of each data item.
*
* @param data
* @param n
* @param predicate
*/
export function exactlyN<T>(
data: Iterable<T> | Iterator<T>,
n: number,
predicate?: (item: T) => boolean
): boolean {
if (n < 0) {
return false;
}
if (predicate === undefined) {
predicate = (datum) => Boolean(datum);
}
let count = 0;
for (const datum of toIterable(data)) {
if (predicate(datum)) {
count++;
if (count > n) {
return false;
}
}
}
return count === n;
}
/**
* Returns true if exactly n items in the async iterable are true where the predicate function is true.
*
* Default predicate if not provided is the boolean value of each data item.
*
* @param data
* @param n
* @param predicate
*/
export async function exactlyNAsync<T>(
data: AsyncIterable<T> | AsyncIterator<T> | Iterable<T> | Iterator<T>,
n: number,
predicate?: (item: T) => Promise<boolean> | boolean
): Promise<boolean> {
if (n < 0) {
return false;
}
if (predicate === undefined) {
predicate = (datum) => Boolean(datum);
}
let count = 0;
for await (const datum of toAsyncIterable(data)) {
if (await predicate(datum)) {
count++;
if (count > n) {
return false;
}
}
}
return count === n;
}
/**
* Returns true if given collection is empty.
*
* @param data
*/
export function isEmpty(data: Iterable<unknown> | Iterator<unknown>): boolean {
/* eslint-disable @typescript-eslint/no-unused-vars */
for (const _ of toIterable(data)) {
return false;
}
return true;
}
/**
* Returns true if given async collection is empty.
*
* @param data
*/
export async function isEmptyAsync(
data:
| AsyncIterable<unknown>
| AsyncIterator<unknown>
| Iterable<unknown>
| Iterator<unknown>
): Promise<boolean> {
/* eslint-disable @typescript-eslint/no-unused-vars */
for await (const _ of toAsyncIterable(data)) {
return false;
}
return true;
}
/**
* Return true if given input is an Iterable instance.
*
* @param input
*/
export function isIterable(input: unknown): boolean {
if (input === null || input === undefined) {
return false;
}
return (
typeof (input as Record<string | symbol, unknown>)[Symbol.iterator] ===
"function"
);
}
/**
* Return true if given input is an AsyncIterable instance.
*
* @param input
*/
export function isAsyncIterable(input: unknown): boolean {
if (input === null || input === undefined) {
return false;
}
return (
typeof (input as Record<string | symbol, unknown>)[Symbol.asyncIterator] ===
"function"
);
}
/**
* Return true if given input is an Iterator instance.
*
* @param input
*/
export function isIterator(input: unknown): boolean {
if (input === null || input === undefined) {
return false;
}
return (
(input as Record<string, unknown>).next !== undefined &&
typeof (input as Record<string, unknown>).next === "function"
);
}
/**
* Returns true if given collection is sorted in descending order; otherwise false.
*
* Items of given collection must be comparable.
*
* Returns true if given collection is empty or has only one element.
*
* @param data
*/
export function isReversed(
data: Iterable<Comparable> | Iterator<Comparable>
): boolean {
for (const [lhs, rhs] of pairwise(toIterable(data))) {
if (lhs < rhs) {
return false;
}
}
return true;
}
/**
* Returns true if given async collection is sorted in descending order; otherwise false.
*
* Items of given collection must be comparable.
*
* Returns true if given collection is empty or has only one element.
*
* @param data
*/
export async function isReversedAsync(
data:
| AsyncIterable<Comparable>
| AsyncIterator<Comparable>
| Iterable<Comparable>
| Iterator<Comparable>
): Promise<boolean> {
for await (const [lhs, rhs] of pairwiseAsync(toAsyncIterable(data))) {
if (lhs < rhs) {
return false;
}
}
return true;
}
/**
* Returns true if given collection is sorted in ascending order; otherwise false.
*
* Items of given collection must be comparable.
*
* Returns true if given collection is empty or has only one element.
*
* @param data
*/
export function isSorted(
data: Iterable<Comparable> | Iterator<Comparable>
): boolean {
for (const [lhs, rhs] of pairwise(toIterable(data))) {
if (lhs > rhs) {
return false;
}
}
return true;
}
/**
* Returns true if given async collection is sorted in ascending order; otherwise false.
*
* Items of given collection must be comparable.
*
* Returns true if given collection is empty or has only one element.
*
* @param data
*/
export async function isSortedAsync(
data:
| AsyncIterable<Comparable>
| AsyncIterator<Comparable>
| Iterable<Comparable>
| Iterator<Comparable>
): Promise<boolean> {
for await (const [lhs, rhs] of pairwiseAsync(toAsyncIterable(data))) {
if (lhs > rhs) {
return false;
}
}
return true;
}
/**
* Return true if given input is string.
*
* @param input
*/
export function isString(input: unknown): boolean {
return typeof input === "string" || input instanceof String;
}
/**
* Returns true if no element matches the predicate function.
*
* Empty collections return true.
*
* @param data
* @param predicate
*/
export function noneMatch<T>(
data: Iterable<T> | Iterator<T>,
predicate: (item: T) => boolean
): boolean {
for (const datum of toIterable(data)) {
if (predicate(datum)) {
return false;
}
}
return true;
}
/**
* Returns true if no element in async collection matches the predicate function.
*
* Empty collections return true.
*
* @param data
* @param predicate
*/
export async function noneMatchAsync<T>(
data: AsyncIterable<T> | AsyncIterator<T> | Iterable<T> | Iterator<T>,
predicate: (item: T) => Promise<boolean> | boolean
): Promise<boolean> {
for await (const datum of toAsyncIterable(data)) {
if (await predicate(datum)) {
return false;
}
}
return true;
}
/**
* Returns true if all given collections are the same.
*
* For single collection or empty collections list returns true.
*
* @param collections
*/
export function same(
...collections: Array<Iterable<unknown> | Iterator<unknown>>
): boolean {
try {
for (const values of zipEqual(...collections)) {
for (const [lhs, rhs] of pairwise(values)) {
if (lhs !== rhs) {
return false;
}
}
}
} catch (e) {
return false;
}
return true;
}
/**
* Returns true if all given async collections are the same.
*
* For single collection or empty collections list returns true.
*
* @param collections
*/
export async function sameAsync(
...collections: Array<
| AsyncIterable<unknown>
| AsyncIterator<unknown>
| Iterable<unknown>
| Iterator<unknown>
>
): Promise<boolean> {
try {
for await (const values of zipEqualAsync(...collections)) {
for (const [lhs, rhs] of pairwise(values)) {
if (lhs !== rhs) {
return false;
}
}
}
} catch (e) {
return false;
}
return true;
}
/**
* Returns true if all given collections have the same lengths.
*
* For single collection or empty collections list returns true.
*
* @param collections
*/
export function sameCount(
...collections: Array<Iterable<unknown> | Iterator<unknown>>
): boolean {
if (collections.length <= 1) {
return true;
}
const counts = collections.map((collection) => toCount(collection));
return new Set(counts).size === 1;
}
/**
* Returns true if all given async collections have the same lengths.
*
* For single collection or empty collections list returns true.
*
* @param collections
*/
export async function sameCountAsync(
...collections: Array<
| AsyncIterable<unknown>
| AsyncIterator<unknown>
| Iterable<unknown>
| Iterator<unknown>
>
): Promise<boolean> {
if (collections.length <= 1) {
return true;
}
const counts = await mapAsync(
collections,
async (collection) => await toCountAsync(collection)
);
return new Set(await toArrayAsync(counts)).size === 1;
}