-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(utils): 新增 cartesianProduct 计算多个数组的笛卡尔积
- Loading branch information
Showing
3 changed files
with
360 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
const _Function = Function | ||
|
||
describe('cartesianProduct - fast', () => { | ||
beforeAll(() => { | ||
jest.resetModules() | ||
global.Function = _Function | ||
}) | ||
|
||
test('正常', async () => { | ||
const { cartesianProduct } = await import('./cartesianProduct') | ||
|
||
expect(cartesianProduct([])).toEqual([]) | ||
|
||
expect(cartesianProduct([[], []])).toEqual([]) | ||
|
||
expect(cartesianProduct([['a']])).toEqual([['a']]) | ||
|
||
expect(cartesianProduct([['a', 'b']])).toEqual([['a'], ['b']]) | ||
|
||
expect( | ||
cartesianProduct([ | ||
['a', 'b'], | ||
[1, 2], | ||
]), | ||
).toEqual([ | ||
['a', 1], | ||
['a', 2], | ||
['b', 1], | ||
['b', 2], | ||
]) | ||
|
||
// adding a 0 element set causes the entire product to be empty. | ||
expect(cartesianProduct([['a', 'b'], [1, 2], []])).toEqual([]) | ||
|
||
expect( | ||
cartesianProduct([ | ||
[1, 2], | ||
['a', 'b', 'c'], | ||
]), | ||
).toEqual([ | ||
[1, 'a'], | ||
[1, 'b'], | ||
[1, 'c'], | ||
[2, 'a'], | ||
[2, 'b'], | ||
[2, 'c'], | ||
]) | ||
|
||
expect( | ||
cartesianProduct([ | ||
['a', 'b'], | ||
[1, 2], | ||
[[], {}], | ||
]), | ||
).toEqual([ | ||
['a', 1, []], | ||
['a', 1, {}], | ||
['a', 2, []], | ||
['a', 2, {}], | ||
['b', 1, []], | ||
['b', 1, {}], | ||
['b', 2, []], | ||
['b', 2, {}], | ||
]) | ||
}) | ||
|
||
test('报错', async () => { | ||
const { cartesianProduct } = await import('./cartesianProduct') | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(1), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(null), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct('js'), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(['dd', ['d']]), | ||
).toThrowError(/index 0 must be an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct([['dd'], ['d'], {}]), | ||
).toThrowError(/index 2 must be an array/) | ||
}) | ||
}) | ||
|
||
describe('cartesianProduct - universal', () => { | ||
beforeAll(() => { | ||
jest.resetModules() | ||
// @ts-expect-error | ||
global.Function = class Function2 extends _Function { | ||
constructor(...args: any[]) { | ||
if (args[0] === '1') { | ||
throw new Error() | ||
} | ||
super(...args) | ||
} | ||
} | ||
}) | ||
|
||
test('正常', async () => { | ||
const { cartesianProduct } = await import('./cartesianProduct') | ||
|
||
expect(cartesianProduct([])).toEqual([]) | ||
|
||
expect(cartesianProduct([[], []])).toEqual([]) | ||
|
||
expect(cartesianProduct([['a']])).toEqual([['a']]) | ||
|
||
expect(cartesianProduct([['a', 'b']])).toEqual([['a'], ['b']]) | ||
|
||
expect( | ||
cartesianProduct([ | ||
['a', 'b'], | ||
[1, 2], | ||
]), | ||
).toEqual([ | ||
['a', 1], | ||
['a', 2], | ||
['b', 1], | ||
['b', 2], | ||
]) | ||
|
||
// adding a 0 element set causes the entire product to be empty. | ||
expect(cartesianProduct([['a', 'b'], [1, 2], []])).toEqual([]) | ||
|
||
expect( | ||
cartesianProduct([ | ||
[1, 2], | ||
['a', 'b', 'c'], | ||
]), | ||
).toEqual([ | ||
[1, 'a'], | ||
[1, 'b'], | ||
[1, 'c'], | ||
[2, 'a'], | ||
[2, 'b'], | ||
[2, 'c'], | ||
]) | ||
|
||
expect( | ||
cartesianProduct([ | ||
['a', 'b'], | ||
[1, 2], | ||
[[], {}], | ||
]), | ||
).toEqual([ | ||
['a', 1, []], | ||
['a', 1, {}], | ||
['a', 2, []], | ||
['a', 2, {}], | ||
['b', 1, []], | ||
['b', 1, {}], | ||
['b', 2, []], | ||
['b', 2, {}], | ||
]) | ||
}) | ||
|
||
test('报错', async () => { | ||
const { cartesianProduct } = await import('./cartesianProduct') | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(1), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(null), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct('js'), | ||
).toThrowError(/expects an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct(['dd', ['d']]), | ||
).toThrowError(/index 0 must be an array/) | ||
|
||
expect(() => | ||
// @ts-expect-error | ||
cartesianProduct([['dd'], ['d'], {}]), | ||
).toThrowError(/index 2 must be an array/) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
const supportNewFunction = (() => { | ||
try { | ||
new Function('1') | ||
return true | ||
} catch (err) { | ||
return false | ||
} | ||
})() | ||
|
||
/** | ||
* 计算多个数组的笛卡尔积。 | ||
* | ||
* @param arr 数组内容 | ||
* @example | ||
* ```typescript | ||
* cartesianProduct([ | ||
* ['a', 'b'], | ||
* [1, 2], | ||
* ]) | ||
* // => [['a', 1], ['a', 2], ['b', 1], ['b', 2]] | ||
* ``` | ||
*/ | ||
export function cartesianProduct<T>(arr: [T[]]): [T][] | ||
export function cartesianProduct<T, U>(arr: [T[], U[]]): [T, U][] | ||
export function cartesianProduct<T, U, V>(arr: [T[], U[], V[]]): [T, U, V][] | ||
export function cartesianProduct<T, U, V, W>( | ||
arr: [T[], U[], V[], W[]], | ||
): [T, U, V, W][] | ||
export function cartesianProduct<T, U, V, W, X>( | ||
arr: [T[], U[], V[], W[], X[]], | ||
): [T, U, V, W, X][] | ||
export function cartesianProduct<T, U, V, W, X, Y>( | ||
arr: [T[], U[], V[], W[], X[], Y[]], | ||
): [T, U, V, W, X, Y][] | ||
export function cartesianProduct<T, U, V, W, X, Y, Z>( | ||
arr: [T[], U[], V[], W[], X[], Y[], Z[]], | ||
): [T, U, V, W, X, Y, Z][] | ||
export function cartesianProduct(arr: any[][]): any[][] | ||
|
||
export function cartesianProduct(arr: any[][]): any[][] { | ||
if (!Array.isArray(arr)) { | ||
throw new Error('cartesianProduct expects an array') | ||
} | ||
|
||
if (!arr.length) { | ||
return [] | ||
} | ||
|
||
if (!Array.isArray(arr[0])) { | ||
throw new Error('set at index 0 must be an array') | ||
} | ||
|
||
return ( | ||
supportNewFunction && arr.length < 100 | ||
? // 在支持动态构造函数环境下,并且数组长度小于 100 时使用快速模式 | ||
cartesianProductProviders.fast | ||
: // 否则使用通用模式,如:小程序、数组长度大于等于 100 | ||
cartesianProductProviders.universal | ||
).run(arr) | ||
} | ||
|
||
const cartesianProductProviders: Record< | ||
'universal' | 'fast', | ||
{ | ||
run(arr: any[][]): any[][] | ||
[K: string]: any | ||
} | ||
> = { | ||
// https://github.com/angus-c/just/blob/master/packages/array-cartesian-product/index.js | ||
universal: { | ||
run(arr) { | ||
// initialize our product array | ||
let product = arr[0].map(function (v) { | ||
return [v] | ||
}) | ||
|
||
for (let i = 1; i < arr.length; i++) { | ||
if (!Array.isArray(arr[i])) { | ||
throw new Error(`set at index ${i} must be an array`) | ||
} | ||
product = cartesianProductProviders.universal.baseProduct( | ||
product, | ||
arr[i], | ||
) | ||
} | ||
|
||
return product | ||
}, | ||
baseProduct(prevProduct: any[], arr2: any[]) { | ||
// pre allocate all our memory | ||
const newProduct = new Array(prevProduct.length * arr2.length) | ||
|
||
for (let i = 0; i < prevProduct.length; i++) { | ||
for (let j = 0; j < arr2.length; j++) { | ||
// always provide array to array.concat for consistent behavior | ||
newProduct[i * arr2.length + j] = prevProduct[i].concat([arr2[j]]) | ||
} | ||
} | ||
return newProduct | ||
}, | ||
}, | ||
// https://github.com/ehmicky/fast-cartesian/blob/main/src/main.js | ||
fast: { | ||
cache: Object.create(null), | ||
run(arr) { | ||
const loopFunc = cartesianProductProviders.fast.getLoopFunc(arr.length) | ||
const result: any[][] = [] | ||
loopFunc(arr, result) | ||
return result | ||
}, | ||
getLoopFunc(length: number) { | ||
const cachedLoopFunc = cartesianProductProviders.fast.cache[length] | ||
|
||
if (cachedLoopFunc !== undefined) { | ||
return cachedLoopFunc | ||
} | ||
|
||
const loopFunc = cartesianProductProviders.fast.mGetLoopFunc(length) | ||
cartesianProductProviders.fast.cache[length] = loopFunc | ||
|
||
return loopFunc | ||
}, | ||
mGetLoopFunc(length: number) { | ||
const prefixArr: string[] = [] | ||
const startArr: string[] = [] | ||
const middleArr: string[] = [] | ||
const endArr: string[] = [] | ||
|
||
for (let i = 0; i < length; i++) { | ||
prefixArr.push( | ||
`if (!Array.isArray(arrays[${i}])) { throw new Error('set at index ${i} must be an array') }`, | ||
) | ||
startArr.push( | ||
`for (var i${i} = 0; i${i} < arrays[${i}].length; i${i}++) {`, | ||
) | ||
middleArr.push(`arrays[${i}][i${i}]`) | ||
endArr.push(`}`) | ||
} | ||
|
||
const prefix = prefixArr.join('\n') | ||
const start = startArr.join('\n') | ||
const middle = middleArr.join(',') | ||
const end = endArr.join('\n') | ||
|
||
return new Function( | ||
'arrays', | ||
'result', | ||
`${prefix}\n${start}\nresult.push([${middle}])\n${end}`, | ||
) | ||
}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters