Skip to content

Commit f8894da

Browse files
authored
feat(collections/unstable): add cycle iterator utility (#6386)
1 parent 65a272e commit f8894da

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

collections/deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"./take-while": "./take_while.ts",
4848
"./union": "./union.ts",
4949
"./unstable-chunk": "./unstable_chunk.ts",
50+
"./unstable-cycle": "./unstable_cycle.ts",
5051
"./unstable-drop-while": "./unstable_drop_while.ts",
5152
"./unstable-drop-last-while": "./unstable_drop_last_while.ts",
5253
"./unstable-intersect": "./unstable_intersect.ts",

collections/unstable_cycle.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
3+
/**
4+
* Creates an iterator that cycles indefinitely over the provided iterable.
5+
*
6+
* Each time the iterable is exhausted, a new iterator is obtained to start the cycle again.
7+
* This generator will yield the values from the iterable continuously.
8+
*
9+
* > **Note:** If the iterable is empty, the generator will keep restarting and yield nothing.
10+
*
11+
* @typeParam T The type of the elements in the iterable.
12+
* @param iterable The iterable whose values are to be cycled.
13+
* @returns A generator that yields values from the iterable in an endless cycle.
14+
*
15+
* @example Basic usage
16+
* ```ts
17+
* import { cycle } from "@std/collections/unstable-cycle";
18+
* import { assertEquals } from "@std/assert";
19+
*
20+
* const cyclic = cycle([1, 2, 3]);
21+
* const result: number[] = [];
22+
*
23+
* for (const num of cyclic) {
24+
* result.push(num);
25+
* if (result.length === 7) break;
26+
* }
27+
*
28+
* assertEquals(result, [1, 2, 3, 1, 2, 3, 1]);
29+
* ```
30+
*/
31+
export function* cycle<T>(iterable: Iterable<T>): Generator<T> {
32+
let iterator = iterable[Symbol.iterator]();
33+
34+
while (true) {
35+
const result = iterator.next();
36+
if (result.done) {
37+
iterator = iterable[Symbol.iterator]();
38+
} else {
39+
yield result.value;
40+
}
41+
}
42+
}

collections/unstable_cycle_test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2018-2025 the Deno authors. MIT license.
2+
3+
import { assertEquals } from "@std/assert";
4+
import { cycle } from "./unstable_cycle.ts";
5+
6+
Deno.test({
7+
name: "cycle() basic usage with an array",
8+
fn() {
9+
const cyclic = cycle([1, 2, 3]);
10+
const result = [];
11+
12+
for (const num of cyclic) {
13+
result.push(num);
14+
if (result.length === 7) break;
15+
}
16+
assertEquals(result, [1, 2, 3, 1, 2, 3, 1]);
17+
},
18+
});
19+
20+
Deno.test({
21+
name: "cycle() works with a string iterable",
22+
fn() {
23+
const cyclic = cycle("ab");
24+
const result = [];
25+
26+
for (const ch of cyclic) {
27+
result.push(ch);
28+
if (result.length === 5) break;
29+
}
30+
assertEquals(result, ["a", "b", "a", "b", "a"]);
31+
},
32+
});
33+
34+
Deno.test({
35+
name: "cycle() works with a single element iterable",
36+
fn() {
37+
const cyclic = cycle([42]);
38+
const result = [];
39+
40+
for (const num of cyclic) {
41+
result.push(num);
42+
if (result.length === 4) break;
43+
}
44+
assertEquals(result, [42, 42, 42, 42]);
45+
},
46+
});
47+
48+
Deno.test({
49+
name: "cycle() does not mutate the input iterable",
50+
fn() {
51+
const input = [1, 2, 3];
52+
const cyclic = cycle(input);
53+
54+
const result = [];
55+
for (const num of cyclic) {
56+
result.push(num);
57+
if (result.length === 5) break;
58+
}
59+
60+
assertEquals(input, [1, 2, 3]);
61+
},
62+
});

0 commit comments

Comments
 (0)