Поиск всех комбинаций значений массива 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)