Produit cartésien de plusieurs tableaux en JavaScript


112

Comment implémenteriez-vous le produit cartésien de plusieurs tableaux en JavaScript?

Par exemple,

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

devrait revenir

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


3
Ceci implémenté dans le module js-combinatorics: github.com/dankogai/js-combinatorics
Erel Segal-Halevi


Je suis d'accord sur underscore.js mais je ne suis pas sûr de voir comment la suppression de la balise de programmation fonctionnelle aidera @le_m
viebel

Fwiw, d3 ajouté d3.cross(a, b[, reducer])en février. github.com/d3/d3-array#cross
Toph

Réponses:


106

Mise à jour 2017: réponse en 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 juste deux lignes de JavaScript vanilla , pas de lodash, de trait 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é avec 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 d'origine.

Exemple

Voici l'exemple exact de votre question:

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

Production

Voici le résultat 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émo

Voir les démos sur:

Syntaxe

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

Cela rend un code comme celui-ci si simple que c'est un péché de ne pas l'utiliser. Pour les anciennes plates-formes qui ne le prennent pas en charge de manière native, vous pouvez toujours utiliser Babel ou d'autres outils pour le transpiler dans une syntaxe plus ancienne - et en fait mon exemple transpilé par Babel est toujours plus court et plus simple que la plupart des exemples ici, mais ce n'est pas le cas. vraiment important parce que le résultat 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 des centaines de lignes de code difficiles à maintenir et il n'est pas nécessaire d'utiliser des bibliothèques entières pour une chose aussi simple, quand deux lignes de JavaScript vanilla peuvent facilement faire le travail. Comme vous pouvez le voir, il est vraiment avantageux d'utiliser les fonctionnalités modernes du langage et dans les cas où vous devez prendre en charge des plates-formes archaïques sans support natif des fonctionnalités modernes, vous pouvez toujours utiliser Babel ou d'autres outils pour transposer la nouvelle syntaxe à l'ancienne. .

Ne codez pas comme si c'était 1995

JavaScript évolue et il le fait pour une raison. TC39 fait un travail incroyable de conception du langage en ajoutant de nouvelles fonctionnalités et les fournisseurs de navigateurs font un travail incroyable pour implémenter ces fonctionnalités.

Pour voir l'état actuel de la prise en charge native d'une fonctionnalité donnée dans les navigateurs, consultez:

Pour voir la prise en charge dans les versions Node, voir:

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


Voici une version dactylographiée avec un léger changement pour tenir compte de la façon dont le typographie diffuse les tableaux. gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef
Sam Sippe

1
@rsp merci beaucoup pour une très bonne réponse. bien que je voudrais vous demander de l'améliorer un peu pour obtenir un avertissement de variables ombrées (2 variables locales aet 2 variables locales b)
ZuBB

7
"Ne codez pas comme si c'était 1995" - pas besoin d'être désagréable, tout le monde n'a pas encore rattrapé son retard.
Godwhacker

7
C'est bien mais échoue lorsqu'il est nourri avec ['a', 'b'], [1,2], [[9], [10]]ce qui donnera [ [ 'a', 1, 9 ], [ 'a', 1, 10 ], [ 'a', 2, 9 ], [ 'a', 2, 10 ], [ 'b', 1, 9 ], [ 'b', 1, 10 ], [ 'b', 2, 9 ], [ 'b', 2, 10 ] ]en conséquence. Je veux dire ne conservera pas le type d'articles [[9], [10]].
Rédu

1
Puisque nous utilisons ...déjà, ne devrait pas [].concat(...[array])devenir simplement [...array]?
Lazar Ljubenović

89

Voici une solution fonctionnelle au problème (sans aucune variable mutable !) En utilisant reduceet 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);
    }, [ [] ]);
}

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

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


Il y a une faute de frappe dans cette réponse, il ne devrait pas y avoir de ", vrai" (peut-être que le lodash a changé depuis que vous avez publié ce message?)
Chris Jefferson

