Skip to content

Commit

Permalink
feat: add async iterator utility functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lambdalisue committed Aug 7, 2024
1 parent dc02486 commit 08915d2
Show file tree
Hide file tree
Showing 59 changed files with 2,839 additions and 1 deletion.
24 changes: 24 additions & 0 deletions async/chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Chains multiple iterables together.
*
* @param iterables The iterables to chain.
* @returns The chained iterable.
* @example
*
* ```ts
* import { toArray } from "@core/iterutil/async/to-array";
* import { chain } from "@core/iterutil/async/chain";
*
* const iter = chain([1, 2], [3, 4]);
* console.log(await toArray(iter)); // [1, 2, 3, 4]
* ```
*/
export async function* chain<T>(
...iterables: Iterable<T>[] | AsyncIterable<T>[]
): AsyncIterable<T> {
for await (const iterable of iterables) {
for await (const value of iterable) {
yield value;
}
}
}
25 changes: 25 additions & 0 deletions async/chain_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { assertEquals } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { toAsyncIterable } from "./to_async_iterable.ts";
import { toArray } from "./to_array.ts";
import { chain } from "./chain.ts";

Deno.test("chain", async (t) => {
await t.step("with async iterable", async () => {
const result = chain(
toAsyncIterable([1, 2]),
toAsyncIterable([3, 4]),
toAsyncIterable([5]),
);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with iterable", async () => {
const result = chain([1, 2], [3, 4], [5]);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});
});
32 changes: 32 additions & 0 deletions async/chunked.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Chunks an iterable into arrays of a given size.
*
* @param iterable The iterable to chunk.
* @param size The size of each chunk.
* @returns The chunked iterable.
*
* @example
* ```ts
* import { toArray } from "@core/iterutil/async/to-array";
* import { chunked } from "@core/iterutil/async/chunked";
*
* const iter = chunked([1, 2, 3, 4, 5], 2);
* console.log(await toArray(iter)); // [[1, 2], [3, 4], [5]]
* ```
*/
export async function* chunked<T>(
iterable: Iterable<T> | AsyncIterable<T>,
size: number,
): AsyncIterable<T[]> {
let chunk = [];
for await (const item of iterable) {
chunk.push(item);
if (chunk.length === size) {
yield chunk;
chunk = [];
}
}
if (chunk.length > 0) {
yield chunk;
}
}
67 changes: 67 additions & 0 deletions async/chunked_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { assertEquals } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { toAsyncIterable } from "./to_async_iterable.ts";
import { toArray } from "./to_array.ts";
import { chunked } from "./chunked.ts";

