Как бы вы реализовали декартово произведение множественных массивов в JavaScript?

И, чтобы увидеть все в действии, запустите следующий фрагмент кода

cartesian([1,2],[10,20],[100,200,300]) //should be
// [[1,10,100],[1,10,200],[1,10,300],[2,10,100],[2,10,200]...]

Вот функциональное решение проблемы (без какого-либо изменяемая переменная !) с использованием reduce и flatten, предоставлено underscore.js:

function cartesianProductOf() {
    return _.reduce(arguments, function(a, b) {
        return _.flatten(_.map(a, function(x) {
            return _.map(b, function(y) {
                return x.concat([y]);
            });
        }), true);
    }, [ [] ]);
}

// [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]]
console.log(cartesianProductOf([1, 2], [3, 4], ['a']));  
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore.js"></script>

Примечание: это решение было вдохновлено http://cwestblog.com/2011/05/02/cartesian-product-of- множественные массивы /

Другими словами, сначала

Все ответы здесь слишком сложны , большинство из них занимают 20 строк кода или даже больше.

В этом примере используется только две строки ванильного обновления JavaScript (нажмите F12)

let f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesian = (a, b, ...c) => b ? cartesian(f(a, b), ...c) : a;

:

Это то же самое, что и выше, но улучшено, чтобы строго следовать Руководству по стилю JavaScript в Airbnb — проверено с помощью ESLint Отдельное спасибо . Это как окно, которое вы можете открыть с помощью не является […] непротиворечивой функцией сравнения для элементов этого массива, поведение sort определяется реализацией. :

const f = (a, b) => [].concat(...a.map(d => b.map(e => [].concat(d, e))));
const cartesian = (a, b, ...c) => (b ? cartesian(f(a, b), ...c) : a);

ZuBB за то, что сообщили мне о проблемах с линтером в исходном коде. Это точный пример из вашего вопроса:

Пример

Смотрите демонстрации на:

let output = cartesian([1,2],[10,20],[100,200,300]);

Вывод

Архитектура модульного сервиса

[ [ 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 ] ]

на Jake Archibald на HTML5 Rocks.

Синтаксис, который я здесь использовал, не нов. В моем примере используется оператор распространения, а остальные параметры — функции JavaScript, определенные в шестом разделе. издание стандарта ECMA-262, опубликованное в июне 2015 года и разработанное намного раньше, более известное как ES6 или ES2015. Смотрите:

синтаксис { *} HTML5 предоставляет Canvas.toDataURL (mimetype), который реализован в бета-версиях Opera, Firefox и Safari 4. Однако существует ряд ограничений безопасности (в основном для рисования контента из другого источника на холсте).

Это делает такой код настолько простым, что грех его не использовать. Для старых платформ, которые не поддерживают его изначально, вы всегда можете использовать Babel или другие инструменты, чтобы перенести его в более старый синтаксис — и фактически мой пример, переданный Babel, все еще короче и проще, чем большинство примеров здесь, но это не так действительно важно, потому что вывод транспиляции — это не то, что вам нужно понимать или поддерживать, это просто факт, который мне показался интересным.

Не кодируйте, как в 1995 году

Заключение

Нет необходимости писать сотни строк кода, которые его трудно поддерживать, и нет необходимости использовать целые библиотеки для такой простой вещи, когда две строки ванильного JavaScript могут легко выполнить свою работу. Как вы можете видеть, это действительно окупается при использовании современных возможностей языка и в случаях там, где вам нужно поддерживать архаичные платформы без встроенной поддержки современных функций, вы всегда можете использовать Babel или другие инструменты для переноса нового синтаксиса на старый.

JavaScript развивается, и это происходит по причине. TC39 делает потрясающую работу над языковым дизайном с добавлением новых функций, а производители браузеров делают потрясающую работу по реализации этих функций.

Чтобы увидеть текущее состояние встроенной поддержки любой данной функции в браузерах, см .:

Чтобы увидеть поддержку в версиях Node, см .:

Чтобы использовать современный синтаксис на платформах, которые не поддерживают его изначально , используйте Babel:

Вот модифицированная версия кода @ viebel в простом Javascript, без использования какой-либо библиотеки:

по эффективности: вы можете получить ее, вынув if из цикла и имея 2 отдельных цикла, так как он технически постоянна, и вы бы помогли с предсказанием ветвлений и всем этим беспорядком, но этот момент является своего рода спорным в javascript

function cartesianProduct(arr)
{
    return arr.reduce(function(a,b){
        return a.map(function(x){
            return b.map(function(y){
                return x.concat(y);
            })
        }).reduce(function(a,b){ return a.concat(b) },[])
    }, [[]])
}

var a = cartesianProduct([[1, 2,3], [4, 5,6], [7, 8], [9,10]]);
console.log(a);

кажется, что сообщество считает это тривиальным и / или легко найти эталонную реализацию, после краткого осмотра я не смог, или, может быть, просто мне нравится заново изобретать колесо или решать задачи программирования, подобные классной, в любом случае, ваш счастливый день:

function cartProd(paramArray) {

  function addTo(curr, args) {

    var i, copy, 
        rest = args.slice(1),
        last = !rest.length,
        result = [];

    for (i = 0; i < args[0].length; i  ) {

      copy = curr.slice();
      copy.push(args[0][i]);

      if (last) {
        result.push(copy);

      } else {
        result = result.concat(addTo(copy, rest));
      }
    }

    return result;
  }


  return addTo([], Array.prototype.slice.call(arguments));
}


>> console.log(cartProd([1,2], [10,20], [100,200,300]));
>> [
     [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]
   ]

полной справочной реализации, относительно эффективна … :-D

Вот необычное, простое рекурсивное решение:

Во всяком случае, я думаю, что это достаточно близко на данный момент. Спасибо всем!

Следующая эффективная

function cartesianProduct(a) { // a = array of array
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0]; // the first array of a
    a = cartesianProduct(a);
    for (i = 0, l = a1.length; i < l; i  ) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j  )
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}

console.log(cartesianProduct([[1,2], [10,20], [100,200,300]]));
// [[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]]

функция генератора { *} возвращает декартово произведение всех заданных итераций генераторная функция . Он принимает массивы, строки, множества и все другие объекты, реализующие итерируемый протокол :

// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Example:
console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));

, следуя спецификации , которую он дает Глядя на вывод

если одна или несколько заданных итераций пусты, например, ), которое is of дано.

  • [] Все остальные случаи обрабатываются, как и ожидалось, как показано в следующих тестовых примерах: [] или ''
  • [[a]] если одна итерация, содержащая одно значение a Вот рекурсивный способ, который использует функцию генератора ECMAScript 2015

Все остальные случаи обрабатываются, как и ожидалось, как продемонстрировано в следующих тестовых примерах:

// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  const remainder = tail.length > 0 ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Test cases:
console.log([...cartesian([])]);              // []
console.log([...cartesian([1])]);             // [[1]]
console.log([...cartesian([1, 2])]);          // [[1], [2]]

console.log([...cartesian([1], [])]);         // []
console.log([...cartesian([1, 2], [])]);      // []

console.log([...cartesian([1], [2])]);        // [[1, 2]]
console.log([...cartesian([1], [2], [3])]);   // [[1, 2, 3]]
console.log([...cartesian([1, 2], [3, 4])]);  // [[1, 3], [2, 3], [1, 4], [2, 4]]

console.log([...cartesian('')]);              // []
console.log([...cartesian('ab', 'c')]);       // [['a','c'], ['b', 'c']]
console.log([...cartesian([1, 2], 'ab')]);    // [[1, 'a'], [2, 'a'], [1, 'b'], [2, 'b']]

console.log([...cartesian(new Set())]);       // []
console.log([...cartesian(new Set([1]))]);    // [[1]]
console.log([...cartesian(new Set([1, 1]))]); // [[1]]

, поэтому вам не нужно создавать все кортежи сразу: Используя типичный возврат с генераторов ES6, Ниже представлена ​​аналогичная версия, совместимая с более старыми браузерами.

function* cartesian() {
    let arrays = arguments;
    function* doCartesian(i, prod) {
        if (i == arrays.length) {
            yield prod;
        } else {
            for (let j = 0; j < arrays[i].length; j  ) {
                yield* doCartesian(i   1, prod.concat([arrays[i][j]]));
            }
        }
    }
    yield* doCartesian(0, []);
}

