Skip to content

Commit 01df34c

Browse files
committed
Add product(...) and repeat(x, n) support
1 parent cce6385 commit 01df34c

File tree

4 files changed

+113
-13
lines changed

4 files changed

+113
-13
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Make an iterator returning elements from the iterable and saving a copy of each.
3636
cycle([1, 2, 3]); //=> [1, 2, 3, 1, 2, 3, 1, 2, ...]
3737
```
3838

39-
### `repeat<T>(value: T): Iterable<T>`
39+
### `repeat<T>(value: T, times?: number): Iterable<T>`
4040

4141
Make an iterator that repeats `value` over and over again.
4242

@@ -271,6 +271,14 @@ Sums `start` and the items of an `iterable` from left to right and returns the t
271271
sum([1, 2, 3, 4, 5]); //=> 15
272272
```
273273

274+
### `product<T>(...iterables: Iterable<T>[]): Iterable<T[]>`
275+
276+
Cartesian product of input iterables.
277+
278+
```ts
279+
product("ABCD", "xy"); //=> Ax Ay Bx By Cx Cy Dx Dy
280+
```
281+
274282
## Reference
275283

276284
- [Itertools Recipes](https://docs.python.org/3/library/itertools.html#itertools-recipes)

src/async.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,11 @@ export function contains<T>(iterable: AnyIterable<T>, needle: T) {
7272
/**
7373
* Returns an iterable of enumeration pairs.
7474
*/
75-
export async function* enumerate<T>(
75+
export function enumerate<T>(
7676
iterable: AnyIterable<T>,
7777
offset = 0
7878
): AsyncIterable<[number, T]> {
79-
let index = offset;
80-
81-
for await (const value of iterable) yield [index++, value];
79+
return zip(range(offset), iterable);
8280
}
8381

8482
/**
@@ -188,8 +186,9 @@ export async function* cycle<T>(iterable: AnyIterable<T>): AsyncIterable<T> {
188186
/**
189187
* Make an iterator that repeats `value` over and over again.
190188
*/
191-
export async function* repeat<T>(value: T): AsyncIterable<T> {
192-
while (true) yield value;
189+
export async function* repeat<T>(value: T, times?: number): AsyncIterable<T> {
190+
if (times === undefined) while (true) yield value;
191+
for (let i = 0; i < times; i++) yield value;
193192
}
194193

195194
/**
@@ -642,3 +641,37 @@ export async function sum(
642641
): Promise<number> {
643642
return reduce(iterable, (x, y) => x + y, start);
644643
}
644+
645+
/**
646+
* Recursively produce all produces of a list of iterators.
647+
*/
648+
async function* _product<T>(
649+
pools: AnyIterator<T | typeof SENTINEL>[],
650+
buffer: T[] = []
651+
): AsyncIterable<T[]> {
652+
if (pools.length === 0) {
653+
yield buffer.slice();
654+
return;
655+
}
656+
657+
const [pool, ...others] = pools;
658+
659+
while (true) {
660+
const item = await pool.next();
661+
if (item.value === SENTINEL) break;
662+
buffer.push(item.value);
663+
yield* _product(others, buffer);
664+
buffer.pop();
665+
}
666+
}
667+
668+
/**
669+
* Cartesian product of input iterables.
670+
*/
671+
export async function* product<T extends any[]>(
672+
...iterables: AnyTupleIterable<T>
673+
): AsyncIterable<T> {
674+
const pools = iterables.map(x => iter(cycle(chain(x, repeat(SENTINEL, 1)))));
675+
676+
yield* _product(pools) as AsyncIterable<T>;
677+
}

src/index.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ describe("iterative", () => {
149149

150150
expect(Array.from(iterable)).toEqual([10, 10, 10, 10, 10]);
151151
});
152+
153+
it("should repeat a value up to times", () => {
154+
const iterable = iter.repeat(10, 5);
155+
156+
expect(Array.from(iterable)).toEqual([10, 10, 10, 10, 10]);
157+
});
152158
});
153159

154160
describe("cycle", () => {
@@ -469,8 +475,28 @@ describe("iterative", () => {
469475
it("should sum an iterable", () => {
470476
expect(iter.sum(iter.range(0, 10))).toEqual(45);
471477
});
478+
472479
it("should sum an iterable with custom start", () => {
473480
expect(iter.sum(iter.range(0, 10), 5)).toEqual(50);
474481
});
475482
});
483+
484+
describe("product", () => {
485+
it("should generate the product of multiple iterators", () => {
486+
const iterable = iter.product("ABCD", "xy");
487+
488+
const result = [
489+
["A", "x"],
490+
["A", "y"],
491+
["B", "x"],
492+
["B", "y"],
493+
["C", "x"],
494+
["C", "y"],
495+
["D", "x"],
496+
["D", "y"]
497+
];
498+
499+
expect(Array.from(iterable)).toEqual(result);
500+
});
501+
});
476502
});

src/index.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,11 @@ export function contains<T>(iterable: Iterable<T>, needle: T) {
6060
/**
6161
* Returns an iterable of enumeration pairs.
6262
*/
63-
export function* enumerate<T>(
63+
export function enumerate<T>(
6464
iterable: Iterable<T>,
6565
offset = 0
6666
): Iterable<[number, T]> {
67-
let index = offset;
68-
69-
for (const value of iterable) yield [index++, value];
67+
return zip(range(offset), iterable);
7068
}
7169

7270
/**
@@ -161,8 +159,9 @@ export function* cycle<T>(iterable: Iterable<T>): Iterable<T> {
161159
/**
162160
* Make an iterator that repeats `value` over and over again.
163161
*/
164-
export function* repeat<T>(value: T): Iterable<T> {
165-
while (true) yield value;
162+
export function* repeat<T>(value: T, times?: number): Iterable<T> {
163+
if (times === undefined) while (true) yield value;
164+
for (let i = 0; i < times; i++) yield value;
166165
}
167166

168167
/**
@@ -585,3 +584,37 @@ export function max<T extends number>(
585584
export function sum(iterable: Iterable<number>, start = 0): number {
586585
return reduce(iterable, (x, y) => x + y, start);
587586
}
587+
588+
/**
589+
* Recursively produce all produces of a list of iterators.
590+
*/
591+
function* _product<T>(
592+
pools: Iterator<T | typeof SENTINEL>[],
593+
buffer: T[] = []
594+
): Iterable<T[]> {
595+
if (pools.length === 0) {
596+
yield buffer.slice();
597+
return;
598+
}
599+
600+
const [pool, ...others] = pools;
601+
602+
while (true) {
603+
const item = pool.next();
604+
if (item.value === SENTINEL) break;
605+
buffer.push(item.value);
606+
yield* _product(others, buffer);
607+
buffer.pop();
608+
}
609+
}
610+
611+
/**
612+
* Cartesian product of input iterables.
613+
*/
614+
export function* product<T extends any[]>(
615+
...iterables: TupleIterable<T>
616+
): Iterable<T> {
617+
const pools = iterables.map(x => iter(cycle(chain(x, repeat(SENTINEL, 1)))));
618+
619+
yield* _product(pools) as Iterable<T>;
620+
}

0 commit comments

Comments
 (0)