Skip to content

Commit

Permalink
Add .repeat(count) and .loop()
Browse files Browse the repository at this point in the history
  • Loading branch information
Cody Casterline committed Jul 16, 2023
1 parent a4b0581 commit c063c51
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 9 deletions.
148 changes: 143 additions & 5 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -213,7 +216,7 @@ interface LazyShared<T> {
/** Flattens a Lazy<Iterable<T>> to a Lazy<T> */
flatten(): LazyShared<Flattened<T>>

/** Fold values. See example in {@link LazyShared#sum */
/** Fold values. See example in {@link LazyShared#sum} */
fold<I>(initialValue: I, foldFn: (i: I, t: T) => I): Awaitable<I>

/**
Expand Down Expand Up @@ -256,6 +259,22 @@ interface LazyShared<T> {
* from 1-size items long.
*/
chunked(size: number): LazyShared<T[]>

/**
* Repeat items `count` times.
*
* ```ts
* import { range } from "./mod.ts"
*
* let nine = range({to: 3}).repeat(3).sum()
* ```
*/
repeat(count: number): LazyShared<T>

/**
* Like {@link #repeat}, but repeates forever.
*/
loop(): LazyShared<T>
}

export class Lazy<T> implements Iterable<T>, LazyShared<T> {
Expand Down Expand Up @@ -520,6 +539,66 @@ export class Lazy<T> implements Iterable<T>, LazyShared<T> {
}
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<T> {
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<T> {
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())
}
}


Expand Down Expand Up @@ -856,7 +935,66 @@ export class LazyAsync<T> implements AsyncIterable<T>, LazyShared<T> {
}
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<T> {
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<T> {
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())
}
}

/**
Expand Down
14 changes: 10 additions & 4 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,19 @@ export class Timer {
}


export async function testBoth<T>(t: Deno.TestContext, data: Iterable<T>, innerTest: (iter: Lazy<T>|LazyAsync<T>) => Promise<unknown>) {
let input = [...data]
export async function testBoth<T>(t: Deno.TestContext, data: Iterable<T> | (() => Iterable<T>), innerTest: (iter: Lazy<T>|LazyAsync<T>) => Promise<unknown>) {
let input: () => Iterable<T>
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())
})
}

Expand Down
18 changes: 18 additions & 0 deletions tests/repeats_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { assertEquals } from "https://deno.land/[email protected]/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])
})
})

0 comments on commit c063c51

Please sign in to comment.