console.log(JSON.stringify(Array.from(cartesian([1,2],[10,20],[100,200,300]))));
console.log(JSON.stringify(Array.from(cartesian([[1],[2]],[10,20],[100,200,300]))));

Это чистое решение ES6, использующее

function cartesianProduct(...arrays) {
  let current = new Array(arrays.length);
  return (function* backtracking(index) {
    if(index == arrays.length) yield current.slice();
    else for(let num of arrays[index]) {
      current[index] = num;
      yield* backtracking(index 1);
    }
  })(0);
}
for (let item of cartesianProduct([1,2],[10,20],[100,200,300])) {
  console.log('['   item.join(', ')   ']');
}
div.as-console-wrapper { max-height: 100%; }

версию coffeescript с lodash:

function cartesianProduct(arrays) {
  var result = [],
      current = new Array(arrays.length);
  (function backtracking(index) {
    if(index == arrays.length) return result.push(current.slice());
    for(var i=0; i<arrays[index].length;   i) {
      current[index] = arrays[index][i];
      backtracking(index 1);
    }
  })(0);
  return result;
}
cartesianProduct([[1,2],[10,20],[100,200,300]]).forEach(function(item) {
  console.log('['   item.join(', ')   ']');
});
div.as-console-wrapper { max-height: 100%; }

Однострочный подход для лучшего чтения с отступами. Функции стрелок

function cartesianProduct(arr) {
  return arr.reduce((a, b) =>
    a.map(x => b.map(y => x.concat(y)))
    .reduce((a, b) => a.concat(b), []), [[]]);
}

var arr = [[1, 2], [10, 20], [100, 200, 300]];
console.log(JSON.stringify(cartesianProduct(arr)));

Требуется один массив с массивами разыскиваемых декартовых элементов.

_ = require("lodash")
cartesianProduct = ->
    return _.reduceRight(arguments, (a,b) ->
        _.flatten(_.map(a,(x) -> _.map b, (y) -> x.concat(y)), true)
    , [ [] ])

Некоторые из ответов по этой теме терпят неудачу, когда любой из входных массивов содержит элемент массива. Тебе лучше проверить это.

result = data.reduce(
    (a, b) => a.reduce(
        (r, v) => r.concat(b.map(w => [].concat(v, w))),
        []
    )
);

В любом случае не нужно подчеркивать, да ладно. Я считаю, что это следует делать с чистым JS ES6, настолько функциональным, насколько это возможно.

var data = [[1, 2], [10, 20], [100, 200, 300]],
    result = data.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; }

Этот фрагмент кода использует карту сокращения и вложенную карту, просто чтобы получить декартово произведение двух массивов, однако второй массив получается из рекурсивного вызова той же функции с одним меньшим массивом ;, следовательно, ..

В любом случае, подчеркивание не требуется, всякие штрихи. Я считаю, что это следует делать с чистым JS ES6, настолько функциональным, насколько это возможно.

Этот фрагмент кода использует карту сокращения и вложенную карту, просто чтобы получить декартово произведение двух массивов, однако второй массив получается из рекурсивного вызова той же функции с одним меньшим массивом ;, следовательно, .. { *} В моем конкретном случае «старомодный» подход казался более эффективным, чем методы, основанные на более современных функциях. Ниже приведен код (включая небольшое сравнение с другими решениями, опубликованными в этой теме @rsp и @sebnukem), если он окажется полезным для кого-то еще. a[0].cartesian(...a.slice(1))

Array.prototype.cartesian = function(...a){
  return a.length ? this.reduce((p,c) => (p.push(...a[0].cartesian(...a.slice(1)).map(e => a.length > 1 ? [c,...e] : [c,e])),p),[])
                  : this;
};

var arr = ['a', 'b', 'c'],
    brr = [1,2,3],
    crr = [[9],[8],[7]];
console.log(JSON.stringify(arr.cartesian(brr,crr))); 

массивы,

