diff --git a/benchmarks/array/product.bench.ts b/benchmarks/array/product.bench.ts new file mode 100644 index 00000000..75db3c6f --- /dev/null +++ b/benchmarks/array/product.bench.ts @@ -0,0 +1,30 @@ +import * as _ from 'radashi' + +describe('product', () => { + bench('with no arguments', () => { + _.product([]) + }) + + bench('with single empty array', () => { + _.product([[]]) + }) + + bench('with one non-empty array (n=1)', () => { + _.product([['a', 'b', 'c']]) + }) + + bench('with two small arrays (n=2)', () => { + _.product([ + ['red', 'blue'], + ['fast', 'slow'], + ]) + }) + + bench('with three small arrays (n=3)', () => { + _.product([ + ['red', 'blue'], + ['fast', 'slow'], + ['big', 'small'], + ]) + }) +}) diff --git a/docs/array/product.mdx b/docs/array/product.mdx new file mode 100644 index 00000000..45c9f5cd --- /dev/null +++ b/docs/array/product.mdx @@ -0,0 +1,28 @@ +--- +title: product +description: Perform a Cartesian product of arrays +--- + +### Usage + +Creates an array of all possible combinations (Cartesian product) from the given arrays, where each combination is represented as an array (or tuple) of elements. + +```ts +import * as _ from 'radashi' + +const colors = ['red', 'blue'] +const sizes = ['big', 'small'] +const compliments = ['nice', 'great'] + +_.product([colors, sizes, numbers]) +// => [ +// ['red', 'big', 'nice'], +// ['red', 'big', 'great'], +// ['red', 'small', 'nice'], +// ['red', 'small', 'great'], +// ['blue', 'big', 'nice'], +// ['blue', 'big', 'great'], +// ['blue', 'small', 'nice'], +// ['blue', 'small', 'great'] +// ] +``` diff --git a/src/array/product.ts b/src/array/product.ts new file mode 100644 index 00000000..e2143b32 --- /dev/null +++ b/src/array/product.ts @@ -0,0 +1,26 @@ +/** + * Perform a Cartesian product of arrays, combining all elements from the + * input arrays into all possible combinations. + * + * @see https://radashi.js.org/reference/array/product + * @example + * ```ts + * product([['red', 'blue'], ['big', 'small'], ['fast', 'slow']]) + * // => [['red', 'big', 'fast'], ['red', 'big', 'slow'], ['red', 'small', 'fast'], ['red', 'small', 'slow'], ['blue', 'big', 'fast'], ['blue', 'big', 'slow'], ['blue', 'small', 'fast'], ['blue', 'small', 'slow']] + * ``` + */ +export function product(arrays: T[][]): T[][] { + let out: T[][] = [[]] + for (const array of arrays) { + const result: T[][] = [] + for (const currentArray of out) { + for (const item of array) { + const currentArrayCopy = currentArray.slice() + currentArrayCopy.push(item) + result.push(currentArrayCopy) + } + } + out = result + } + return out +} diff --git a/src/mod.ts b/src/mod.ts index ae241665..c760143b 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -16,6 +16,7 @@ export * from './array/list.ts' export * from './array/mapify.ts' export * from './array/merge.ts' export * from './array/objectify.ts' +export * from './array/product.ts' export * from './array/replace.ts' export * from './array/replaceOrAppend.ts' export * from './array/select.ts' @@ -28,7 +29,6 @@ export * from './array/unique.ts' export * from './array/unzip.ts' export * from './array/zip.ts' export * from './array/zipToObject.ts' - export * from './async/AggregateError.ts' export * from './async/all.ts' export * from './async/defer.ts' diff --git a/tests/array/product.test.ts b/tests/array/product.test.ts new file mode 100644 index 00000000..a29fbc1f --- /dev/null +++ b/tests/array/product.test.ts @@ -0,0 +1,50 @@ +import * as _ from 'radashi' + +describe('product', () => { + test('returns an array containing an empty array when given an empty input array (n=0)', () => { + expect(_.product([])).toEqual([[]]) + }) + test('returns an empty array when given an array containing an empty array (n=1)', () => { + expect(_.product([[]])).toEqual([]) + }) + test('returns an empty array when given multiple empty arrays (n>1)', () => { + expect(_.product([[], [], []])).toEqual([]) + }) + test('returns an empty array when one of the arrays in the input is empty (n>1)', () => { + expect(_.product([['1', '2', '3'], []])).toEqual([]) + }) + test('returns an array of singletons when given a single array (n=1)', () => { + expect(_.product([['1', '2', '3']])).toEqual([['1'], ['2'], ['3']]) + }) + test('performs a correct Cartesian product for two arrays (n=2)', () => { + expect( + _.product([ + ['red', 'blue'], + ['fast', 'slow'], + ]), + ).toEqual([ + ['red', 'fast'], + ['red', 'slow'], + ['blue', 'fast'], + ['blue', 'slow'], + ]) + }) + test('performs a correct Cartesian product for more than two arrays (n>2)', () => { + expect( + _.product([ + ['red', 'blue'], + ['fast', 'slow'], + ['big', 'small'], + ]), + ).toEqual([ + ['red', 'fast', 'big'], + ['red', 'fast', 'small'], + ['red', 'slow', 'big'], + ['red', 'slow', 'small'], + ['blue', 'fast', 'big'], + ['blue', 'fast', 'small'], + ['blue', 'slow', 'big'], + ['blue', 'slow', 'small'], + ]) + }) +})