Поиск всех комбинаций значений массива JavaScript
Как я могу произвести все комбинации значений в N числе массивов JavaScript переменных длин?
Скажем, у меня есть N количество массивов JavaScript, например
var first = ['a', 'b', 'c', 'd'];
var second = ['e'];
var third = ['f', 'g', 'h', 'i', 'j'];
(Три массива в этом примере, но его N количество массивов для проблемы.)
И я хочу вывести все комбинации их значений, чтобы создать
aef
aeg
aeh
aei
aej
bef
beg
....
dej
EDIT: Здесь версия, в которой я работал, использует в качестве основы принятый ответ.
var allArrays = [['a', 'b'], ['c', 'z'], ['d', 'e', 'f']];
function allPossibleCases(arr) {
if (arr.length === 0) {
return [];
}
else if (arr.length ===1){
return arr[0];
}
else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array
for (var c in allCasesOfRest) {
for (var i = 0; i < arr[0].length; i++) {
result.push(arr[0][i] + allCasesOfRest[c]);
}
}
return result;
}
}
var r=allPossibleCases(allArrays);
//outputs ["acd", "bcd", "azd", "bzd", "ace", "bce", "aze", "bze", "acf", "bcf", "azf", "bzf"]
Ответы
Ответ 1
Это не перестановки, см. определения перестановок из Википедии.
Но вы можете добиться этого с помощью рекурсии:
var allArrays = [['a', 'b'], ['c'], ['d', 'e', 'f']]
function allPossibleCases(arr) {
if (arr.length == 1) {
return arr[0];
} else {
var result = [];
var allCasesOfRest = allPossibleCases(arr.slice(1)); // recur with the rest of array
for (var i = 0; i < allCasesOfRest.length; i++) {
for (var j = 0; j < arr[0].length; j++) {
result.push(arr[0][j] + allCasesOfRest[i]);
}
}
return result;
}
}
Вы также можете сделать это с помощью циклов, но это будет немного сложно, и вам потребуется реализовать собственный аналог стека.
Ответ 2
Я предлагаю простую рекурсивную функцию генератора следующим образом:
// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
let remainder = tail.length ? cartesian(...tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
// Example:
const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third = ['f', 'g', 'h', 'i', 'j'];
console.log(...cartesian(first, second, third));
Ответ 3
Вам не нужна рекурсия или сильно вложенные циклы или даже для создания/хранения всего массива перестановок в памяти.
Так как число перестановок является произведением длин каждого из массивов (назовем это numPerms
), вы можете создать функцию getPermutation(n)
, которая возвращает уникальную перестановку между индексами 0
и numPerms - 1
на вычисляя индексы, необходимые для извлечения своих символов, на основе n
.
Как это делается? Если вы думаете о создании перестановок на массивах, каждый из которых содержит: [0, 1, 2,... 9], это очень просто... 245-я перестановка (n = 245) является "245", скорее интуитивно, или:
arrayHundreds[Math.floor(n / 100) % 10]
+ arrayTens[Math.floor(n / 10) % 10]
+ arrayOnes[Math.floor(n / 1) % 10]
Усложнение в вашей проблеме заключается в том, что размеры массива различаются. Мы можем обойти это, заменив n/100
, n/10
и т.д. Другими делителями. С этой целью мы можем легко вычислить массив делителей. В приведенном выше примере делитель 100 был равен arrayTens.length * arrayOnes.length
. Поэтому мы можем вычислить делитель для данного массива как произведение длин оставшихся массивов. У самого последнего массива всегда есть делитель 1. Кроме того, вместо modding на 10 мы mod по длине текущего массива.
Пример кода ниже:
var allArrays = [first, second, third, ...];
// Pre-calculate divisors
var divisors = [];
for (var i = allArrays.length - 1; i >= 0; i--) {
divisors[i] = divisors[i + 1] ? divisors[i + 1] * allArrays[i + 1].length : 1;
}
function getPermutation(n) {
var result = "", curArray;
for (var i = 0; i < allArrays.length; i++) {
curArray = allArrays[i];
result += curArray[Math.floor(n / divisors[i]) % curArray.length];
}
return result;
}
Ответ 4
Предоставленные ответы слишком сложны для меня. Итак, мое решение:
var allArrays = new Array(['a', 'b'], ['c', 'z'], ['d', 'e', 'f']);
function getPermutation(array, prefix) {
prefix = prefix || '';
if (!array.length) {
return prefix;
}
var result = array[0].reduce(function (result, value) {
return result.concat(getPermutation(array.slice(1), prefix + value));
}, []);
return result;
}
console.log(getPermutation(allArrays));
Ответ 5
Вы можете использовать типичный обратный отсчет:
function cartesianProductConcatenate(arr) {
var data = new Array(arr.length);
return (function* recursive(pos) {
if(pos === arr.length) yield data.join('');
else for(var i=0; i<arr[pos].length; ++i) {
data[pos] = arr[pos][i];
yield* recursive(pos+1);
}
})(0);
}
Я использовал функции генератора, чтобы избежать одновременного выделения всех результатов, но если вы хотите, вы можете
[...cartesianProductConcatenate([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']])];
// ["acd","ace","acf","azd","aze","azf","bcd","bce","bcf","bzd","bze","bzf"]
Ответ 6
Копия файла le_m Ответ на получение массива массивов напрямую:
function *combinations(arrOfArr) {
let [head, ...tail] = arrOfArr
let remainder = tail.length ? combinations(tail) : [[]];
for (let r of remainder) for (let h of head) yield [h, ...r];
}
Надеюсь, это сэкономит время.
Ответ 7
Вы можете использовать однолинейный подход, генерируя декартово произведение.
result = items.reduce(
(a, b) => a.reduce(
(r, v) => r.concat(b.map(w => [].concat(v, w))),
[]
)
);
var items = [['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']],
result = items.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []));
console.log(result.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Ответ 8
Самый простой способ найти комбинации
const arr1= [ 'a', 'b', 'c', 'd' ];
const arr2= [ '1', '2', '3' ];
const arr3= [ 'x', 'y', ];
const all = [arr1, arr2, arr3];
const output = all.reduce((acc, cu) => {
let ret = [];
acc.map(obj => {
cu.map(obj_1 => {
ret.push(obj + '-' + obj_1)
});
});
return ret;
})
console.log(output);
Ответ 9
Если вы ищете совместимую с потоком функцию, которая может обрабатывать двумерные массивы с любым типом элемента, вы можете использовать функцию ниже.
const getUniqueCombinations = <T>(items : Array<Array<T>>, prepend : Array<T> = []) : Array<Array<T>> => {
if(!items || items.length === 0) return [prepend];
let out = [];
for(let i = 0; i < items[0].length; i++){
out = [...out, ...getUniqueCombinations(items.slice(1), [...prepend, items[0][i]])];
}
return out;
}
Визуализация операции:
в:
[
[Obj1, Obj2, Obj3],
[Obj4, Obj5],
[Obj6, Obj7]
]
из:
[
[Obj1, Obj4, Obj6 ],
[Obj1, Obj4, Obj7 ],
[Obj1, Obj5, Obj6 ],
[Obj1, Obj5, Obj7 ],
[Obj2, Obj4, Obj6 ],
[Obj2, Obj4, Obj7 ],
[Obj2, Obj5, Obj6 ],
[Obj2, Obj5, Obj7 ],
[Obj3, Obj4, Obj6 ],
[Obj3, Obj4, Obj7 ],
[Obj3, Obj5, Obj6 ],
[Obj3, Obj5, Obj7 ]
]
Ответ 10
Вот версия, адаптированная из приведенной выше пары ответов, которая выдает результаты в порядке, указанном в OP, и возвращает строки вместо массивов:
function *cartesianProduct(...arrays) {
if (!arrays.length) yield [];
else {
const [tail, ...head] = arrays.reverse();
const beginning = cartesianProduct(...head.reverse());
for (let b of beginning) for (let t of tail) yield b + t;
}
}
const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third = ['f', 'g', 'h', 'i', 'j'];
console.log([...cartesianProduct(first, second, third)])
Ответ 11
Вы также можете использовать эту функцию:
const result = (arrayOfArrays) => arrayOfArrays.reduce((t, i) => { let ac = []; for (const ti of t) { for (const ii of i) { ac.push(ti + '/' + ii) } } return ac })
result([['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']])
// which will output [ 'a/e/f', 'a/e/g', 'a/e/h','a/e/i','a/e/j','b/e/f','b/e/g','b/e/h','b/e/i','b/e/j','c/e/f','c/e/g','c/e/h','c/e/i','c/e/j','d/e/f','d/e/g','d/e/h','d/e/i','d/e/j']
Конечно, вы можете удалить + '/'
в ac.push(ti + '/' + ii)
чтобы убрать косую черту из конечного результата. И вы можете заменить их for (... of...)
на функции forEach (плюс соответствующую точку с запятой перед return ac
), независимо от того, с кем вам удобнее.
Ответ 12
Массивный подход без рекурсии:
const combinations = [['1', '2', '3'], ['4', '5', '6'], ['7', '8']];
let outputCombinations = combinations[0]
combinations.slice(1).forEach(row => {
outputCombinations = outputCombinations.reduce((acc, existing) =>
acc.concat(row.map(item => existing + item))
, []);
});
console.log(outputCombinations);
Ответ 13
Вы можете создать 2D-массив и reduce
его. Затем используйте flatMap
для создания комбинаций строк в массиве аккумулятора и текущего итерируемого массива и объединения их.
const data = [ ['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j'] ]
const output = data.reduce((acc, cur, i) => acc.flatMap(c => cur.map(n => c + n)) )
console.log(output)