Deno.test("chunked", async (t) => {
await t.step("with async iterable", async (t) => {
await t.step("the length is divisible by the size", async () => {
const result = chunked(toAsyncIterable([1, 2, 3, 4, 5, 6]), 2);
const expected = [[1, 2], [3, 4], [5, 6]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});

await t.step("the length is not divisible by the size", async () => {
const result = chunked(toAsyncIterable([1, 2, 3, 4, 5]), 2);
const expected = [[1, 2], [3, 4], [5]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});

await t.step("the length is equal to the size", async () => {
const result = chunked(toAsyncIterable([1, 2, 3, 4, 5]), 5);
const expected = [[1, 2, 3, 4, 5]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});

await t.step("the length is less than the size", async () => {
const result = chunked(toAsyncIterable([1, 2, 3, 4, 5]), 6);
const expected = [[1, 2, 3, 4, 5]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});
});

await t.step("with iterable", async (t) => {
await t.step("the length is divisible by the size", async () => {
const result = chunked([1, 2, 3, 4, 5, 6], 2);
const expected = [[1, 2], [3, 4], [5, 6]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});

await t.step("the length is not divisible by the size", async () => {
const result = chunked([1, 2, 3, 4, 5], 2);
const expected = [[1, 2], [3, 4], [5]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});

await t.step("the length is equal to the size", async () => {
const result = chunked([1, 2, 3, 4, 5], 5);
const expected = [[1, 2, 3, 4, 5]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});

await t.step("the length is less than the size", async () => {
const result = chunked([1, 2, 3, 4, 5], 6);
const expected = [[1, 2, 3, 4, 5]];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number[]>>>(true);
});
});
});
24 changes: 24 additions & 0 deletions async/compact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Removes all nullish values from an iterable.
*
* @param iterable The iterable to compact.
* @returns The compacted iterable.
*
* @example
* ```ts
* import { toArray } from "@core/iterutil/async/to-array";
* import { compact } from "@core/iterutil/async/compact";
*
* const iter = compact([1, undefined, 2, null, 3]);
* console.log(await toArray(iter)); // [1, 2, 3]
* ```
*/
export async function* compact<T>(
iterable: Iterable<T> | AsyncIterable<T>,
): AsyncIterable<NonNullable<T>> {
for await (const value of iterable) {
if (value != null) {
yield value;
}
}
}
91 changes: 91 additions & 0 deletions async/compact_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { assertEquals } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { toAsyncIterable } from "./to_async_iterable.ts";
import { toArray } from "./to_array.ts";
import { compact } from "./compact.ts";

Deno.test("compact", async (t) => {
await t.step("with async iterable", async (t) => {
await t.step("without undefined/null", async () => {
const result = compact(toAsyncIterable([1, 2, 3, 4, 5]));
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with undefined", async () => {
const result = compact(toAsyncIterable([
undefined,
1,
2,
undefined,
3,
undefined,
4,
5,
undefined,
]));
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with null", async () => {
const result = compact(
toAsyncIterable([null, 1, 2, null, 3, null, 4, 5, null]),
);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with undefined/null", async () => {
const result = compact(
toAsyncIterable([undefined, 1, 2, null, 3, undefined, 4, 5, null]),
);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});
});

await t.step("with iterable", async (t) => {
await t.step("without undefined/null", async () => {
const result = compact([1, 2, 3, 4, 5]);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with undefined", async () => {
const result = compact([
undefined,
1,
2,
undefined,
3,
undefined,
4,
5,
undefined,
]);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with null", async () => {
const result = compact([null, 1, 2, null, 3, null, 4, 5, null]);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("with undefined/null", async () => {
const result = compact([undefined, 1, 2, null, 3, undefined, 4, 5, null]);
const expected = [1, 2, 3, 4, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});
});
});
39 changes: 39 additions & 0 deletions async/compress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Compress an iterable by selecting elements using a selector iterable.
*
* @param iterable The iterable to compress.
* @param selectors The selectors to use.
* @returns The compressed iterable.
*
* @example
* ```ts
* import { toArray } from "@core/iterutil/async/to-array";
* import { compress } from "@core/iterutil/async/compress";
*
* const iter = compress([1, 2, 3, 4, 5], [true, false, true, false, true]);
* console.log(await toArray(iter)); // [1, 3, 5]
* ```
*/
export async function* compress<T>(
iterable: Iterable<T> | AsyncIterable<T>,
selectors: Iterable<boolean> | AsyncIterable<boolean>,
): AsyncIterable<T> {
const it1 = Symbol.iterator in iterable
? iterable[Symbol.iterator]()
: iterable[Symbol.asyncIterator]();
const it2 = Symbol.iterator in selectors
? selectors[Symbol.iterator]()
: selectors[Symbol.asyncIterator]();
while (true) {
const [
{ done: done1, value: value1 },
{ done: done2, value: value2 },
] = await Promise.all([it1.next(), it2.next()]);
if (done1 || done2) {
break;
}
if (value2) {
yield value1;
}
}
}
68 changes: 68 additions & 0 deletions async/compress_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { assertEquals } from "@std/assert";
import { assertType, type IsExact } from "@std/testing/types";
import { toAsyncIterable } from "./to_async_iterable.ts";
import { toArray } from "./to_array.ts";
import { compress } from "./compress.ts";

Deno.test("compress", async (t) => {
await t.step("with async iterable", async (t) => {
await t.step("the iterable and the selectors are same length", async () => {
const result = compress(
toAsyncIterable([1, 2, 3, 4, 5]),
toAsyncIterable([true, false, true, false, true]),
);
const expected = [1, 3, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("the iterable is larger than the selectors", async () => {
const result = compress(
toAsyncIterable([1, 2, 3, 4, 5]),
toAsyncIterable([true, false, true]),
);
const expected = [1, 3];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("the iterable is smaller than the selector", async () => {
const result = compress(
toAsyncIterable([1, 2, 3]),
toAsyncIterable([true, false, true, false, true]),
);
const expected = [1, 3];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});
});

await t.step("with iterable", async (t) => {
await t.step("the iterable and the selectors are same length", async () => {
const result = compress([1, 2, 3, 4, 5], [
true,
false,
true,
false,
true,
]);
const expected = [1, 3, 5];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("the iterable is larger than the selectors", async () => {
const result = compress([1, 2, 3, 4, 5], [true, false, true]);
const expected = [1, 3];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});

await t.step("the iterable is smaller than the selector", async () => {
const result = compress([1, 2, 3], [true, false, true, false, true]);
const expected = [1, 3];
assertEquals(await toArray(result), expected);
assertType<IsExact<typeof result, AsyncIterable<number>>>(true);
});
});
});
Loading

0 comments on commit 08915d2

Please sign in to comment.