@ChrisJefferson le deuxième paramètre flattenest de rendre l'aplatissement superficiel. C'est obligatoire ici!
viebel

4
Désolé, c'est une incompatibilité lodash / soulignement, ils ont échangé autour du drapeau.
Chris Jefferson

1
Ainsi, lors de l'aplatissement, utilisez trueavec un trait de soulignement et utilisez falseavec du lodash pour assurer un aplatissement peu profond.
Akseli Palén

Comment modifier cette fonction pour qu'elle accepte un tableau de tableaux?

44

Voici une version modifiée du code de @ viebel en Javascript brut, 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(JSON.stringify(a));


2
Échec pour cartésianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', 'faa']]) car il aplatit ['gamma'] en 'gamma' et [['alpha']] en ['alpha']
Mzn

car .concat(y)au lieu de.concat([ y ])
Merci

@Merci vous pouvez modifier la réponse directement au lieu de commenter, faites-le donc pas besoin maintenant: P
Olivier Lalonde

28

il semble que la communauté pense que c'est trivial et / ou facile de trouver une implémentation de référence, après une brève inspection, je ne pouvais pas ou peut-être que c'est juste que j'aime réinventer la roue ou résoudre des problèmes de programmation de type salle de 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]
   ]

implémentation de référence complète qui est relativement efficace ... :-D

sur l'efficacité: vous pourriez en gagner en retirant le if de la boucle et en ayant 2 boucles séparées car il est techniquement constant et vous aideriez à la prédiction de branche et tout ce désordre, mais ce point est un peu discutable en javascript

anywho, appréciez -ck


1
Merci @ckoz pour votre réponse détaillée. Pourquoi n'utiliseriez-vous pas la reducefonction de tableau?
viebel

1
@viebel pourquoi voulez-vous utiliser la réduction? d'une part, la réduction a un très mauvais support pour les navigateurs plus anciens (voir: developer.mozilla.org/en-US/docs/JavaScript/Reference/… ), et dans tous les cas, ce code fou de cette autre réponse vous semble-t-il réellement lisible ? ça ne me concerne pas. bien sûr, c'est plus court, mais une fois minifié, ce code serait à peu près de la même longueur, plus facile à déboguer / optimiser, deuxièmement, toutes ces solutions de «réduction» se résument à la même chose, sauf qu'elles ont une recherche de fermeture (théoriquement plus lente), c'est aussi plus difficile à concevoir pour qu'il gère des ensembles infinis ...
ckozl

5
J'ai créé une version 2+ fois plus rapide et (imo) plus propre: pastebin.com/YbhqZuf7 Il permet d'augmenter la vitesse en n'utilisant pas result = result.concat(...)et en n'utilisant pas args.slice(1). Malheureusement, je n'ai pas pu trouver un moyen de me débarrasser de curr.slice()la récursivité.
Pauan

2
@Pauan beau travail, belle réduction des points chauds dans l'ensemble pour dans la ligue un gain de performance de 10% à 50% basé sur ce que je vois. Je ne peux pas parler de «propreté» cependant, je pense que votre version est en fait plus difficile à suivre en raison de l'utilisation de variables de portée de fermeture. Mais d'une manière générale, un code plus performant est plus difficile à suivre. J'ai écrit la version originale pour plus de lisibilité, j'aurais aimé avoir plus de temps pour pouvoir vous engager dans une
présentation

c'est vraiment l'un de ces problèmes
James

26

La fonction de générateur efficace suivante renvoie le produit cartésien de tous les itérables donnés :

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

Il accepte les tableaux, les chaînes, les ensembles et tous les autres objets implémentant le protocole itérable .

Suite à la spécification du produit cartésien n-aire qu'il donne

  • []si un ou plusieurs itérables donnés sont vides, par exemple []ou''
  • [[a]]si un seul itérable contenant une seule valeur aest donné.

Tous les autres cas sont traités comme prévu, comme le montrent les cas de test suivants:


