const cartesian = (...arrays) =>
// итеративно получаем декартово произведение
// нескольких первых массивов из arrays,
// начиная с нуля массивов и пустого декартова произведения --- [[]]
arrays.reduce((cartesianPart, array) =>
// cartesianPart --- декартово произведение нескольких первых массивов из arrays
cartesianPart.flatMap(cartesianPartTuple =>
// array --- новый массив из arrays для добавления в декартово произведение
array.map(arrayElement =>
// cartesianPartTuple --- массив-префикс одного из элементов декартова произведения
// arrayElement --- элемент одного из массива из arrays
[...cartesianPartTuple, arrayElement]
)
),
[[]]
);
非递归方法,通过其数显式构造笛卡尔积的元素
function cartesian(...arrays) {
// находим число элементов в декартовом произведении
let resultLength = 1;
for (const array of arrays) {
resultLength *= array.length;
}
// создаём массив такого размера и перебираем номер элемента
const result = new Array(resultLength);
for (let i = 0; i < resultLength; ++i) {
// один элемент декартова произведения
const tuple = new Array(arrays.length);
let tupleIndex = i;
// цикл в обратном порядке нужен чтобы элементы начинали изменяться с конца
for (let j = arrays.length - 1; j >= 0; --j) {
const array = arrays[j];
// имеем биекцию между индексами элементов декартова произведения (числа от 0 до resultLength-1)
// и кортежами длины arrays.length, в которых каждый элемент — индекс в соответствующем массиве
tuple[j] = array[tupleIndex % array.length];
// целочисленное деление
tupleIndex = Math.floor(tupleIndex / array.length);
}
result[i] = tuple;
}
return result;
}
function cartesian3(...arrays) {
const result = [];
// функция, которая будет рекурсивно вызываться
// глубина рекурсии равна arrays.length
// в процессе рекурсии функция будет создавать часть элемента декартова произведения
// в конце рекусрии функция добавит созданный элемент в массив result
const recursion = (tuplePart) => {
if (tuplePart.length === arrays.length) {
result.push(tuplePart);
} else {
const array = arrays[tuplePart.length];
for (const element of array) {
// создаём копию tuplePart и добавляем в неё очередной элемент
const tuplePartWithNewElement = tuplePart.concat([element]);
recursion(tuplePartWithNewElement);
}
}
};
recursion([]);
return result;
}
Snippet,它检查所有建议方法的工作的正确性
// cartesian 1
const cartesian1 = (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [...d, e])), [[]]);
// cartesian 1 (с комментариями)
const cartesian1b = (...arrays) =>
// итеративно получаем декартово произведение
// нескольких первых массивов из arrays,
// начиная с нуля массивов и пустого декартова произведения --- [[]]
arrays.reduce((cartesianPart, array) =>
// cartesianPart --- декартово произведение нескольких первых массивов из arrays
cartesianPart.flatMap(cartesianPartTuple =>
// array --- новый массив из arrays для добавления в декартово произведение
array.map(arrayElement =>
// cartesianPartTuple --- массив-префикс одного из элементов декартова произведения
// arrayElement --- элемент одного из массива из arrays
[...cartesianPartTuple, arrayElement]
)
),
[[]]
);
// cartesian 2
function cartesian2(...arrays) {
// находим число элементов в декартовом произведении
let resultLength = 1;
for (const array of arrays) {
resultLength *= array.length;
}
// создаём массив такого размера и перебираем номер элемента
const result = new Array(resultLength);
for (let i = 0; i < resultLength; ++i) {
// один элемент декартова произведения
const tuple = new Array(arrays.length);
let tupleIndex = i;
// цикл в обратном порядке нужен чтобы элементы начинали изменяться с конца
for (let j = arrays.length - 1; j >= 0; --j) {
const array = arrays[j];
// имеем биекцию между индексами элементов декартова произведения (числа от 0 до resultLength-1)
// и кортежами длины arrays.length, в которых каждый элемент — индекс в соответствующем массиве
tuple[j] = array[tupleIndex % array.length];
// целочисленное деление
tupleIndex = Math.floor(tupleIndex / array.length);
}
result[i] = tuple;
}
return result;
}
// cartesian 3
function cartesian3(...arrays) {
const result = [];
// функция, которая будет рекурсивно вызываться
// глубина рекурсии равна arrays.length
// в процессе рекурсии функция будет создавать часть элемента декартова произведения
// в конце рекусрии функция добавит созданный элемент в массив result
const recursion = (tuplePart) => {
if (tuplePart.length === arrays.length) {
result.push(tuplePart);
} else {
const array = arrays[tuplePart.length];
for (const element of array) {
// создаём копию tuplePart и добавляем в неё очередной элемент
const tuplePartWithNewElement = tuplePart.concat([element]);
recursion(tuplePartWithNewElement);
}
}
};
recursion([]);
return result;
}
// tests
const cartesians = [
cartesian1,
cartesian1b,
cartesian2,
cartesian3
];
const tests = [
{
name: 'product of zero arrays',
input: [],
output: [[]]
},
{
name: 'product of single array',
input: [
[1, 2, 3]
],
output: [
[1],
[2],
[3]
]
},
{
name: 'product of two arrays',
input: [
[1, 2, 3],
[10, 20]
],
output: [
[1, 10],
[1, 20],
[2, 10],
[2, 20],
[3, 10],
[3, 20]
]
},
{
name: 'product of three arrays',
input: [
[1, 2],
[10, 20],
[100, 200, 300]
],
output: [
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[1, 20, 100],
[1, 20, 200],
[1, 20, 300],
[2, 10, 100],
[2, 10, 200],
[2, 10, 300],
[2, 20, 100],
[2, 20, 200],
[2, 20, 300]
]
},
{
name: 'nested arrays',
input: [
[[1], [2], [3]],
[[10], [20]]
],
output: [
[[1], [10]],
[[1], [20]],
[[2], [10]],
[[2], [20]],
[[3], [10]],
[[3], [20]]
]
}
];
console.log('если нет сообщений об ошибке, то все функции работают правильно');
cartesians.forEach((cartesian, index) => {
for (const test of tests) {
const output = cartesian(...test.input);
const ok = JSON.stringify(output) === JSON.stringify(test.output);
if (!ok) {
console.log(`FAIL: cartesian function #${index + 1} for test ${test.name}`);
console.log(` expected: ${JSON.stringify(test.output)}`);
console.log(` received: ${JSON.stringify(output)}`);
}
}
});
window.onload = function() {
// несколько массивов
let list1 = [1, 2, 3, 4, 5];
let list2 = [10, 20];
let list3 = [100, 200, 300];
// декартово произведение трёх массивов
let cp = cartesianProduct(list1, list2, list3);
// вывод по столбцам
let rows = 6;
for (let i = 0; i < rows; i++) {
let str = "";
for (let j = 0; j < cp.length; j++)
str += j % rows == i ? JSON.stringify(cp[j]) + " " : "";
console.log(str);
}
};
// декартово произведение нескольких массивов
function cartesianProduct(...lists) {
// входящие данные не равны null
if (lists == null) return null;
// промежуточный результат
let cp = [[]];
// обходим входящие массивы
for (let i = 0; i < lists.length; i++) {
// текущий массив
let list = lists[i]
// ненулевой и непустой массив
if (list == null || list.length == 0) continue;
// следующий промежуточный результат
let next = [];
// строки текущего промежуточного результата
for (let j = 0; j < cp.length; j++) {
// текущая строка
let row = cp[j];
// элементы текущего массива
for (let k = 0; k < list.length; k++) {
// текущий элемент
let el = list[k];
// новая строка для следующего
// промежуточного результата
let nRow = row.slice();
nRow[row.length] = el;
next[next.length] = nRow;
}
}
// передать на следующую итерацию
cp = next;
}
// итоговый результат
return cp;
}
纯 JavaScript 中的最短路径
此方法正确处理空笛卡尔积,并且在数组中有嵌套数组时也可以正常工作(与 enSO 的答案不同!)。
被使用:
.reduce
,.map
,.flatMap
...
和与评论相同。
非递归方法,通过其数显式构造笛卡尔积的元素
递归方式
在递归的帮助下,将创建笛卡尔积的元素:从一个空数组开始,在每个递归步骤中,将当前数组的所有元素都排序出来,笛卡尔积元素的类型化前缀的副本是创建后,将一个数组元素添加到副本中,并对生成的新前缀进行递归调用。
Snippet,它检查所有建议方法的工作的正确性
准备好的库实现
一些库具有笛卡尔积函数:
cartesianProduct
cross
(仅适用于两个数组)多个数组的直接或笛卡尔积