Идея следующая. Допустим, мы создаем внешний продукт N каждый из которых имеет a_1,...,a_N компонентов. Внешнее произведение этих массивов имеет элементы m_i, и мы можем отождествить каждый из них с M=m_1*m_2*...*m_N мерным вектором, компоненты которого являются натуральными числами, а N- -й компонент строго ограничен сверху i. Например, вектор m_i будет соответствовать конкретной комбинации, в которой каждый берет первый элемент из каждого массива, тогда как (0, 0, ..., 0) идентифицируется с комбинацией, в которой каждый берет последний элемент из каждого массива. Таким образом, чтобы построить все (m_1-1, m_2-1, ..., m_N-1) комбинации, функция ниже последовательно строит все такие векторы и для каждого из них идентифицирует соответствующую комбинацию элементов входных массивов. M, Я получаю следующее время:

function cartesianProduct(){
    const N = arguments.length;

    var arr_lengths = Array(N);
    var digits = Array(N);
    var num_tot = 1;
    for(var i = 0; i < N;   i){
        const len = arguments[i].length;
        if(!len){
            num_tot = 0;
            break;
        }
        digits[i] = 0;
        num_tot *= (arr_lengths[i] = len);
    }

    var ret = Array(num_tot);
    for(var num = 0; num < num_tot;   num){

        var item = Array(N);
        for(var j = 0; j < N;   j){ item[j] = arguments[j][digits[j]]; }
        ret[num] = item;

        for(var idx = 0; idx < N;   idx){
            if(digits[idx] == arr_lengths[idx]-1){
                digits[idx] = 0;
            }else{
                digits[idx]  = 1;
                break;
            }
        }
    }
    return ret;
}
//------------------------------------------------------------------------------
let _f = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b))));
let cartesianProduct_rsp = (a, b, ...c) => b ? cartesianProduct_rsp(_f(a, b), ...c) : a;
//------------------------------------------------------------------------------
function cartesianProduct_sebnukem(a) {
    var i, j, l, m, a1, o = [];
    if (!a || a.length == 0) return a;

    a1 = a.splice(0, 1)[0];
    a = cartesianProduct_sebnukem(a);
    for (i = 0, l = a1.length; i < l; i  ) {
        if (a && a.length) for (j = 0, m = a.length; j < m; j  )
            o.push([a1[i]].concat(a[j]));
        else
            o.push([a1[i]]);
    }
    return o;
}
//------------------------------------------------------------------------------
const L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const args = [L, L, L, L, L, L];

let fns = {
    'cartesianProduct': function(args){ return cartesianProduct(...args); },
    'cartesianProduct_rsp': function(args){ return cartesianProduct_rsp(...args); },
    'cartesianProduct_sebnukem': function(args){ return cartesianProduct_sebnukem(args); }
};

Object.keys(fns).forEach(fname => {
    console.time(fname);
    const ret = fns[fname](args);
    console.timeEnd(fname);
});

2 — это число цифр, до которого мы хотим округлить это число. node v6.12.2 Это помечено

cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms

функциональное программирование , поэтому давайте посмотрим на монаду List . Одно из приложений для этого монадического списка представляет недетерминированные вычисления. :

может содержать результаты для всех путей выполнения List Что ж, это звучит как Вы можете избежать

совершенная пригодность для . JavaScript дает нам cartesian, а функция монадического связывания — Array, поэтому давайте использовать их — Array.prototype.flatMap Вместо

const cartesian = (...all) =>
{ const loop = (t, a, ...more) =>
    a === undefined
      ? [ t ]
      : a .flatMap (x => loop ([ ...t, x ], ...more))
  return loop ([], ...all)
}

console .log (cartesian ([1,2], [10,20], [100,200,300]))

можно добавить в качестве параметра карри — loop выше, t Нерекурсивный подход, который добавляет возможность фильтровать и модифицировать продукты, прежде чем фактически добавлять их в набор результатов. Обратите внимание на использование .map, а не .forEach. В некоторых браузерах .map работает быстрее.

const makeCartesian = (t = []) => (a, ...more) =>
  a === undefined
    ? [ t ]
    : a .flatMap (x => makeCartesian ([ ...t, x ]) (...more))

const cartesian =
  makeCartesian ()

console .log (cartesian ([1,2], [10,20], [100,200,300]))

Для тех, кому нужен TypeScript (переопределён @ ответ Дэнни)

