diff --git a/README.md b/README.md index 80fa0cc..11adabd 100644 --- a/README.md +++ b/README.md @@ -1073,6 +1073,48 @@ const joined = await pipe( console.log(joined); // 12345 ``` +### repeat + +Returns an finite iterable that repeats through the given iterable. + +```ts +import { repeat } from "@core/iterutil/repeat"; + +const iter = repeat([1, 2, 3], 2); +console.log(Array.from(iter)); // [1, 2, 3, 1, 2, 3] +``` + +```ts +import { repeat } from "@core/iterutil/async/repeat"; + +const iter = repeat([1, 2, 3], 2); +console.log(await Array.fromAsync(iter)); // [1, 2, 3, 1, 2, 3] +``` + +Use `pipe` and `pipe/async` modules for [@core/pipe] package like. + +```ts +import { pipe } from "@core/pipe"; +import { repeat } from "@core/iterutil/pipe/repeat"; + +const iter = pipe( + [1, 2, 3], + repeat(2), +); +console.log(Array.from(iter)); // [1, 2, 3, 1, 2, 3] +``` + +```ts +import { pipe } from "@core/pipe"; +import { repeat } from "@core/iterutil/pipe/async/repeat"; + +const iter = pipe( + [1, 2, 3], + repeat(2), +); +console.log(await Array.fromAsync(iter)); // [1, 2, 3, 1, 2, 3] +``` + ### some Returns true if at least one element in the iterable satisfies the provided diff --git a/async/mod.ts b/async/mod.ts index 7228389..aac738f 100644 --- a/async/mod.ts +++ b/async/mod.ts @@ -20,6 +20,7 @@ export * from "./nth.ts"; export * from "./pairwise.ts"; export * from "./partition.ts"; export * from "./reduce.ts"; +export * from "./repeat.ts"; export * from "./some.ts"; export * from "./take.ts"; export * from "./take_while.ts"; diff --git a/async/repeat.ts b/async/repeat.ts new file mode 100644 index 0000000..081eb68 --- /dev/null +++ b/async/repeat.ts @@ -0,0 +1,39 @@ +/** + * Returns an infinite iterable that repeats through the given iterable. + * + * Use {@linkcode https://jsr.io/@core/iterutil/doc/async/cycle/~/cycle cycle} to cycle the iterable + * Use {@linkcode https://jsr.io/@core/iterutil/doc/repeat/~/repeat repeat} to repeat the iterable synchronously. + * + * @param iterable The iterable to repeat. + * @returns The repeatd iterable. + * + * @example + * ```ts + * import { repeat } from "@core/iterutil/async/repeat"; + * + * const iter = repeat([1, 2, 3], 2); + * console.log(await Array.fromAsync(iter)); // [1, 2, 3, 1, 2, 3] + * ``` + */ +export function repeat( + iterable: Iterable | AsyncIterable, + n: number, +): AsyncIterable { + if (n < 0 || !Number.isSafeInteger(n)) { + throw new RangeError( + `n must be 0 or positive safe integer, but got ${n}.`, + ); + } + if (n === 0) { + return async function* () {}(); + } + return async function* () { + const array = await Array.fromAsync(iterable); + if (array.length === 0) { + return; + } + for (let i = 0; i < n; i++) { + yield* array; + } + }(); +} diff --git a/async/repeat_test.ts b/async/repeat_test.ts new file mode 100644 index 0000000..c9bf7a0 --- /dev/null +++ b/async/repeat_test.ts @@ -0,0 +1,69 @@ +import { assertEquals, assertThrows } from "@std/assert"; +import { assertType, type IsExact } from "@std/testing/types"; +import { toAsyncIterable } from "./to_async_iterable.ts"; +import { repeat } from "./repeat.ts"; + +Deno.test("repeat", async (t) => { + await t.step("with async iterable", async (t) => { + await t.step("with non empty iterable", async () => { + const result = repeat(toAsyncIterable([0, 1, 2]), 2); + const expected = [0, 1, 2, 0, 1, 2]; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + + await t.step("with single value iterable", async () => { + const result = repeat(toAsyncIterable([0]), 2); + const expected = [0, 0]; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + + await t.step("with empty iterable", async () => { + const result = repeat(toAsyncIterable([] as number[]), 2); + const expected: number[] = []; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + }); + + await t.step("with iterable", async (t) => { + await t.step("with non empty iterable", async () => { + const result = repeat([0, 1, 2], 2); + const expected = [0, 1, 2, 0, 1, 2]; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + + await t.step("with single value iterable", async () => { + const result = repeat([0], 2); + const expected = [0, 0]; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + + await t.step("with empty iterable", async () => { + const result = repeat([] as number[], 2); + const expected: number[] = []; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + }); + + await t.step("with n=0", async () => { + const result = repeat([0, 1, 2], 0); + const expected: number[] = []; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); + + await t.step("throws RangeError", async (t) => { + await t.step("if the limit is not 0 nor positive safe integer", () => { + assertThrows(() => repeat([], NaN), RangeError); + assertThrows(() => repeat([], Infinity), RangeError); + assertThrows(() => repeat([], -Infinity), RangeError); + assertThrows(() => repeat([], -1), RangeError); + assertThrows(() => repeat([], 1.1), RangeError); + }); + }); +}); diff --git a/deno.jsonc b/deno.jsonc index ae89f3e..d902dc2 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -26,6 +26,7 @@ "./async/pairwise": "./async/pairwise.ts", "./async/partition": "./async/partition.ts", "./async/reduce": "./async/reduce.ts", + "./async/repeat": "./async/repeat.ts", "./async/some": "./async/some.ts", "./async/take": "./async/take.ts", "./async/take-while": "./async/take_while.ts", @@ -77,6 +78,7 @@ "./pipe/async/pairwise": "./pipe/async/pairwise.ts", "./pipe/async/partition": "./pipe/async/partition.ts", "./pipe/async/reduce": "./pipe/async/reduce.ts", + "./pipe/async/repeat": "./pipe/async/repeat.ts", "./pipe/async/some": "./pipe/async/some.ts", "./pipe/async/take": "./pipe/async/take.ts", "./pipe/async/take-while": "./pipe/async/take_while.ts", @@ -103,6 +105,7 @@ "./pipe/pairwise": "./pipe/pairwise.ts", "./pipe/partition": "./pipe/partition.ts", "./pipe/reduce": "./pipe/reduce.ts", + "./pipe/repeat": "./pipe/repeat.ts", "./pipe/some": "./pipe/some.ts", "./pipe/take": "./pipe/take.ts", "./pipe/take-while": "./pipe/take_while.ts", @@ -110,6 +113,7 @@ "./pipe/zip": "./pipe/zip.ts", "./range": "./range.ts", "./reduce": "./reduce.ts", + "./repeat": "./repeat.ts", "./some": "./some.ts", "./take": "./take.ts", "./take-while": "./take_while.ts", @@ -156,6 +160,7 @@ "@core/iterutil/async/pairwise": "./async/pairwise.ts", "@core/iterutil/async/partition": "./async/partition.ts", "@core/iterutil/async/reduce": "./async/reduce.ts", + "@core/iterutil/async/repeat": "./async/repeat.ts", "@core/iterutil/async/some": "./async/some.ts", "@core/iterutil/async/take": "./async/take.ts", "@core/iterutil/async/take-while": "./async/take_while.ts", @@ -209,6 +214,7 @@ "@core/iterutil/pipe/async/pairwise": "./pipe/async/pairwise.ts", "@core/iterutil/pipe/async/partition": "./pipe/async/partition.ts", "@core/iterutil/pipe/async/reduce": "./pipe/async/reduce.ts", + "@core/iterutil/pipe/async/repeat": "./pipe/async/repeat.ts", "@core/iterutil/pipe/async/some": "./pipe/async/some.ts", "@core/iterutil/pipe/async/take": "./pipe/async/take.ts", "@core/iterutil/pipe/async/take-while": "./pipe/async/take_while.ts", @@ -237,6 +243,7 @@ "@core/iterutil/pipe/pairwise": "./pipe/pairwise.ts", "@core/iterutil/pipe/partition": "./pipe/partition.ts", "@core/iterutil/pipe/reduce": "./pipe/reduce.ts", + "@core/iterutil/pipe/repeat": "./pipe/repeat.ts", "@core/iterutil/pipe/some": "./pipe/some.ts", "@core/iterutil/pipe/take": "./pipe/take.ts", "@core/iterutil/pipe/take-while": "./pipe/take_while.ts", @@ -244,6 +251,7 @@ "@core/iterutil/pipe/zip": "./pipe/zip.ts", "@core/iterutil/range": "./range.ts", "@core/iterutil/reduce": "./reduce.ts", + "@core/iterutil/repeat": "./repeat.ts", "@core/iterutil/some": "./some.ts", "@core/iterutil/take": "./take.ts", "@core/iterutil/take-while": "./take_while.ts", diff --git a/mod.ts b/mod.ts index 0cf360d..6337340 100644 --- a/mod.ts +++ b/mod.ts @@ -22,6 +22,7 @@ export * from "./pairwise.ts"; export * from "./partition.ts"; export * from "./range.ts"; export * from "./reduce.ts"; +export * from "./repeat.ts"; export * from "./some.ts"; export * from "./take.ts"; export * from "./take_while.ts"; diff --git a/pipe/async/mod.ts b/pipe/async/mod.ts index 9e842c6..4af9ac7 100644 --- a/pipe/async/mod.ts +++ b/pipe/async/mod.ts @@ -19,6 +19,7 @@ export * from "./nth.ts"; export * from "./pairwise.ts"; export * from "./partition.ts"; export * from "./reduce.ts"; +export * from "./repeat.ts"; export * from "./some.ts"; export * from "./take.ts"; export * from "./take_while.ts"; diff --git a/pipe/async/repeat.ts b/pipe/async/repeat.ts new file mode 100644 index 0000000..2c9deaa --- /dev/null +++ b/pipe/async/repeat.ts @@ -0,0 +1,24 @@ +import { repeat as base } from "@core/iterutil/async/repeat"; + +/** + * An operator to return a function that repeats the elements of an iterable. + * + * See {@linkcode https://jsr.io/@core/iterutil/doc/async/repeat/~/repeat repeat} for native repeat. + * + * @example + * ```ts + * import { pipe } from "@core/pipe"; + * import { repeat } from "@core/iterutil/pipe/async/repeat"; + * + * const iter = pipe( + * [1, 2, 3], + * repeat(2), + * ); + * console.log(await Array.fromAsync(iter)); // [1, 2, 3, 1, 2, 3] + * ``` + */ +export function repeat( + n: number, +): (iterable: Iterable | AsyncIterable) => AsyncIterable { + return (iterable: Iterable | AsyncIterable) => base(iterable, n); +} diff --git a/pipe/async/repeat_test.ts b/pipe/async/repeat_test.ts new file mode 100644 index 0000000..b2179ff --- /dev/null +++ b/pipe/async/repeat_test.ts @@ -0,0 +1,13 @@ +import { assertEquals } from "@std/assert"; +import { assertType, type IsExact } from "@std/testing/types"; +import { pipe } from "@core/pipe"; +import { repeat } from "./repeat.ts"; + +Deno.test("repeat", async (t) => { + await t.step("usage", async () => { + const result = pipe([0, 1, 2], repeat(2)); + const expected = [0, 1, 2, 0, 1, 2]; + assertEquals(await Array.fromAsync(result), expected); + assertType>>(true); + }); +}); diff --git a/pipe/mod.ts b/pipe/mod.ts index 9e842c6..4af9ac7 100644 --- a/pipe/mod.ts +++ b/pipe/mod.ts @@ -19,6 +19,7 @@ export * from "./nth.ts"; export * from "./pairwise.ts"; export * from "./partition.ts"; export * from "./reduce.ts"; +export * from "./repeat.ts"; export * from "./some.ts"; export * from "./take.ts"; export * from "./take_while.ts"; diff --git a/pipe/repeat.ts b/pipe/repeat.ts new file mode 100644 index 0000000..de4d9ee --- /dev/null +++ b/pipe/repeat.ts @@ -0,0 +1,22 @@ +import { repeat as base } from "@core/iterutil/repeat"; + +/** + * An operator to return a function that repeats the elements of an iterable. + * + * See {@linkcode https://jsr.io/@core/iterutil/doc/repeat/~/repeat repeat} for native repeat. + * + * @example + * ```ts + * import { pipe } from "@core/pipe"; + * import { repeat } from "@core/iterutil/pipe/repeat"; + * + * const iter = pipe( + * [1, 2, 3], + * repeat(2), + * ); + * console.log(Array.from(iter)); // [1, 2, 3, 1, 2, 3] + * ``` + */ +export function repeat(n: number): (iterable: Iterable) => Iterable { + return (iterable: Iterable) => base(iterable, n); +} diff --git a/pipe/repeat_test.ts b/pipe/repeat_test.ts new file mode 100644 index 0000000..07aff1e --- /dev/null +++ b/pipe/repeat_test.ts @@ -0,0 +1,13 @@ +import { assertEquals } from "@std/assert"; +import { assertType, type IsExact } from "@std/testing/types"; +import { pipe } from "@core/pipe"; +import { repeat } from "./repeat.ts"; + +Deno.test("repeat", async (t) => { + await t.step("usage", () => { + const result = pipe([0, 1, 2], repeat(2)); + const expected = [0, 1, 2, 0, 1, 2]; + assertEquals(Array.from(result), expected); + assertType>>(true); + }); +}); diff --git a/repeat.ts b/repeat.ts new file mode 100644 index 0000000..cb46d0d --- /dev/null +++ b/repeat.ts @@ -0,0 +1,38 @@ +/** + * Returns a finite iterable that repeats through the given iterable. + * + * Use {@linkcode https://jsr.io/@core/iterutil/doc/cycle/~/cycle cycle} to cycle the iterable. + * Use {@linkcode https://jsr.io/@core/iterutil/doc/async/repeat/~/repeat repeat} to repeat the iterable asynchronously. + * + * @param iterable The iterable to repeat. + * @param n The number of times to repeat the iterable. Must be a safe integer greater than or equal to 0 + * @returns The repeated iterable. + * @throws {RangeError} If `n` is not a safe integer greater than or equal to 0. + * + * @example + * ```ts + * import { repeat } from "@core/iterutil/repeat"; + * + * const iter = repeat([1, 2, 3], 2); + * console.log(Array.from(iter)); // [1, 2, 3, 1, 2, 3] + * ``` + */ +export function repeat(iterable: Iterable, n: number): Iterable { + if (n < 0 || !Number.isSafeInteger(n)) { + throw new RangeError( + `n must be 0 or positive safe integer, but got ${n}.`, + ); + } + if (n === 0) { + return []; + } + return function* () { + const array = Array.from(iterable); + if (array.length === 0) { + return; + } + for (let i = 0; i < n; i++) { + yield* array; + } + }(); +} diff --git a/repeat_test.ts b/repeat_test.ts new file mode 100644 index 0000000..f60dfe7 --- /dev/null +++ b/repeat_test.ts @@ -0,0 +1,43 @@ +import { assertEquals, assertThrows } from "@std/assert"; +import { assertType, type IsExact } from "@std/testing/types"; +import { repeat } from "./repeat.ts"; + +Deno.test("repeat", async (t) => { + await t.step("with non empty iterable", () => { + const result = repeat([0, 1, 2], 2); + const expected = [0, 1, 2, 0, 1, 2]; + assertEquals(Array.from(result), expected); + assertType>>(true); + }); + + await t.step("with single value iterable", () => { + const result = repeat([0], 2); + const expected = [0, 0]; + assertEquals(Array.from(result), expected); + assertType>>(true); + }); + + await t.step("with empty iterable", () => { + const result = repeat([] as number[], 2); + const expected: number[] = []; + assertEquals(Array.from(result), expected); + assertType>>(true); + }); + + await t.step("with n=0", () => { + const result = repeat([0, 1, 2], 0); + const expected: number[] = []; + assertEquals(Array.from(result), expected); + assertType>>(true); + }); + + await t.step("throws RangeError", async (t) => { + await t.step("if the limit is not 0 nor positive safe integer", () => { + assertThrows(() => repeat([], NaN), RangeError); + assertThrows(() => repeat([], Infinity), RangeError); + assertThrows(() => repeat([], -Infinity), RangeError); + assertThrows(() => repeat([], -1), RangeError); + assertThrows(() => repeat([], 1.1), RangeError); + }); + }); +});