Produto cartesiano de vários arrays em JavaScript


111

Como você implementaria o produto cartesiano de vários arrays em JavaScript?

Como um exemplo,

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

deveria retornar

[
  [1, 10, 100],
  [1, 10, 200],
  [1, 10, 300],
  [2, 10, 100],
  [2, 10, 200]
  ...
]


3
Isso implementado no módulo js-combinatorics: github.com/dankogai/js-combinatorics
Segal-Halevi


Eu concordo sobre o underscore.js, mas não tenho certeza se vejo como a remoção da tag de programação funcional ajudará @le_m
viebel

Fwiw, d3 adicionado d3.cross(a, b[, reducer])em fevereiro. github.com/d3/d3-array#cross
Toph

Respostas:


105

Atualização de 2017: resposta de 2 linhas com vanilla JS

Todas as respostas aqui são excessivamente complicadas , a maioria delas ocupa 20 linhas de código ou até mais.

Este exemplo usa apenas duas linhas de JavaScript vanilla , sem lodash, sublinhado ou outras 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;

Atualizar:

Este é o mesmo que acima, mas melhorado para seguir estritamente o Guia de Estilo do Airbnb JavaScript - validado usando ESLint com 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);

Agradecimentos especiais a ZuBB por me informar sobre os problemas do linter com o código original.

Exemplo

Este é o exemplo exato de sua pergunta:

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

Resultado

Esta é a saída desse 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 ] ]

Demo

Veja as demos em:

Sintaxe

A sintaxe que usei aqui não é nova. Meu exemplo usa o operador spread e os parâmetros restantes - recursos do JavaScript definidos na 6ª edição da norma ECMA-262 publicada em junho de 2015 e desenvolvida muito antes, mais conhecida como ES6 ou ES2015. Vejo:

Isso torna um código assim tão simples que é um pecado não usá-lo. Para plataformas antigas que não o suportam nativamente, você pode sempre usar o Babel ou outras ferramentas para transpilar para a sintaxe mais antiga - e de fato meu exemplo transpilado por Babel ainda é mais curto e simples do que a maioria dos exemplos aqui, mas não realmente importa porque o resultado da transpilação não é algo que você precise entender ou manter, é apenas um fato que achei interessante.

Conclusão

Não há necessidade de escrever centenas de linhas de código que são difíceis de manter e não há necessidade de usar bibliotecas inteiras para uma coisa tão simples, quando duas linhas de JavaScript vanilla podem facilmente realizar o trabalho. Como você pode ver, realmente vale a pena usar recursos modernos da linguagem e nos casos em que você precisa suportar plataformas arcaicas sem suporte nativo dos recursos modernos você pode sempre usar o Babel ou outras ferramentas para transpilar a nova sintaxe para a antiga .

Não codifique como se fosse 1995

JavaScript evolui e isso acontece por uma razão. TC39 faz um trabalho incrível no design da linguagem com a adição de novos recursos e os fornecedores de navegadores fazem um trabalho incrível de implementação desses recursos.

Para ver o estado atual do suporte nativo de qualquer recurso específico nos navegadores, consulte:

Para ver o suporte nas versões do Node, consulte:

Para usar a sintaxe moderna em plataformas que não oferecem suporte nativo, use o Babel:


Aqui está uma versão do texto datilografado com uma ligeira mudança para explicar a maneira como o texto datilografado faz a propagação do array. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp muito obrigado pela resposta realmente boa. embora eu gostaria de pedir a você para melhorá-lo um pouco para obter equipamento de aviso de variáveis ​​ocultas (2 vars locais ae 2 vars locais b)
ZuBB

7
"Não codifique como se fosse 1995" - não precisa ser desagradável, nem todo mundo percebeu ainda.
Godwhacker 01 de

7
Isso é bom, porém falha quando alimentado com o ['a', 'b'], [1,2], [[9], [10]]que irá render [ [ '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. Quer dizer, não vou manter o tipo de itens de [[9], [10]].
Redu de

1
Já que ...já estamos usando , não deveria se [].concat(...[array])tornar simples [...array]?
Lazar Ljubenović de

88

Aqui está uma solução funcional para o problema (sem nenhuma variável mutável !) Usando reducee flatten, fornecido 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>

Observação: Esta solução foi inspirada em http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/


Há um erro de digitação nesta resposta, não deveria haver um ", verdadeiro" (talvez Lodash tenha mudado desde que você fez esta postagem?)
Chris Jefferson

@ChrisJefferson o segundo parâmetro flattené tornar o nivelamento raso. É obrigatório aqui!
viebel

4
Desculpe, esta é uma incompatibilidade de lodash / sublinhado, eles trocaram a bandeira.
Chris Jefferson

1
Portanto, ao achatar, use truecom sublinhado e use falsecom lodash para garantir um achatamento raso.
Akseli Palén de

Como modificar esta função para que ela aceite array de arrays?

44

Esta é uma versão modificada do código de @viebel em Javascript simples, sem usar nenhuma 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));