function crossproduct(arrays,rowtest,rowaction) {
      // Calculate the number of elements needed in the result
      var result_elems = 1, row_size = arrays.length;
      arrays.map(function(array) {
            result_elems *= array.length;
      });
      var temp = new Array(result_elems), result = [];

      // Go through each array and add the appropriate element to each element of the temp
      var scale_factor = result_elems;
      arrays.map(function(array)
      {
        var set_elems = array.length;
        scale_factor /= set_elems;
        for(var i=result_elems-1;i>=0;i--) {
            temp[i] = (temp[i] ? temp[i] : []);
            var pos = i / scale_factor % set_elems;
            // deal with floating point results for indexes, this took a little experimenting
            if(pos < 1 || pos % 1 <= .5) {
                pos = Math.floor(pos);
            } else {
                pos = Math.min(array.length-1,Math.ceil(pos));
            }
            temp[i].push(array[pos]);
            if(temp[i].length===row_size) {
                var pass = (rowtest ? rowtest(temp[i]) : true);
                if(pass) {
                    if(rowaction) {
                        result.push(rowaction(temp[i]));
                    } else {
                        result.push(temp[i]);
                    }
                }
            }
        }
      });
      return result;
    }

— пользователь агент получил все данные изображения, которые он может, но он даже не может декодировать изображение настолько, чтобы получить размеры изображения (например, изображение повреждено, или формат не поддерживается, или данные не могут быть получены). reduce:

const array1 = ["day", "month", "year", "time"];
const array2 = ["from", "to"];
const process = (one, two) => [one, two].join(" ");

const product = array1.reduce((result, one) => result.concat(array2.map(two => process(one, two))), []);

Я заметил, что никто не опубликовал решение, позволяющее передавать функцию для обработки каждой комбинации, поэтому вот мое решение:

/**
 * Calculates "Cartesian Product" sets.
 * @example
 *   cartesianProduct([[1,2], [4,8], [16,32]])
 *   Returns:
 *   [
 *     [1, 4, 16],
 *     [1, 4, 32],
 *     [1, 8, 16],
 *     [1, 8, 32],
 *     [2, 4, 16],
 *     [2, 4, 32],
 *     [2, 8, 16],
 *     [2, 8, 32]
 *   ]
 * @see https://stackoverflow.com/a/36234242/1955709
 * @see https://en.wikipedia.org/wiki/Cartesian_product
 * @param arr {T[][]}
 * @returns {T[][]}
 */
function cartesianProduct<T> (arr: T[][]): T[][] {
  return arr.reduce((a, b) => {
    return a.map(x => {
      return b.map(y => {
        return x.concat(y)
      })
    }).reduce((c, d) => c.concat(d), [])
  }, [[]] as T[][])
}

Вывод:

const _ = require('lodash')

function combinations(arr, f, xArr = []) {
    return arr.length>1 
    ? _.flatMap(arr[0], x => combinations(arr.slice(1), f, xArr.concat(x)))
    : arr[0].map(x => f(...xArr.concat(x)))
}

// use case
const greetings = ["Hello", "Goodbye"]
const places = ["World", "Planet"]
const punctuationMarks = ["!", "?"]
combinations([greetings,places,punctuationMarks], (greeting, place, punctuationMark) => `${greeting} ${place}${punctuationMark}`)
  .forEach(row => console.log(row))

Простой подход с использованием грубой силы JS, который принимает массив массивов в качестве входных данных.

Hello World!
Hello World?
Hello Planet!
Hello Planet?
Goodbye World!
Goodbye World?
Goodbye Planet!
Goodbye Planet?

Простое «разумное и визуально дружественное» решение.

var cartesian = function(arrays) {
    var product = [];
    var precals = [];
    var length = arrays.reduce(function(acc, curr) {
        return acc * curr.length
    }, 1);
    for (var i = 0; i < arrays.length; i  ) {
        var array = arrays[i];
        var mod = array.length;
        var div = i > 0 ? precals[i - 1].div * precals[i - 1].mod : 1;
        precals.push({
            div: div,
            mod: mod
        });
    }
    for (var j = 0; j < length; j  ) {
        var item = [];
        for (var i = 0; i < arrays.length; i  ) {
            var array = arrays[i];
            var precal = precals[i];
            var k = (~~(j / precal.div)) % precal.mod;
            item.push(array[k]);
        }
        product.push(item);
    }
    return product;
};

cartesian([
    [1],
    [2, 3]
]);

cartesian([
    [1],
    [2, 3],
    [4, 5, 6]
]);

Только что преобразовал

