diff --git a/mod.ts b/mod.ts index d7bdf06..2791339 100644 --- a/mod.ts +++ b/mod.ts @@ -46,7 +46,7 @@ * Asynchronous Iteration With Promises (Not Recommended) * ------------------------------------------------------ * - * You *could* use a Lazy directly for async work, but it has some problems: + * Other iterator libraries show examples of parallel/async iteration like this: * * ```ts * import { lazy, range } from "./mod.ts" @@ -73,12 +73,15 @@ * have N URLs, `.toArray()` will create N promises, and the JavaScript runtime * will start making progress on all of them simultaneously. * + * That might work for small workloads, but network and memory resources are not + * unbounded, so you may end up with worse, or less reliable performance. + * * * Lazy Asynchronous Iteration * --------------------------- * - * For a simpler, safer API when working with async code, you can convert a - * `Lazy` to a `LazyAsync`: + * Better Iterators provides a simpler, safer API when working with async code: + * You can convert a `Lazy` to a `LazyAsync`: * * ```ts * import { lazy, range } from "./mod.ts" @@ -213,7 +216,7 @@ interface LazyShared { /** Flattens a Lazy> to a Lazy */ flatten(): LazyShared> - /** Fold values. See example in {@link LazyShared#sum */ + /** Fold values. See example in {@link LazyShared#sum} */ fold(initialValue: I, foldFn: (i: I, t: T) => I): Awaitable /** @@ -256,6 +259,22 @@ interface LazyShared { * from 1-size items long. */ chunked(size: number): LazyShared + + /** + * Repeat items `count` times. + * + * ```ts + * import { range } from "./mod.ts" + * + * let nine = range({to: 3}).repeat(3).sum() + * ``` + */ + repeat(count: number): LazyShared + + /** + * Like {@link #repeat}, but repeates forever. + */ + loop(): LazyShared } export class Lazy implements Iterable, LazyShared { @@ -520,6 +539,66 @@ export class Lazy implements Iterable, LazyShared { } return lazy(gen()) } + + /** + * Repeat items `count` times. + * + * ```ts + * import { range } from "./mod.ts" + * + * let nine = range({to: 3}).repeat(3).sum() + * ``` + */ + repeat(count: number): Lazy { + if (count < 0) { + throw new Error(`count may not be < 0. Was: ${count}`) + } + + if (count == 1) { + return this + } + + let inner = this.#inner + const gen = function* generator() { + const arr: T[] = [] + for (const item of inner) { + yield item + arr.push(item) + } + + if (arr.length == 0) { + return + } + + for (let i = 1; i < count; i++) { + yield * arr + } + } + return lazy(gen()) + } + + /** + * Like {@link #repeat}, but repeates forever. + */ + loop(): Lazy { + let inner = this.#inner + const gen = function* generator() { + const arr: T[] = [] + for (const item of inner) { + yield item + arr.push(item) + } + + if (arr.length == 0) { + return + } + + while (true) { + yield * arr + } + } + return lazy(gen()) + } } @@ -856,7 +935,66 @@ export class LazyAsync implements AsyncIterable, LazyShared { } return lazy(gen()) } - + + /** + * Repeat items `count` times. + * + * ```ts + * import { range } from "./mod.ts" + * + * let nine = range({to: 3}).repeat(3).sum() + * ``` + */ + repeat(count: number): LazyAsync { + if (count < 0) { + throw new Error(`count may not be < 0. Was: ${count}`) + } + + if (count == 1) { + return this + } + + let inner = this.#inner + const gen = async function* generator() { + const arr: T[] = [] + for await (const item of inner) { + yield item + arr.push(item) + } + + if (arr.length == 0) { + return + } + + for (let i = 1; i < count; i++) { + yield * arr + } + } + return lazy(gen()) + } + + /** + * Like {@link #repeat}, but repeates forever. + */ + loop(): LazyAsync { + let inner = this.#inner + const gen = async function* generator() { + const arr: T[] = [] + for await (const item of inner) { + yield item + arr.push(item) + } + + if (arr.length == 0) { + return + } + + while (true) { + yield * arr + } + } + return lazy(gen()) + } } /** diff --git a/tests/helpers.ts b/tests/helpers.ts index 4597a8d..57fb3eb 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -46,13 +46,19 @@ export class Timer { } -export async function testBoth(t: Deno.TestContext, data: Iterable, innerTest: (iter: Lazy|LazyAsync) => Promise) { - let input = [...data] +export async function testBoth(t: Deno.TestContext, data: Iterable | (() => Iterable), innerTest: (iter: Lazy|LazyAsync) => Promise) { + let input: () => Iterable + if (Symbol.iterator in data) { + const inputValues = [...data] + input = () => inputValues + } else { + input = data + } await t.step("sync", async () => { - await innerTest(lazy(input)) + await innerTest(lazy(input())) }) await t.step("async", async () => { - await innerTest(lazy(input).toAsync()) + await innerTest(lazy(input()).toAsync()) }) } diff --git a/tests/repeats_test.ts b/tests/repeats_test.ts new file mode 100644 index 0000000..b3eddea --- /dev/null +++ b/tests/repeats_test.ts @@ -0,0 +1,18 @@ +import { assertEquals } from "https://deno.land/std@0.179.0/testing/asserts.ts"; +import { range } from "../mod.ts"; +import { testBoth } from "./helpers.ts"; + + +Deno.test(async function repeats(t) { + await testBoth(t, () => range({to: 4}), async (iter) => { + let result = await iter.repeat(3).toArray() + assertEquals(result, [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]) + }) +}) + +Deno.test(async function loops(t) { + await testBoth(t, () => range({to: 4}), async (iter) => { + let result = await iter.loop().skip(2).limit(11).toArray() + assertEquals(result, [2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0]) + }) +}) \ No newline at end of file