Producto cartesiano de múltiples matrices en JavaScript

111

¿Cómo implementaría el producto cartesiano de múltiples matrices en JavaScript?

Como ejemplo,

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

debería volver

[
  [1, 10, 100],
  [1, 10, 200],
  [1, 10, 300],
  [2, 10, 100],
  [2, 10, 200]
  ...
]
viebel
fuente
3
Esto se implementó en el módulo js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi
Estoy de acuerdo con underscore.js pero no estoy seguro de ver cómo la eliminación de la etiqueta de programación funcional ayudará a @le_m
viebel
Fwiw, d3 añadido d3.cross(a, b[, reducer])en febrero. github.com/d3/d3-array#cross
Toph

Respuestas:

105

Actualización de 2017: respuesta de 2 líneas con vanilla JS

Todas las respuestas aquí son demasiado complicadas , la mayoría de ellas toman 20 líneas de código o incluso más.

Este ejemplo usa solo dos líneas de JavaScript vanilla , sin lodash, subrayado u otras bibliotecas:

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;

Actualizar:

Es el mismo que el anterior, pero mejorado para seguir estrictamente la Guía de estilo de JavaScript de Airbnb , validada con ESLint con eslint-config-airbnb-base :

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);

Un agradecimiento especial a ZuBB por informarme sobre problemas de linter con el código original.

Ejemplo

Este es el ejemplo exacto de su pregunta:

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

Salida

Esta es la salida de ese comando:

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

Manifestación

Ver demostraciones en:

Sintaxis

La sintaxis que utilicé aquí no es nada nuevo. Mi ejemplo usa el operador de propagación y los demás parámetros, características de JavaScript definidas en la sexta edición del estándar ECMA-262 publicado en junio de 2015 y desarrollado mucho antes, mejor conocido como ES6 o ES2015. Ver:

Hace que un código como este sea tan simple que es un pecado no usarlo. Para las plataformas antiguas que no lo admiten de forma nativa, siempre puede usar Babel u otras herramientas para transpilarlo a una sintaxis anterior, y de hecho, mi ejemplo transpilado por Babel es aún más corto y simple que la mayoría de los ejemplos aquí, pero no es así. realmente importa porque la salida de la transpilación no es algo que deba comprender o mantener, es solo un hecho que me pareció interesante.

Conclusión

No es necesario escribir cientos de líneas de código que sean difíciles de mantener y no es necesario utilizar bibliotecas completas para algo tan simple, cuando dos líneas de JavaScript vanilla pueden hacer el trabajo fácilmente. Como puede ver, realmente vale la pena usar funciones modernas del lenguaje y, en los casos en que necesite admitir plataformas arcaicas sin soporte nativo de las funciones modernas, siempre puede usar Babel u otras herramientas para transpilar la nueva sintaxis a la anterior. .

No codifique como si fuera 1995

JavaScript evoluciona y lo hace por una razón. TC39 hace un trabajo increíble en el diseño del lenguaje al agregar nuevas funciones y los proveedores de navegadores hacen un trabajo increíble al implementar esas funciones.

Para ver el estado actual del soporte nativo de cualquier característica dada en los navegadores, consulte:

Para ver el soporte en las versiones de Node, consulte:

Para usar la sintaxis moderna en plataformas que no la admiten de forma nativa, use Babel:

rsp
fuente
Aquí hay una versión mecanografiada con un ligero cambio para tener en cuenta la forma en que el mecanografiado hace la distribución de matrices. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe
1
@rsp muchas gracias por la buena respuesta. aunque me gustaría pedirle que lo mejore un poco para obtener una plataforma de advertencia de variables sombreadas (2 vars locales ay 2 vars locales b)
ZuBB
7
"No codifique como si fuera 1995": no hay necesidad de ser desagradable, no todos se han puesto al día todavía.
Godwhacker
7
Esto está bien, sin embargo, falla cuando se alimenta con ['a', 'b'], [1,2], [[9], [10]]lo que cederá [ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]como resultado. Quiero decir, no conservaré el tipo de artículos de [[9], [10]].
Reducción el
1
Como ya lo estamos usando ..., ¿no debería [].concat(...[array])ser simple [...array]?
Lazar Ljubenović
88

Aquí hay una solución funcional al problema (¡sin ninguna variable mutable !) Usando reducey flatten, proporcionada por 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>

Observación: esta solución se inspiró en http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/

viebel
fuente
Hay un error tipográfico en esta respuesta, no debería haber un ", verdadero" (¿tal vez lodash ha cambiado desde que hiciste esta publicación?)
Chris Jefferson
@ChrisJefferson, el segundo parámetro flattenes hacer que el aplanamiento sea poco profundo. ¡Es obligatorio aquí!
viebel
4
Lo siento, esta es una incompatibilidad lodash / subrayado, se intercambiaron alrededor de la bandera.
Chris Jefferson
1
Por lo tanto, al aplanar, úselotrue con subrayado y úselofalse con lodash para asegurar un aplanamiento poco profundo.
Akseli Palén
¿Cómo modificar esta función para que acepte una matriz de matrices?
44

Aquí hay una versión modificada del código de @ viebel en JavaScript simple, sin usar ninguna biblioteca:

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(JSON.stringify(a));

Danny
fuente
2
Falla para producto cartesiano ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]) ya que aplana ['gamma'] a 'gamma' y [['alpha']] a ['alpha']
Mzn
porque en .concat(y)lugar de.concat([ y ])
Gracias
@Gracias, puede editar la respuesta directamente en lugar de comentar, simplemente lo hice, así que no es necesario ahora: P
Olivier Lalonde
28

parece que la comunidad piensa que esto es trivial o fácil de encontrar una implementación de referencia, después de una breve inspección no pude o tal vez es solo que me gusta reinventar la rueda o resolver problemas de programación similares a los de un salón de clases de cualquier manera es tu día de suerte :

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

implementación de referencia completa que es relativamente eficiente ... :-D

en eficiencia: podría ganar algo sacando el if del bucle y teniendo 2 bucles separados, ya que es técnicamente constante y estaría ayudando con la predicción de ramas y todo ese lío, pero ese punto es un poco discutible en javascript

quien sea, disfruta -ck

ckozl
fuente
1
Gracias @ckoz por su respuesta detallada. ¿Por qué no usarías la reducefunción de matriz?
viebel
1
@viebel ¿por qué quieres usar reduce? por un lado, reduce tiene muy poca compatibilidad con los navegadores más antiguos (consulte: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ), y en cualquier caso, el código loco de esa otra respuesta realmente le parece legible ? a mí no. seguro que es más corto, pero una vez minimizado este código tendría aproximadamente la misma longitud, más fácil de depurar / optimizar, en segundo lugar, todas esas soluciones de "reducción" se descomponen en lo mismo, excepto que tienen una búsqueda de cierre (teóricamente más lenta), también es más difícil para diseñar para que maneje conjuntos infinitos ...
ckozl
5
Creé una versión 2+ veces más rápida y (en mi opinión) más limpia: pastebin.com/YbhqZuf7 Alcanza el aumento de velocidad al no usar result = result.concat(...)y al no usar args.slice(1). Desafortunadamente, no pude encontrar una manera de deshacerme de curr.slice()la recursividad.
Pauan
2
Buen trabajo de @Pauan, buena reducción de puntos calientes en general para un aumento de rendimiento del 10% -50% en función de lo que estoy viendo. Sin embargo, no puedo hablar sobre la "limpieza", creo que su versión es más difícil de seguir debido al uso de variables de alcance de cierre. Pero, en términos generales, es más difícil seguir un código más eficaz. Escribí la versión original para facilitar la lectura, desearía tener más tiempo para poder participar en un lanzamiento de rendimiento;) tal vez más tarde ...
ckozl
este es realmente uno de esos problemas
James
26

La siguiente función generadora eficiente devuelve el producto cartesiano de todos los iterables dados :

// 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]));

Acepta matrices, cadenas, conjuntos y todos los demás objetos que implementan el protocolo iterable. .

Siguiendo la especificación del producto cartesiano n-ario se obtiene

  • []si uno o más iterables dados están vacíos, por ejemplo, []o''
  • [[a]]si se da un único iterable que contiene un solo valor a.

Todos los demás casos se manejan como se esperaba, como lo demuestran los siguientes casos de prueba:

le_m
fuente
¿Te importaría explicar lo que está sucediendo en este? ¡Muchas gracias!
LeandroP
¡Gracias por enseñarnos un ejemplo maravilloso del uso de la función de generador + recursividad de cola + bucles de doble capa! Pero la posición del primer bucle for en el código debe cambiarse para que el orden de las submatrices de salida sea correcto. Código fijo:function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
ooo
@ooo Si desea reproducir el orden de las tuplas de producto cartesiano dado por el comentario de OP, entonces su modificación es correcta. Sin embargo, el orden de las tuplas dentro del producto generalmente no es relevante, por ejemplo, matemáticamente el resultado es un conjunto desordenado. Elegí este orden porque requiere llamadas mucho menos recursivas y, por lo tanto, es un poco más eficaz; sin embargo, no ejecuté un punto de referencia.
le_m
Errata: En mi comentario anterior, "recursividad de cola" debería ser "recursividad" (no una llamada de cola en este caso).
ooo
20

Aquí hay una solución recursiva sencilla y sencilla:

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

sebnukem
fuente
2
Este resulta ser el código JS puro más eficiente en este tema. Se necesitan aproximadamente ~ 600 ms para terminar en matrices de 3 x 100 elementos para producir una matriz de 1M de longitud.
Reducción del
1
Funciona para productos cartesianos ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]); sin aplanar los valores originales
Mzn
10

Aquí hay una forma recursiva que usa una función generadora de ECMAScript 2015 para que no tenga que crear todas las tuplas a la vez:

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]))));

heenenee
fuente
Esto no funcionará cuando una de las matrices tenga elementos de matriz comocartesian([[1],[2]],[10,20],[100,200,300])
Reducción del
@Redu Answer se ha actualizado para admitir argumentos de matriz.
heenenee
Sí, el .concat()operador de propagación integrado a veces puede resultar engañoso.
Reducción del
10

Aquí hay un resumen usando el ES2019 nativo flatMap. No se necesitan bibliotecas, solo un navegador moderno (o transpilador):

data.reduce((a, b) => a.flatMap(x => b.map(y => [...x, y])), [[]]);

Es esencialmente una versión moderna de la respuesta de viebel, sin lodash.

Fred Kleuver
fuente
9

Usando un retroceso típico con generadores 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%; }

A continuación hay una versión similar compatible con navegadores más antiguos.

Oriol
fuente
9

Esta es una solución ES6 pura que utiliza funciones de flecha

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)));

Christopher Moore
fuente
7

Una versión coffeescript con lodash:

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

Un enfoque de una sola línea, para una mejor lectura con sangrías.

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

Se necesita una sola matriz con matrices de elementos cartesianos deseados.

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; }

Nina Scholz
fuente
Tuve que agregar una declaración de guardia para manejar correctamente el caso donde la matriz tiene un solo elemento:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn
5

Esto está etiquetado como programación funcional, así que echemos un vistazo a la mónada List :

Una aplicación para esta lista monádica es la representación de cálculos no deterministas. List puede contener resultados para todas las rutas de ejecución en un algoritmo ...

Bueno, eso suena perfecto para cartesian. JavaScript nos da Arrayy la función de enlace monádico es Array.prototype.flatMap, así que pongámoslos en uso:

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

En lugar de lo loopanterior, tse puede agregar como un parámetro de curry:

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

Gracias
fuente
3

Algunas de las respuestas de este tema fallan cuando alguna de las matrices de entrada contiene un elemento de matriz. Será mejor que compruebes eso.

De todos modos no hay necesidad de subrayado, lodash en absoluto. Creo que este debería hacerlo con JS ES6 puro, tan funcional como sea posible.

Este fragmento de código usa un mapa anidado y uno reducido, simplemente para obtener el producto cartesiano de dos arreglos, sin embargo, el segundo arreglo proviene de una llamada recursiva a la misma función con un arreglo menos; por lo tanto.. 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))); 

Redu
fuente
3

En mi entorno particular, el enfoque "pasado de moda" parecía ser más eficiente que los métodos basados ​​en características más modernas. A continuación se muestra el código (incluida una pequeña comparación con otras soluciones publicadas en este hilo por @rsp y @sebnukem) en caso de que también resulte útil para otra persona.

La idea sigue. Digamos que estamos construyendo el producto externo de Nmatrices, a_1,...,a_Ncada una de las cuales tiene m_icomponentes. El producto externo de estas matrices tiene M=m_1*m_2*...*m_Nelementos y podemos identificar cada uno de ellos con un N-vector dimensional cuyos componentes son enteros positivos y el icomponente -ésimo está estrictamente acotado desde arriba por m_i. Por ejemplo, el vector (0, 0, ..., 0)correspondería a la combinación particular dentro de la cual se toma el primer elemento de cada matriz, mientras que (m_1-1, m_2-1, ..., m_N-1)se identifica con la combinación donde se toma el último elemento de cada matriz. Así, para construir todosM combinaciones, la función a continuación construye consecutivamente todos esos vectores y para cada uno de ellos identifica la combinación correspondiente de los elementos de las matrices de entrada.

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);
});

