Produit cartésien de multiples tableaux en JavaScript

Comment implétez-vous le produit cartésien de multiples tableaux en JavaScript?

Par exemple,

cartesian([1,2],[10,20],[100,200,300]) //should be // [[1,10,100],[1,10,200],[1,10,300],[2,10,100],[2,10,200]...] 

Voici une solution fonctionnelle au problème (sans aucune variable mutable !) En utilisant reduce et à flatten , fourni par 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); }, [ [] ]); }; cartesianProductOf([1, 2], [3, 4], ['a', 'b']); // [[1,3,"a"],[1,3,"b"],[1,4,"a"],[1,4,"b"],[2,3,"a"],[2,3,"b"],[2,4,"a"],[2,4,"b"]] 

Remarque: Cette solution a été inspirée par http://cwestblog.com/2011/05/02/cartesian-product-of-multiple-arrays/

Il semble que la communauté pense que ce soit trivial ou facile à trouver une mise en œuvre de référence, lors d'une inspection brève, je ne pourrais pas ou peut-être que j'aime réinventer la roue ou résoudre des problèmes de programmation en classe, de toute façon, c'est votre jour de chance :

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

Mise en œuvre de référence complète qui est relativement efficace … 😀

Sur l'efficacité: vous pourriez en gagner en prenant le si hors de la boucle et en ayant 2 boucles distinctes car il est techniquement constant et vous aiderez avec la prédiction de branche et tout ce désordre, mais ce point est un peu discutable dans javascript

N'importe qui, profite -ck

Voici une version modifiée du code @ viebel en Javascript simple, sans utiliser de bibliothèque:

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

Mise à jour de 2017: réponse de 2 lignes avec vanilla JS

Toutes les réponses ici sont trop compliquées , la plupart d'entre elles prennent 20 lignes de code ou même plus.

Cet exemple utilise seulement deux lignes de JavaScript vanille , pas de listes, de soulignement ou d'autres bibliothèques:

 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; 

Mettre à jour:

Ceci est le même que ci-dessus, mais amélioré pour suivre strictement le Guide de style JavaScript Airbnb – validé en utilisant ESLint avec 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 merci spécial à ZuBB pour m'avoir informé des problèmes de linter avec le code original.

Exemple

Voici l'exemple exact de votre question:

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

Sortie

C'est la sortie de cette commande:

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

Démonstration

Voir les démos sur:

  • JS Bin avec Babel (pour les anciens navigateurs)
  • JS Bin sans Babel (pour les navigateurs modernes)

Syntaxe

La syntaxe que j'ai utilisée ici n'est rien de nouveau. Mon exemple utilise l'opérateur de répartition et les autres paramètres – fonctionnalités de JavaScript définies dans la 6ème édition de la norme ECMA-262 publiée en juin 2015 et développée plus tôt, mieux connu sous le nom ES6 ou ES2015. Voir:

Cela rend le code tellement simple que c'est un péché de ne pas l'utiliser. Pour les anciennes plates-formes qui ne le supportent pas de manière native, vous pouvez toujours utiliser Babel ou d'autres outils pour le transpiler vers une ancienne syntaxe – et en fait, mon exemple transpilé par Babel est encore plus court et plus simple que la plupart des exemples ici, mais ce n'est pas le cas Vraiment important parce que la sortie de la transpilation n'est pas quelque chose que vous devez comprendre ou maintenir, c'est juste un fait que j'ai trouvé intéressant.

Conclusion

Il n'est pas nécessaire d'écrire une centaine de lignes de code qui est difficile à entretenir et il n'est pas nécessaire d'utiliser des bibliothèques entières pour une telle chose simple, lorsque deux lignes de JavaScript vanilla peuvent facilement effectuer le travail. Comme vous pouvez le voir, il est vraiment utile d'utiliser les fonctionnalités modernes de la langue et dans les cas où vous devez prendre en charge les plates-formes archaïques sans support natif des fonctionnalités modernes, vous pouvez toujours utiliser Babel ou d'autres outils pour transporter la nouvelle syntaxe vers l'ancienne .

Ne codez pas comme c'est 1995

JavaScript évolue et il le fait pour une raison quelconque. TC39 fait un travail incroyable de la conception de la langue avec l'ajout de nouvelles fonctionnalités et les fournisseurs de navigateurs font un travail incroyable de mise en œuvre de ces fonctionnalités.

Pour voir l'état actuel du support natif d'une caractéristique donnée dans les navigateurs, voir:

Pour voir le support dans les versions Node, voir:

Pour utiliser la syntaxe moderne sur les plates-formes qui ne la supportent pas de manière native, utilisez Babel:

Voici une solution récursive simple et simple:

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

Voici une méthode récursive qui utilise une fonction générateur ECMAScript 2015 , de sorte que vous ne devez pas créer tous les tuples à la fois:

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

En utilisant un retour arrière typique avec des générateurs 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%; } 

Une version coffeescript avec lodash:

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

Il s'agit d'une solution ES6 pure utilisant des fonctions de flèche

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

Quelques-unes des réponses dans ce sujet échouent lorsque l'un des tableaux d'entrée contient un élément de tableau. Vous feriez mieux de vérifier cela.

Quoi qu'il en soit, il n'est pas nécessaire de souligner, de lodash. Je crois que celui-ci devrait le faire avec JS ES6 pur, aussi fonctionnel que possible.

Ce code utilise une carte réduite et une carte imbriquée, simplement pour obtenir le produit cartésien de deux tableaux, mais le second tableau provient d'un appel récursif à la même fonction avec un tableau inférieur; Par conséquent .. 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))); 

Une approche non récursive qui ajoute la possibilité de filtrer et de modifier les produits avant de les ajouter au jeu de résultats. Notez l'utilisation de .map plutôt que .forEach. Dans certains navigateurs, .map fonctionne plus rapidement.

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

La fonction générateur efficace suivante renvoie le produit cartésien de toutes les informations données:

 // Return cartesian product of given iterables: function* cartesian(head, ...tail) { const remaining = tail.length > 0 ? cartesian(...tail) : [[]]; for (let r of remaining) for (let h of head) yield [h, ...r]; } // Example: console.log(...cartesian([1, 2], [10, 20], [100, 200, 300]));