2
Falha para produto cartesiano ([[[1], [2], [3]], ['a', 'b'], [['gama'], [['alfa']]], ['zii', 'faa']]), pois nivela ['gama'] para 'gama' e [['alfa']] para ['alfa']
Mzn

porque em .concat(y)vez de.concat([ y ])
Obrigado

@Obrigado, você pode editar a resposta diretamente em vez de comentar, simplesmente fiz isso sem necessidade agora: P
Olivier Lalonde

28

parece que a comunidade pensa que isso é trivial e / ou fácil de encontrar uma implementação de referência, após uma breve inspeção eu não pude ou talvez seja apenas que eu gosto de reinventar a roda ou resolver problemas de programação semelhantes à sala de aula de qualquer forma, é seu dia de sorte :

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

implementação de referência completa que é relativamente eficiente ... :-D

na eficiência: você poderia ganhar algum tirando o if do loop e tendo 2 loops separados, uma vez que é tecnicamente constante e você estaria ajudando com a previsão de branch e toda essa bagunça, mas esse ponto é meio discutível em javascript

qualquer um, aproveite -ck


1
Obrigado @ckoz pela sua resposta detalhada. Por que você não usaria a reducefunção de array?
viebel

1
@viebel, por que você deseja usar a redução? por um lado, a redução tem um suporte muito pobre para navegadores mais antigos (consulte: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ) e, em qualquer caso, aquele código maluco daquela outra resposta realmente parece legível para você ? não para mim. claro que é mais curto, mas uma vez minimizado, esse código teria aproximadamente o mesmo tamanho, mais fácil de depurar / otimizar, em segundo lugar, todas as soluções de "redução" se dividem na mesma coisa, exceto que têm uma pesquisa de encerramento (teoricamente mais lento), também é mais difícil projetar para lidar com conjuntos infinitos ...
ckozl

5
Eu criei uma versão 2+ vezes mais rápida e (imo) mais limpa: pastebin.com/YbhqZuf7 Ele atinge o aumento de velocidade por não usar result = result.concat(...)e por não usar args.slice(1). Infelizmente, não consegui encontrar uma maneira de me livrar da curr.slice()recursão.
Pauan

2
@Pauan bom trabalho, boa redução de pontos críticos em geral para um aumento de desempenho de 10% a 50% baseado no que estou vendo. Eu não posso falar sobre a "limpeza", porém, eu sinto que sua versão é realmente mais difícil de seguir devido ao uso de variáveis ​​de escopo de fechamento. Mas, de modo geral, um código com melhor desempenho é mais difícil de seguir. Eu escrevi a versão original para facilitar a leitura, gostaria de ter mais tempo para poder envolvê-los em uma apresentação;) talvez mais tarde ...
ckozl

este é realmente um dos problemas
James

26

A seguinte função geradora eficiente retorna o produto cartesiano de todos os iteráveis fornecidos :

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

Ele aceita arrays, strings, conjuntos e todos os outros objetos que implementam o protocolo iterável .

Seguindo a especificação do produto cartesiano n-ário , produz

  • []se um ou mais iteráveis ​​fornecidos estiverem vazios, por exemplo, []ou''
  • [[a]]se um único iterável contendo um único valor afor fornecido.

Todos os outros casos são tratados conforme o esperado, conforme demonstrado pelos seguintes casos de teste:


Importa-se de explicar o que está acontecendo neste? Muito obrigado!
LeandroP de

Obrigado por nos ensinar um exemplo maravilhoso de como usar função de gerador + recursão de cauda + loops de camada dupla! Mas a posição do primeiro for-loop no código precisa ser alterada para tornar a ordem dos subarrays de saída correta. Código fixo: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 Se você deseja reproduzir a ordem das tuplas do produto cartesiano fornecidas pelo comentário de OP, sua modificação está correta. No entanto, a ordem das tuplas dentro do produto geralmente não é relevante, por exemplo, matematicamente o resultado é um conjunto não ordenado. Escolhi esta ordem porque requer muito menos chamadas recursivas e, portanto, tem um pouco mais de desempenho - embora não execute um benchmark.
le_m

Errata: Em meu comentário acima, "recursão final" deve ser "recursão" (não uma chamada final neste caso).
ooo

20

Esta é uma solução recursiva simples e não sofisticada:

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


2
Este acaba sendo o código JS puro mais eficiente neste tópico. Demora cerca de 600 msegs para terminar em matrizes de 3 x 100 itens para produzir uma matriz de comprimento 1M.
Redu

1
Funciona para cartesianProduct ([[[1], [2], [3]], ['a', 'b'], [['gama'], [['alfa']]], ['zii', 'faa']]); sem
nivelar os

10

Esta é uma maneira recursiva que usa uma função geradora ECMAScript 2015 para que você não precise criar todas as tuplas de uma 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]))));


Isso não funcionará quando uma das matrizes tiver itens de matriz comocartesian([[1],[2]],[10,20],[100,200,300])
Redu

A resposta @Redu foi atualizada para suportar argumentos de matriz.
heenenee 02 de

Sim, o .concat()operador de propagação integrado às vezes pode se tornar enganoso.
Redu em

10