Voulez-vous expliquer ce qui se passe sur celui-ci? Merci beaucoup!
LeandroP

Merci de nous avoir enseigné un exemple assez merveilleux d'utilisation de la fonction de générateur + récursion de queue + boucles double couche! Mais la position de la première boucle for dans le code doit être modifiée pour que l'ordre des sous-tableaux de sortie soit correct. Code fixe: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 Si vous voulez reproduire l'ordre des tuples de produit cartésien donné par le commentaire d'OP, alors votre modification est correcte. Cependant, l'ordre des tuples dans le produit n'est généralement pas pertinent, par exemple, mathématiquement, le résultat est un ensemble non ordonné. J'ai choisi cet ordre car il nécessite beaucoup moins d'appels récursifs et est donc un peu plus performant - je n'ai cependant pas exécuté de benchmark.
le_m

Erratum: Dans mon commentaire ci-dessus, "récursion de queue" devrait être "récursion" (pas un appel de queue dans ce cas).
ooo

21

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


2
Celui-ci s'avère être le code JS pur le plus efficace sous cette rubrique. Il faut environ 600 ms pour terminer sur des tableaux d'éléments 3 x 100 pour produire un tableau de longueur 1M.
Rédu

1
Fonctionne pour CartésianProduct ([[[1], [2], [3]], ['a', 'b'], [['gamma'], [['alpha']]], ['zii', «faa»]]); sans aplatissement des valeurs d'origine
Mzn

10

Voici une méthode récursive qui utilise une fonction de générateur ECMAScript 2015 pour que vous n'ayez 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]))));


Cela ne fonctionnera pas si l'un des tableaux a des éléments de tableau tels quecartesian([[1],[2]],[10,20],[100,200,300])
Redu

@Redu Answer a été mis à jour pour prendre en charge les arguments de tableau.
heenenee

Oui, l' .concat()opérateur de diffusion intégré peut parfois devenir trompeur.
Rédu

10

Voici un one-liner utilisant l'ES2019 natif flatMap. Aucune bibliothèque nécessaire, juste un navigateur moderne (ou transpilateur):

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

C'est essentiellement une version moderne de la réponse de viebel, sans lodash.


Bien sûr, aucune bibliothèque n'était nécessaire. Mais ce n'est pas non plus le code le plus lisible qui soit. C'est un compromis.
Arturo Hernandez

La lisibilité a plus à voir dans ce cas avec mon choix d'utiliser l'opérateur de diffusion, je pense, et pas tellement avec le choix de ne pas utiliser de bibliothèque. Je ne pense pas que lodash mène à un code plus lisible du tout.
Fred Kleuver il y a

9

En utilisant un retour en 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%; }

Ci-dessous, une version similaire compatible avec les anciens navigateurs.


9

Il s'agit d'une solution ES6 pure utilisant des fonctions fléchées

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

Une version coffeescript avec lodash:

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

7

Une approche sur une seule ligne, pour une meilleure lecture avec des indentations.

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

Il prend un seul tableau avec des tableaux d'éléments cartésiens voulus.

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


J'ai dû ajouter une déclaration de garde pour gérer correctement le cas où le tableau a un seul élément:if (arr.length === 1) return arr[0].map(el => [el]);
JacobEvelyn

5

Ceci est étiqueté programmation fonctionnelle, alors jetons un coup d'œil à la monade List :

Une application pour cette liste monadique représente le calcul non déterministe. List peut contenir des résultats pour tous les chemins d'exécution dans un algorithme ...

Eh bien, cela semble être un ajustement parfait pour cartesian. JavaScript nous donne Arrayet la fonction de liaison monadique est Array.prototype.flatMap, alors mettons-les à profit -

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

Au lieu de loopci-dessus, tpeut être ajouté en tant que paramètre curry -

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

Quelques-unes des réponses sous cette rubrique é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, pas besoin de soulignement, de lodash que ce soit. Je pense que celui-ci devrait le faire avec du JS ES6 pur, aussi fonctionnel que possible.

