¿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]
...
]
¿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]
...
]
d3.cross(a, b[, reducer])
en febrero. github.com/d3/d3-array#crossRespuestas:
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:
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 :
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:
Salida
Esta es la salida de ese comando:
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:
fuente
a
y 2 vars localesb
)['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]]
....
, ¿no debería[].concat(...[array])
ser simple[...array]
?Aquí hay una solución funcional al problema (¡sin ninguna variable mutable !) Usando
reduce
yflatten
, proporcionada porunderscore.js
:Observación: esta solución se inspiró en http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/
fuente
flatten
es hacer que el aplanamiento sea poco profundo. ¡Es obligatorio aquí!true
con subrayado y úselofalse
con lodash para asegurar un aplanamiento poco profundo.Aquí hay una versión modificada del código de @ viebel en JavaScript simple, sin usar ninguna biblioteca:
fuente
.concat(y)
lugar de.concat([ y ])
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 :
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
fuente
reduce
función de matriz?result = result.concat(...)
y al no usarargs.slice(1)
. Desafortunadamente, no pude encontrar una manera de deshacerme decurr.slice()
la recursividad.La siguiente función generadora eficiente devuelve el producto cartesiano de todos los iterables dados :
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 valora
.Todos los demás casos se manejan como se esperaba, como lo demuestran los siguientes casos de prueba:
Mostrar fragmento de código
fuente
function* cartesian(head, ...tail) { for (let h of head) { const remainder = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remainder) yield [h, ...r] } }
Aquí hay una solución recursiva sencilla y sencilla:
fuente
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:
fuente
cartesian([[1],[2]],[10,20],[100,200,300])
.concat()
operador de propagación integrado a veces puede resultar engañoso.Aquí hay un resumen usando el ES2019 nativo
flatMap
. No se necesitan bibliotecas, solo un navegador moderno (o transpilador):Es esencialmente una versión moderna de la respuesta de viebel, sin lodash.
fuente
Usando un retroceso típico con generadores ES6,
A continuación hay una versión similar compatible con navegadores más antiguos.
Mostrar fragmento de código
fuente
Esta es una solución ES6 pura que utiliza funciones de flecha
fuente
Una versión coffeescript con lodash:
fuente
Un enfoque de una sola línea, para una mejor lectura con sangrías.
Se necesita una sola matriz con matrices de elementos cartesianos deseados.
fuente
if (arr.length === 1) return arr[0].map(el => [el]);
Esto está etiquetado como programación funcional, así que echemos un vistazo a la mónada List :
Bueno, eso suena perfecto para
cartesian
. JavaScript nos daArray
y la función de enlace monádico esArray.prototype.flatMap
, así que pongámoslos en uso:En lugar de lo
loop
anterior,t
se puede agregar como un parámetro de curry:fuente
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))
fuente
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
N
matrices,a_1,...,a_N
cada una de las cuales tienem_i
componentes. El producto externo de estas matrices tieneM=m_1*m_2*...*m_N
elementos y podemos identificar cada uno de ellos con unN-
vector dimensional cuyos componentes son enteros positivos y eli
componente -ésimo está estrictamente acotado desde arriba porm_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.con
node v6.12.2
, obtengo los siguientes tiempos:fuente
Para aquellos que necesitan TypeScript (reimplementado @ la respuesta de Danny)
fuente
Solo para una opción, una implementación realmente simple usando matrices
reduce
:fuente
JavaScript moderno en solo unas pocas líneas. Sin bibliotecas externas o dependencias como Lodash.
fuente
Podría
reduce
la matriz 2D. ÚseloflatMap
en la matriz de acumuladores para obtener elacc.length x curr.length
número de combinaciones en cada bucle.[].concat(c, n)
se usa porquec
es un número en la primera iteración y una matriz después.(Esto se basa en la respuesta de Nina Scholz )
fuente
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.
fuente
Una solución simple "mental y visualmente amigable".
fuente
Una versión simple y modificada del código de @ viebel en JavaScript simple:
fuente
Una implementación más legible
fuente
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:
pero usado:
fuente
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:
Salida:
fuente
Enfoque de fuerza bruta de JS simple que toma una matriz de matrices como entrada.
fuente
Acabo de convertir la respuesta de @ dummersl de CoffeScript a JavaScript. Simplemente funciona.
fuente
Otra implementación más. No es el más corto ni elegante, sino rápido:
fuente
¡No se necesitan bibliotecas! :)
Sin embargo, necesita funciones de flecha y probablemente no sea tan eficiente. : /
fuente
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.
Atentamente.
fuente