Extract cartesian test into test file
[incubator-annotator.git] / packages / range / src / cartesian.mjs
1 import cartesianArrays from 'cartesian';
2
3 export async function* product(...iterables) {
4   // We listen to all iterators in parallel, while logging all the values they
5   // produce. Whenever an iterator produces a value, we produce and yield all
6   // combinations of that value with the logged values from other iterators.
7   // Every combination is thus made exactly once, and as soon as it is known.
8
9   const iterators = iterables.map(iterable => iterable[Symbol.asyncIterator]());
10   // Initialise an empty log for each iterable.
11   const logs = iterables.map(() => []);
12
13   const nextValuePromises = iterators.map((iterator, iterableNr) =>
14     iterator
15       .next()
16       .then(async ({ value, done }) => ({ value: await value, done }))
17       .then(
18         // Label the result with iterableNr, to know which iterable produced
19         // this value after Promise.race below.
20         ({ value, done }) => ({ value, done, iterableNr })
21       )
22   );
23
24   // Keep listening as long as any of the iterables is not yet exhausted.
25   while (nextValuePromises.some(p => p !== null)) {
26     // Wait until any of the active iterators has produced a new value.
27     const { value, done, iterableNr } = await Promise.race(
28       nextValuePromises.filter(p => p !== null)
29     );
30
31     // If this iterable was exhausted, stop listening to it and move on.
32     if (done) {
33       nextValuePromises[iterableNr] = null;
34       continue;
35     }
36
37     // Produce all combinations of the received value with the logged values
38     // from the other iterables.
39     const arrays = [...logs];
40     arrays[iterableNr] = [value];
41     const combinations = cartesianArrays(arrays);
42
43     // Append the received value to the right log.
44     logs[iterableNr] = [...logs[iterableNr], value];
45
46     // Start listening for the next value of this iterable.
47     nextValuePromises[iterableNr] = iterators[iterableNr]
48       .next()
49       .then(async ({ value, done }) => ({ value: await value, done }))
50       .then(({ value, done }) => ({ value, done, iterableNr }));
51
52     // Yield each of the produced combinations separately.
53     yield* combinations;
54   }
55 }