Aqui está um one-liner usando o ES2019 nativo flatMap. Nenhuma biblioteca necessária, apenas um navegador moderno (ou transpiler):

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

É essencialmente uma versão moderna da resposta de Viebel, sem lodash.


9

Usando um retrocesso típico com geradores 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%; }

Abaixo, há uma versão semelhante compatível com navegadores mais antigos.


9

Esta é uma solução ES6 pura usando funções de seta

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


7

Uma versão coffeescript com lodash:

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

7

Abordagem unifilar, para melhor leitura com recuos.

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

Leva uma única matriz com matrizes de itens cartesianos desejados.

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


Tive que adicionar uma instrução guard para lidar corretamente com o caso em que a matriz tem um único elemento:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

Esta é uma programação funcional marcada , então vamos dar uma olhada na lista monad :

Uma aplicação para essa lista monádica é a representação de computação não determinística. List pode conter resultados para todos os caminhos de execução em um algoritmo ...

Bem, isso soa como um ajuste perfeito para cartesian. JavaScript nos dá Arraye a função de ligação monádica é Array.prototype.flatMap, então vamos colocá-los em 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]))

Em vez de loopacima, tpode ser adicionado como um parâmetro curried -

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


3

Algumas das respostas neste tópico falham quando qualquer uma das matrizes de entrada contém um item da matriz. É melhor você verificar isso.

De qualquer forma, não há necessidade de sublinhado, lodash de qualquer natureza. Eu acredito que este deveria fazer isso com JS ES6 puro, o mais funcional possível.

Este trecho de código usa uma redução e um mapa aninhado, simplesmente para obter o produto cartesiano de duas matrizes; no entanto, a segunda matriz vem de uma chamada recursiva para a mesma função com uma matriz a menos; conseqüentemente.. 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))); 


3

No meu ambiente particular, a abordagem "antiquada" parecia ser mais eficiente do que os métodos baseados em recursos mais modernos. Abaixo está o código (incluindo uma pequena comparação com outras soluções postadas neste tópico por @rsp e @sebnukem) caso seja útil para outra pessoa também.

A ideia é seguir. Digamos que estejamos construindo o produto externo de Narrays, a_1,...,a_Ncada um com m_icomponentes. O produto externo desses arrays tem M=m_1*m_2*...*m_Nelementos e podemos identificar cada um deles com um N-vetor dimensional cujos componentes são inteiros positivos e o i-ésimo componente é estritamente limitado de cima por m_i. Por exemplo, o vetor (0, 0, ..., 0)corresponderia à combinação particular dentro da qual se pega o primeiro elemento de cada matriz, enquanto (m_1-1, m_2-1, ..., m_N-1)é identificado com a combinação onde se pega o último elemento de cada matriz. Assim, a fim de construir todosM combinações, a função abaixo constrói consecutivamente todos esses vetores e para cada um deles identifica a combinação correspondente dos elementos das matrizes 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);
});

com node v6.12.2, eu obtenho os seguintes tempos:

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

3

Para quem precisa de TypeScript (reimplementado na resposta 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[][])
}

2

Apenas para uma escolha, uma implementação realmente simples usando array 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))), []);

2

JavaScript moderno em apenas algumas linhas. Sem bibliotecas externas ou dependências 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')
);


2

Você poderia reduceo array 2D. Use flatMapna matriz do acumulador para obter o acc.length x curr.lengthnúmero de combinações em cada loop. [].concat(c, n)é usado porque cé um número na primeira iteração e uma matriz posteriormente.

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

(Isso é baseado na resposta de Nina Scholz )


1

Uma abordagem não recursiva que adiciona a capacidade de filtrar e modificar os produtos antes de realmente adicioná-los ao conjunto de resultados. Observe o uso de .map em vez de .forEach. Em alguns navegadores, .map é executado mais 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;
    }

1

Uma solução simples "mental e visualmente amigável".

insira a descrição da imagem aqui


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

1

Uma versão simples e modificada do código de @viebel em Javascript simples:

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

1

Uma implementação mais legível

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


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

Isso é para 3 matrizes.
Algumas respostas deram um caminho para qualquer número de matrizes.
Isso pode facilmente contrair ou expandir para menos ou mais matrizes.
Eu precisava de combinações de um conjunto com repetições, então poderia ter usado:

f(a,a,a)

mas usado:

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

0

Percebi que ninguém postou uma solução que permita que uma função seja passada para processar cada combinação, então aqui está a minha solução:

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

Resultado:

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

0

Abordagem simples de força bruta JS que leva uma série de matrizes 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]
]);

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>

Acabei de converter a resposta de @ dummersl de CoffeScript para JavaScript. Simplesmente 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) )

0

Mais uma implementação. Não é o mais curto ou sofisticado, mas 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;
}

0

Nenhuma biblioteca necessária! :)

Precisa de funções de seta e provavelmente não é tão 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]))


0

Para o registro

Aqui vai minha versão disso. Fiz isso usando o iterador javascript mais simples "for ()", por isso é compatível em todos os casos e tem o melhor desempenho.

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

Cumprimentos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.