blob: 256e313de44c25404ef7c21ba096ca871a2d715d [file] [log] [blame]
/**
* @license
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import cartesianArrays from 'cartesian';
export async function* product<T>(...iterables: AsyncIterable<T>[]): AsyncGenerator<Array<T>, void, undefined> {
// We listen to all iterators in parallel, while logging all the values they
// produce. Whenever an iterator produces a value, we produce and yield all
// combinations of that value with the logged values from other iterators.
// Every combination is thus made exactly once, and as soon as it is known.
const iterators = iterables.map(iterable => iterable[Symbol.asyncIterator]());
// Initialise an empty log for each iterable.
const logs: T[][] = iterables.map(() => []);
type NumberedResultPromise = Promise<{
nextResult: IteratorResult<T>,
iterableNr: number
}>;
function notNull(p: NumberedResultPromise | null): p is NumberedResultPromise {
return p !== null
}
const nextValuePromises: Array<NumberedResultPromise | null> = iterators.map((iterator, iterableNr) =>
iterator
.next()
.then(
// Label the result with iterableNr, to know which iterable produced
// this value after Promise.race below.
nextResult => ({ nextResult, iterableNr }),
),
);
// Keep listening as long as any of the iterables is not yet exhausted.
while (nextValuePromises.some(notNull)) {
// Wait until any of the active iterators has produced a new value.
const { nextResult, iterableNr } = await Promise.race(
nextValuePromises.filter(notNull),
);
// If this iterable was exhausted, stop listening to it and move on.
if (nextResult.done === true) {
nextValuePromises[iterableNr] = null;
continue;
}
// Produce all combinations of the received value with the logged values
// from the other iterables.
const arrays = [...logs];
arrays[iterableNr] = [nextResult.value];
const combinations: T[][] = cartesianArrays(arrays);
// Append the received value to the right log.
logs[iterableNr] = [...logs[iterableNr], nextResult.value];
// Start listening for the next value of this iterable.
nextValuePromises[iterableNr] = iterators[iterableNr]
.next()
.then(nextResult => ({ nextResult, iterableNr }));
// Yield each of the produced combinations separately.
yield* combinations;
}
}