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]
...
]
d3.cross(a, b[, reducer])
em fevereiro. github.com/d3/d3-array#crossRespostas:
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:
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 :
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:
Resultado
Esta é a saída desse comando:
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:
fonte
a
e 2 vars locaisb
)['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]]
....
já estamos usando , não deveria se[].concat(...[array])
tornar simples[...array]
?Aqui está uma solução funcional para o problema (sem nenhuma variável mutável !) Usando
reduce
eflatten
, fornecido porunderscore.js
:Observação: Esta solução foi inspirada em http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
fonte
flatten
é tornar o nivelamento raso. É obrigatório aqui!true
com sublinhado e usefalse
com lodash para garantir um achatamento raso.Esta é uma versão modificada do código de @viebel em Javascript simples, sem usar nenhuma biblioteca:
fonte
.concat(y)
vez de.concat([ y ])
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 :
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
fonte
reduce
função de array?result = result.concat(...)
e por não usarargs.slice(1)
. Infelizmente, não consegui encontrar uma maneira de me livrar dacurr.slice()
recursão.A seguinte função geradora eficiente retorna o produto cartesiano de todos os iteráveis fornecidos :
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 valora
for fornecido.Todos os outros casos são tratados conforme o esperado, conforme demonstrado pelos seguintes casos de teste:
Exibir trecho de código
fonte
function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
Esta é uma solução recursiva simples e não sofisticada:
fonte
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:
fonte
cartesian([[1],[2]],[10,20],[100,200,300])
.concat()
operador de propagação integrado às vezes pode se tornar enganoso.Aqui está um one-liner usando o ES2019 nativo
flatMap
. Nenhuma biblioteca necessária, apenas um navegador moderno (ou transpiler):É essencialmente uma versão moderna da resposta de Viebel, sem lodash.
fonte
Usando um retrocesso típico com geradores ES6,
Abaixo, há uma versão semelhante compatível com navegadores mais antigos.
Exibir trecho de código
fonte
Esta é uma solução ES6 pura usando funções de seta
fonte
Uma versão coffeescript com lodash:
fonte
Abordagem unifilar, para melhor leitura com recuos.
Leva uma única matriz com matrizes de itens cartesianos desejados.
fonte
if (arr.length === 1) return arr[0].map(el => [el]);
Esta é uma programação funcional marcada , então vamos dar uma olhada na lista monad :
Bem, isso soa como um ajuste perfeito para
cartesian
. JavaScript nos dáArray
e a função de ligação monádica éArray.prototype.flatMap
, então vamos colocá-los em uso -Em vez de
loop
acima,t
pode ser adicionado como um parâmetro curried -fonte
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))
fonte
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
N
arrays,a_1,...,a_N
cada um comm_i
componentes. O produto externo desses arrays temM=m_1*m_2*...*m_N
elementos e podemos identificar cada um deles com umN-
vetor dimensional cujos componentes são inteiros positivos e oi
-ésimo componente é estritamente limitado de cima porm_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.com
node v6.12.2
, eu obtenho os seguintes tempos:fonte
Para quem precisa de TypeScript (reimplementado na resposta de @ Danny)
fonte
Apenas para uma escolha, uma implementação realmente simples usando array
reduce
:fonte
JavaScript moderno em apenas algumas linhas. Sem bibliotecas externas ou dependências como Lodash.
fonte
Você poderia
reduce
o array 2D. UseflatMap
na matriz do acumulador para obter oacc.length x curr.length
número de combinações em cada loop.[].concat(c, n)
é usado porquec
é um número na primeira iteração e uma matriz posteriormente.(Isso é baseado na resposta de Nina Scholz )
fonte
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.
fonte
Uma solução simples "mental e visualmente amigável".
fonte
Uma versão simples e modificada do código de @viebel em Javascript simples:
fonte
Uma implementação mais legível
fonte
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:
mas usado:
fonte
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:
Resultado:
fonte
Abordagem simples de força bruta JS que leva uma série de matrizes como entrada.
fonte
Acabei de converter a resposta de @ dummersl de CoffeScript para JavaScript. Simplesmente funciona.
fonte
Mais uma implementação. Não é o mais curto ou sofisticado, mas rápido:
fonte
Nenhuma biblioteca necessária! :)
Precisa de funções de seta e provavelmente não é tão eficiente. : /
fonte
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.
Cumprimentos.
fonte