Producto cartesiano de múltiples matrices en JavaScript
¿Cómo implementarías el producto cartesiano de múltiples matrices en JavaScript?
Como ejemplo,
cartesian([1, 2], [10, 20], [100, 200, 300])
debería regresar
[
[1, 10, 100],
[1, 10, 200],
[1, 10, 300],
[2, 10, 100],
[2, 10, 200]
...
]
Actualización 2020: respuesta de 1 línea (!) con Vanilla JS
Respuesta original de 2017: respuesta de 2 líneas con Vanilla JS: (ver actualizaciones a continuación)
Todas las respuestas aquí son demasiado complicadas , la mayoría requieren 20 líneas de código o incluso más.
Este ejemplo utiliza solo dos líneas de JavaScript básico , sin lodash, guión bajo 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:
Esto es lo mismo que el anterior, pero mejorado para seguir estrictamente la Guía de estilo de JavaScript de Airbnb , validada usando 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 los problemas de linter con el código original.
Actualización 2020:
Desde que escribí esta respuesta, obtuvimos funciones integradas aún mejores, que finalmente pueden permitirnos reducir (sin juego de palabras) el código a solo 1 línea.
const cartesian =
(...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));
Un agradecimiento especial a entintador por sugerir el uso de reducir.
Un agradecimiento especial a Bergi por sugerir el uso del flatMap recién agregado.
¡Un agradecimiento especial a ECMAScript 2019 por agregar flat y flatMap al lenguaje!
Ejemplo
Este es el ejemplo exacto de su pregunta:
let output = cartesian([1,2],[10,20],[100,200,300]);
Producción
Este es el resultado 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:
- JS Bin con Babel (para navegadores antiguos)
- JS Bin sin Babel (para navegadores modernos)
Sintaxis
La sintaxis que utilicé aquí no es nada nueva. Mi ejemplo utiliza el operador spread y los parámetros restantes: características de JavaScript definidas en la sexta edición del estándar ECMA-262 publicado en junio de 2015 y desarrollado mucho antes, más conocido como ES6 o ES2015. Ver:
- http://www.ecma-international.org/ecma-262/6.0/
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/rest_parameters
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
Los nuevos métodos del ejemplo de la Actualización 2020 se agregaron en ES2019:
- http://www.ecma-international.org/ecma-262/10.0/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
Hace que un código como este sea tan simple que es pecado no usarlo. Para plataformas antiguas que no lo admiten de forma nativa, siempre puedes usar Babel u otras herramientas para transpilarlo a una sintaxis anterior; 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 lo hace. Realmente importa porque el resultado de la transpilación no es algo que necesites entender o mantener, es simplemente un hecho que me pareció interesante.
Conclusión
No hay necesidad de escribir cientos de líneas de código que son difíciles de mantener y no hay necesidad de usar bibliotecas enteras para algo tan simple, cuando dos líneas de JavaScript básico pueden hacer el trabajo fácilmente. Como puede ver, realmente vale la pena usar funciones modernas del lenguaje y, en los casos en los que necesite admitir plataformas arcaicas sin soporte nativo de las funciones modernas, siempre puede usar Babel , TypeScript u otras herramientas para transpilar la nueva sintaxis a la el viejo.
No codifiques 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 determinada en los navegadores, consulte:
- http://caniuse.com/
- https://kangax.github.io/compat-table/
Para ver el soporte en las versiones de Node, consulte:
- http://nodo.verde/
Para usar una sintaxis moderna en plataformas que no la admiten de forma nativa, use Babel o TypeScript:
- https://babeljs.io/
- https://www.typescriptlang.org/
Aquí hay una solución funcional al problema (¡sin ninguna variable mutable !) usando reduce
y 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/