con node v6.12.2, obtengo los siguientes tiempos:

cartesianProduct: 427.378ms
cartesianProduct_rsp: 1710.829ms
cartesianProduct_sebnukem: 593.351ms
ewcz
fuente
3

Para aquellos que necesitan TypeScript (reimplementado @ la respuesta de Danny)

/**
 * 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[][])
}
artnikpro
fuente
2

Solo para una opción, una implementación realmente simple usando matrices 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))), []);
Simple.Js
fuente
2

JavaScript moderno en solo unas pocas líneas. Sin bibliotecas externas o dependencias como Lodash.

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

console.log(
  cartesian([1, 2], [10, 20], [100, 200, 300])
    .map(arr => JSON.stringify(arr))
    .join('\n')
);

Miles Elam
fuente
2

Podría reducela matriz 2D. Úselo flatMapen la matriz de acumuladores para obtener el acc.length x curr.lengthnúmero de combinaciones en cada bucle. [].concat(c, n)se usa porque ces un número en la primera iteración y una matriz después.

const data = [ [1, 2], [10, 20], [100, 200, 300] ];

const output = data.reduce((acc, curr) =>
  acc.flatMap(c => curr.map(n => [].concat(c, n)))
)

console.log(JSON.stringify(output))

(Esto se basa en la respuesta de Nina Scholz )

adiga
fuente
1

Un enfoque no recursivo que agrega la capacidad de filtrar y modificar los productos antes de agregarlos al conjunto de resultados. Tenga en cuenta el uso de .map en lugar de .forEach. En algunos navegadores, .map se ejecuta más rápido.

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;
    }
De cualquier manera
fuente
1

Una solución simple "mental y visualmente amigable".

ingrese la descripción de la imagen aquí


// 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']
));
cero cero siete
fuente
1

Una versión simple y modificada del código de @ viebel en JavaScript simple:

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 ] ];
*/
Juan Gaitán
fuente
1

Una implementación más legible

function productOfTwo(one, two) {
  return one.flatMap(x => two.map(y => [].concat(x, y)));
}

function product(head = [], ...tail) {
  if (tail.length === 0) return head;
  return productOfTwo(head, product(...tail));
}

const test = product(
  [1, 2, 3],
  ['a', 'b']
);

console.log(JSON.stringify(test));

Andrei
fuente
1
f=(a,b,c)=>a.flatMap(ai=>b.flatMap(bi=>c.map(ci=>[ai,bi,ci])))

Esto es para 3 matrices.
Algunas respuestas dieron paso a cualquier número de matrices.
Esto puede contraerse o expandirse fácilmente a menos o más matrices.
Necesitaba combinaciones de un conjunto con repeticiones, por lo que podría haber usado:

f(a,a,a)

pero usado:

f=(a,b,c)=>a.flatMap(a1=>a.flatMap(a2=>a.map(a3=>[a1,a2,a3])))
iAmOren
fuente
0

Noté que nadie publicó una solución que permite pasar una función para procesar cada combinación, así que aquí está mi solución:

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))

Salida:

Hello World!
Hello World?
Hello Planet!
Hello Planet?
Goodbye World!
Goodbye World?
Goodbye Planet!
Goodbye Planet?
Lezorte
fuente
0

Enfoque de fuerza bruta de JS simple que toma una matriz de matrices como entrada.

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]
]);
Samuel Ventura
fuente
0

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>

Acabo de convertir la respuesta de @ dummersl de CoffeScript a JavaScript. Simplemente funciona.

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) )
guneysus
fuente
0

Otra implementación más. No es el más corto ni elegante, sino rápido:

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;
}
flare256
fuente
0

¡No se necesitan bibliotecas! :)

Sin embargo, necesita funciones de flecha y probablemente no sea tan eficiente. : /

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

Chris Vouga
fuente
0

Para el registro

Aquí va mi versión. Lo hice usando el iterador javascript más simple "for ()", por lo que es compatible en todos los casos y tiene el mejor rendimiento.

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;
}

Atentamente.

LeandroP
fuente