enter image description here


// t = [i, length]

const moveThreadForwardAt = (t, tCursor) => {
  if (tCursor < 0)
    return true; // reached end of first array

  const newIndex = (t[tCursor][0]   1) % t[tCursor][1];
  t[tCursor][0] = newIndex;

  if (newIndex == 0)
    return moveThreadForwardAt(t, tCursor - 1);

  return false;
}

const cartesianMult = (...args) => {
  let result = [];
  const t = Array.from(Array(args.length)).map((x, i) => [0, args[i].length]);
  let reachedEndOfFirstArray = false;

  while (false == reachedEndOfFirstArray) {
    result.push(t.map((v, i) => args[i][v[0]]));

    reachedEndOfFirstArray = moveThreadForwardAt(t, args.length - 1);
  }

  return result;
}

// cartesianMult(
//   ['a1', 'b1', 'c1'],
//   ['a2', 'b2'],
//   ['a3', 'b3', 'c3'],
//   ['a4', 'b4']
// );

console.log(cartesianMult(
  ['a1'],
  ['a2', 'b2'],
  ['a3', 'b3']
));
var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [
    []
  ]);
};

console.log(cartesianProduct(chars, nums))
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

@ dummersl Простую, измененную версию кода @ viebel в простом Javascript: ответ от CoffeScript на JavaScript. Это просто работает.

var chars = ['A', 'B', 'C']
var nums = [1, 2, 3]

var cartesianProduct = function() {
  return _.reduce(arguments, function(a, b) {
    return _.flatten(_.map(a, function(x) {
      return _.map(b, function(y) {
        return x.concat(y);
      });
    }), true);
  }, [[]]);
};

console.log( cartesianProduct(chars, nums) )

вызывает wait.php.

function cartesianProduct() {
    var arr = [].slice.call(arguments),
        intLength = arr.length,
        arrHelper = [1],
        arrToReturn = [];

    for (var i = arr.length - 1; i >= 0; i--) {
        arrHelper.unshift(arrHelper[0] * arr[i].length);
    }

    for (var i = 0, l = arrHelper[0]; i < l; i  ) {
        arrToReturn.push([]);
        for (var j = 0; j < intLength; j  ) {
            arrToReturn[i].push(arr[j][(i / arrHelper[j   1] | 0) % arr[j].length]);
        }
    }

    return arrToReturn;
}

Библиотеки не нужны! :)

function cartesianProduct(...arrays) {
  return arrays.reduce((a, b) => {
    return [].concat(...a.map(x => {
      const next = Array.isArray(x) ? x : [x];
      return [].concat(b.map(y => next.concat(...[y])));
    }));
  });
}

const product = cartesianProduct([1, 2], [10, 20], [100, 200, 300]);

console.log(product);
/*
[ [ 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 ] ];
*/

Нужны функции стрелок, хотя и, вероятно, не так эффективно : /

Для справки

const flatten = (xs) => 
    xs.flat(Infinity)

const binaryCartesianProduct = (xs, ys) =>
    xs.map((xi) => ys.map((yi) => [xi, yi])).flat()

const cartesianProduct = (...xss) =>
    xss.reduce(binaryCartesianProduct, [[]]).map(flatten)
      
console.log(cartesianProduct([1,2,3], [1,2,3], [1,2,3]))

Вот моя версия. Я сделал это с помощью простейшего итератора javascript «for ()», так что он совместим в любом случае и имеет лучшую производительность.

С наилучшими пожеланиями.

function cartesian(arrays){
    var quant = 1, counters = [], retArr = [];

    // Counts total possibilities and build the counters Array;
    for(var i=0;i<arrays.length;i  ){
        counters[i] = 0;
        quant *= arrays[i].length;
    }

    // iterate all possibilities
    for(var i=0,nRow;i<quant;i  ){
        nRow = [];
        for(var j=0;j<counters.length;j  ){
            if(counters[j] < arrays[j].length){
                nRow.push(arrays[j][counters[j]]);
            } else { // in case there is no such an element it restarts the current counter
                counters[j] = 0;
                nRow.push(arrays[j][counters[j]]);
            }
            counters[j]  ;
        }
        retArr.push(nRow);
    }
    return retArr;
}

javascript — Как добавить событие onload в элемент div? — Переполнение стека