Ce morceau de code utilise une réduction et une carte imbriquée, simplement pour obtenir le produit cartésien de deux tableaux, mais le deuxième tableau provient d'un appel récursif à la même fonction avec un tableau de moins; 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))); 


3

Dans mon contexte particulier, l'approche «à l'ancienne» m'a semblé plus efficace que les méthodes basées sur des fonctionnalités plus modernes. Vous trouverez ci-dessous le code (y compris une petite comparaison avec d'autres solutions publiées dans ce fil par @rsp et @sebnukem) si cela s'avère également utile à quelqu'un d'autre.

L'idée suit. Disons que nous construisons le produit externe de Ntableaux, dont a_1,...,a_Nchacun a des m_icomposants. Le produit extérieur de ces tableaux a des M=m_1*m_2*...*m_Néléments et nous pouvons identifier chacun d'eux avec un N-vecteur dimensionnel dont les composants sont des entiers positifs et le i-ème composant est strictement délimité par le haut par m_i. Par exemple, le vecteur (0, 0, ..., 0)correspondrait à la combinaison particulière dans laquelle on prend le premier élément de chaque tableau, tandis qu'il (m_1-1, m_2-1, ..., m_N-1)est identifié avec la combinaison où l'on prend le dernier élément de chaque tableau. Ainsi pour construire toutM combinaisons de , la fonction ci-dessous construit consécutivement tous ces vecteurs et pour chacun d'eux identifie la combinaison correspondante des éléments des tableaux d'entrée.

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

avec node v6.12.2, j'obtiens les horaires suivants:

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

3

Pour ceux qui ont besoin de TypeScript (réimplémentation de la réponse 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

Juste pour un choix une implémentation vraiment simple utilisant des tableaux 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 moderne en quelques lignes. Pas de bibliothèques externes ou de dépendances comme 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

Vous pourriez reducele tableau 2D. Utilisez flatMapsur le tableau d'accumulateurs pour obtenir le acc.length x curr.lengthnombre de combinaisons dans chaque boucle. [].concat(c, n)est utilisé car il cs'agit d'un nombre dans la première itération et d'un tableau après.

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

(Ceci est basé sur la réponse de Nina Scholz )


1

Une approche non récursive qui ajoute la possibilité de filtrer et de modifier les produits avant de les ajouter réellement à l'ensemble de résultats. Notez l'utilisation de .map plutôt que de .forEach. Dans certains navigateurs, .map est plus rapide.

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

Une solution simple "esprit et visuellement".

entrez la description de l'image ici


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

Une version simple et modifiée du code de @ viebel en Javascript brut:

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

Une implémentation plus lisible

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

Ceci est pour 3 tableaux.
Certaines réponses ont donné un moyen pour n'importe quel nombre de tableaux.
Cela peut facilement se contracter ou s'étendre à moins ou plus de baies.
J'avais besoin de combinaisons d'un ensemble avec des répétitions, alors j'aurais pu utiliser:

f(a,a,a)

mais utilisé:

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

0

J'ai remarqué que personne n'a posté de solution permettant de passer une fonction pour traiter chaque combinaison, voici donc ma solution:

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

Production:

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

0

Approche de force brute JS simple qui prend un tableau de tableaux en entrée.

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>

Je viens de convertir la réponse de @ dummersl de CoffeScript en JavaScript. Cela fonctionne juste.

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

Encore une autre mise en œuvre. Pas le plus court ou le plus sophistiqué, mais rapide:

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

Aucune bibliothèque nécessaire! :)

Nécessite des fonctions de flèche et probablement pas aussi efficaces. : /

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

Pour la petite histoire

Voici ma version de celui-ci. Je l'ai fait en utilisant l'itérateur javascript le plus simple "for ()", il est donc compatible dans tous les cas et offre les meilleures performances.

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

Meilleures salutations.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.