diff --git a/benchmarks/array/cartesianProduct.bench.ts b/benchmarks/array/cartesianProduct.bench.ts new file mode 100644 index 00000000..37b36ec5 --- /dev/null +++ b/benchmarks/array/cartesianProduct.bench.ts @@ -0,0 +1,19 @@ +import * as _ from 'radashi' + +describe('cartesianProduct', () => { + bench('with an empty array (n=1)', () => { + _.cartesianProduct([]) + }) + + bench('with a non-empty array (n=1)', () => { + _.cartesianProduct(['a', 'b', 'c']) + }) + + bench('with two small arrays (n=2)', () => { + _.cartesianProduct(['red', 'blue'], ['fast', 'slow']) + }) + + bench('with three small arrays (n=3)', () => { + _.cartesianProduct(['red', 'blue'], ['fast', 'slow'], ['big', 'small']) + }) +}) diff --git a/docs/array/cartesianProduct.mdx b/docs/array/cartesianProduct.mdx new file mode 100644 index 00000000..75250464 --- /dev/null +++ b/docs/array/cartesianProduct.mdx @@ -0,0 +1,35 @@ +--- +title: cartesianProduct +description: Perform a Cartesian product of arrays +--- + +### Usage + +Create an [n-ary Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product#n-ary_Cartesian_product) from the given arrays. +The inputs are arrays, and the output is an array of arrays representing all +possible combinations where the first element is from the first array, the second element +is from the second array, and so on. + +```ts +import * as _ from 'radashi' + +const colors = ['red', 'blue'] +const numbers = [1, 2, 3] +const booleans = [true, false] + +_.cartesianProduct(colors, numbers, booleans) +// => [ +// ['red', 1, true], +// ['red', 1, false], +// ['red', 2, true], +// ['red', 2, false], +// ['red', 3, true], +// ['red', 3, false], +// ['blue', 1, true], +// ['blue', 1, false], +// ['blue', 2, true], +// ['blue', 2, false], +// ['blue', 3, true], +// ['blue', 3, false], +// ] +``` diff --git a/src/array/cartesianProduct.ts b/src/array/cartesianProduct.ts new file mode 100644 index 00000000..162ff0d7 --- /dev/null +++ b/src/array/cartesianProduct.ts @@ -0,0 +1,37 @@ +/** + * Create an [n-ary Cartesian product](https://en.wikipedia.org/wiki/Cartesian_product#n-ary_Cartesian_product) from the given arrays. + * + * @see https://radashi.js.org/reference/array/cartesianProduct + * @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 cartesianProduct( + ...arrays: [...T] +): Array<{ [K in keyof T]: T[K][number] }> +export function cartesianProduct(...arrays: T): T[][] { + let out: T[][] = [[]] + for (const array of arrays) { + const result = [] + 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..53b9ff56 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,5 +1,6 @@ export * from './array/alphabetical.ts' export * from './array/boil.ts' +export * from './array/cartesianProduct.ts' export * from './array/castArray.ts' export * from './array/castArrayIfExists.ts' export * from './array/cluster.ts' diff --git a/tests/array/cartesianProduct.test-d.ts b/tests/array/cartesianProduct.test-d.ts new file mode 100644 index 00000000..2a3bc5dd --- /dev/null +++ b/tests/array/cartesianProduct.test-d.ts @@ -0,0 +1,44 @@ +import * as _ from 'radashi' + +describe('cartesianProduct return type', () => { + test('with an empty array', () => { + const result = _.cartesianProduct([]) + expectTypeOf(result).toEqualTypeOf<[never][]>() + }) + test('with two arrays of the same type', () => { + const result = _.cartesianProduct(['red', 'blue'], ['fast', 'slow']) + const [[v1, v2]] = result + expectTypeOf(result).toEqualTypeOf<[string, string][]>() + expectTypeOf(v1).toEqualTypeOf() + expectTypeOf(v2).toEqualTypeOf() + }) + test('with two arrays of different types', () => { + const result = _.cartesianProduct(['red', 'blue'], [1, 2]) + const [[v1, v2]] = result + expectTypeOf(result).toEqualTypeOf<[string, number][]>() + expectTypeOf(v1).toEqualTypeOf() + expectTypeOf(v2).toEqualTypeOf() + }) + test('with three arrays of different types', () => { + const result = _.cartesianProduct(['red', 'blue'], [1, 2], [true, false]) + const [[v1, v2, v3]] = result + expectTypeOf(result).toEqualTypeOf<[string, number, boolean][]>() + expectTypeOf(v1).toEqualTypeOf() + expectTypeOf(v2).toEqualTypeOf() + expectTypeOf(v3).toEqualTypeOf() + }) + test('with constant arrays of different types', () => { + const result = _.cartesianProduct(['red', 'blue'] as const, [1, 2] as const) + const [[v1, v2]] = result + expectTypeOf(result).toEqualTypeOf<['red' | 'blue', 1 | 2][]>() + expectTypeOf(v1).toEqualTypeOf<'red' | 'blue'>() + expectTypeOf(v2).toEqualTypeOf<1 | 2>() + }) + test('with a mix of constant and non-constant types', () => { + const result = _.cartesianProduct(['red', 'blue'], [1, 2] as const) + const [[v1, v2]] = result + expectTypeOf(result).toEqualTypeOf<[string, 1 | 2][]>() + expectTypeOf(v1).toEqualTypeOf() + expectTypeOf(v2).toEqualTypeOf<1 | 2>() + }) +}) diff --git a/tests/array/cartesianProduct.test.ts b/tests/array/cartesianProduct.test.ts new file mode 100644 index 00000000..10c0d655 --- /dev/null +++ b/tests/array/cartesianProduct.test.ts @@ -0,0 +1,47 @@ +import * as _ from 'radashi' + +describe('cartesianProduct', () => { + test('returns an array containing an empty array when given an empty input (n=0)', () => { + expect(_.cartesianProduct()).toEqual([[]]) + }) + test('returns an empty array when given an empty array (n=1)', () => { + expect(_.cartesianProduct([])).toEqual([]) + }) + test('returns an empty array when given multiple empty arrays (n>1)', () => { + expect(_.cartesianProduct([], [], [])).toEqual([]) + }) + test('returns an empty array when one of the arrays in the input is empty (n>1)', () => { + expect(_.cartesianProduct(['1', '2', '3'], [])).toEqual([]) + }) + test('returns an array of singletons when given a single array (n=1)', () => { + expect(_.cartesianProduct(['1', '2', '3'])).toEqual([['1'], ['2'], ['3']]) + }) + test('performs a correct Cartesian cartesianProduct for two arrays (n=2)', () => { + expect(_.cartesianProduct(['red', 'blue'], [1, 2, 3])).toEqual([ + ['red', 1], + ['red', 2], + ['red', 3], + ['blue', 1], + ['blue', 2], + ['blue', 3], + ]) + }) + test('performs a correct Cartesian cartesianProduct for more than two arrays (n>2)', () => { + expect( + _.cartesianProduct(['red', 'blue'], [1, 2, 3], [true, false]), + ).toEqual([ + ['red', 1, true], + ['red', 1, false], + ['red', 2, true], + ['red', 2, false], + ['red', 3, true], + ['red', 3, false], + ['blue', 1, true], + ['blue', 1, false], + ['blue', 2, true], + ['blue', 2, false], + ['blue', 3, true], + ['blue', 3, false], + ]) + }) +})