web-dev-qa-db-fra.com

JavaScript - Génération de combinaisons à partir de n tableaux avec m éléments

J'ai du mal à trouver du code pour générer des combinaisons à partir de n nombres de tableaux avec m nombre d'éléments, en JavaScript. J'ai vu des questions similaires à ce sujet pour d'autres langues, mais les réponses intègrent la magie syntaxique ou de bibliothèque que je ne sais pas comment traduire.

Considérez ces données:

[[0,1], [0,1,2,3], [0,1,2]]

3 tableaux, avec un nombre différent d'éléments en eux. Ce que je veux faire, c'est obtenir toutes les combinaisons en combinant un élément de chaque tableau.

Par exemple:

0,0,0 // item 0 from array 0, item 0 from array 1, item 0 from array 2
0,0,1
0,0,2
0,1,0
0,1,1
0,1,2
0,2,0
0,2,1
0,2,2

Etc.

Si le nombre de tableaux était fixe, il serait facile de faire une implémentation codée en dur. Mais le nombre de tableaux peut varier:

[[0,1], [0,1]]
[[0,1,3,4], [0,1], [0], [0,1]]

Toute aide serait très appréciée.

43
quano

En voici une assez simple et courte utilisant une fonction d'assistance récursive:

function cartesian() {
    var r = [], arg = arguments, max = arg.length-1;
    function helper(arr, i) {
        for (var j=0, l=arg[i].length; j<l; j++) {
            var a = arr.slice(0); // clone arr
            a.Push(arg[i][j]);
            if (i==max)
                r.Push(a);
            else
                helper(a, i+1);
        }
    }
    helper([], 0);
    return r;
}

Usage:

cartesian([0,1], [0,1,2,3], [0,1,2]);

Pour que la fonction prenne un tableau de tableaux, changez simplement la signature en function cartesian(arg) pour que arg soit un paramètre au lieu de all arguments .

86
Bergi

Vous pouvez adopter une approche itérative en créant des sous-tableaux.

var parts = [[0, 1], [0, 1, 2, 3], [0, 1, 2]],
    result = parts.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; }
10
Nina Scholz

Je propose un simple récursif fonction générateur :

// Generate all combinations of array elements:
function* cartesian(head, ...tail) {
  let remainder = tail.length ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}


// Example:
for (let c of cartesian([0,1], [0,1,2,3], [0,1,2])) {
  console.log(...c);
}
6
le_m

Juste pour le plaisir, voici une variante plus fonctionnelle de la solution dans ma première réponse:

function cartesian() {
    var r = [], args = Array.from(arguments);
    args.reduceRight(function(cont, factor, i) {
        return function(arr) {
            for (var j=0, l=factor.length; j<l; j++) {
                var a = arr.slice(); // clone arr
                a[i] = factor[j];
                cont(a);
            }
        };
    }, Array.prototype.Push.bind(r))(new Array(args.length));
    return r;
}

Alternativement, pour une vitesse maximale, nous pouvons compiler dynamiquement nos propres boucles:

function cartesian() {
    return (cartesian.cache[arguments.length] || cartesian.compile(arguments.length)).apply(null, arguments);
}
cartesian.cache = [];
cartesian.compile = function compile(n) {
    var args = [],
        indent = "",
        up = "",
        down = "";
    for (var i=0; i<n; i++) {
        var arr = "$"+String.fromCharCode(97+i),
            ind = String.fromCharCode(105+i);
        args.Push(arr);
        up += indent+"for (var "+ind+"=0, l"+arr+"="+arr+".length; "+ind+"<l"+arr+"; "+ind+"++) {\n";
        down = indent+"}\n"+down;
        indent += "  ";
        up += indent+"arr["+i+"] = "+arr+"["+ind+"];\n";
    }
    var body = "var res=[],\n    arr=[];\n"+up+indent+"res.Push(arr.slice());\n"+down+"return res;";
    return cartesian.cache[n] = new Function(args, body);
}
3
Bergi

Après avoir fait un peu de recherche, j'ai découvert une question connexe précédente: Recherche de toutes les combinaisons de valeurs de tableau JavaScript

J'ai adapté une partie du code à partir de là pour qu'il renvoie un tableau de tableaux contenant toutes les permutations:

function(arraysToCombine) {
    var divisors = [];
    for (var i = arraysToCombine.length - 1; i >= 0; i--) {
       divisors[i] = divisors[i + 1] ? divisors[i + 1] * arraysToCombine[i + 1].length : 1;
    }

    function getPermutation(n, arraysToCombine) {
       var result = [], 
           curArray;    
       for (var i = 0; i < arraysToCombine.length; i++) {
          curArray = arraysToCombine[i];
          result.Push(curArray[Math.floor(n / divisors[i]) % curArray.length]);
       }    
       return result;
    }

    var numPerms = arraysToCombine[0].length;
    for(var i = 1; i < arraysToCombine.length; i++) {
        numPerms *= arraysToCombine[i].length;
    }

    var combinations = [];
    for(var i = 0; i < numPerms; i++) {
        combinations.Push(getPermutation(i, arraysToCombine));
    }
    return combinations;
}

J'ai mis une copie de travail à http://jsfiddle.net/7EakX/ qui prend le tableau que vous avez donné précédemment ([[0,1], [0,1,2,3], [0,1,2]]) et renvoie le résultat à la console du navigateur.

3
Neil Mountford
var f = function(arr){
    if(typeof arr !== 'object'){
        return false;
    }

    arr = arr.filter(function(elem){ return (elem !== null); }); // remove empty elements - make sure length is correct
    var len = arr.length;

    var nextPerm = function(){ // increase the counter(s)
        var i = 0;

        while(i < len)
        {
            arr[i].counter++;

            if(arr[i].counter >= arr[i].length){
                arr[i].counter = 0;
                i++;
            }else{
                return false;
            }
        }

        return true;
    };

    var getPerm = function(){ // get the current permutation
        var perm_arr = [];

        for(var i = 0; i < len; i++)
        {
            perm_arr.Push(arr[i][arr[i].counter]);
        }

        return perm_arr;
    };

    var new_arr = [];

    for(var i = 0; i < len; i++) // set up a counter property inside the arrays
    {
        arr[i].counter = 0;
    }

    while(true)
    {
        new_arr.Push(getPerm()); // add current permutation to the new array

        if(nextPerm() === true){ // get next permutation, if returns true, we got them all
            break;
        }
    }

    return new_arr;
};
2
Neob91

Voici une autre façon de procéder. Je traite les indices de tous les tableaux comme un nombre dont les chiffres sont toutes des bases différentes (comme l'heure et les dates), en utilisant la longueur du tableau comme radix.

Donc, en utilisant votre premier ensemble de données, le premier chiffre est la base 2, le second est la base 4 et le troisième est la base 3. Le compteur commence 000, puis passe 001, 002, puis 010. Les chiffres correspondent aux indices dans le tableaux, et puisque l'ordre est préservé, ce n'est pas un problème.

J'ai un violon avec ça fonctionne ici: http://jsfiddle.net/Rykus0/DS9Ea/1/

et voici le code:

// Arbitrary base x number class 
var BaseX = function(initRadix){
    this.radix     = initRadix ? initRadix : 1;    
    this.value     = 0;
    this.increment = function(){
        return( (this.value = (this.value + 1) % this.radix) === 0);
    }
}

function combinations(input){
    var output    = [],    // Array containing the resulting combinations
        counters  = [],    // Array of counters corresponding to our input arrays
        remainder = false, // Did adding one cause the previous digit to rollover?
        temp;              // Holds one combination to be pushed into the output array

    // Initialize the counters
    for( var i = input.length-1; i >= 0; i-- ){
        counters.unshift(new BaseX(input[i].length));
    }

    // Get all possible combinations
    // Loop through until the first counter rolls over
    while( !remainder ){
        temp      = [];   // Reset the temporary value collection array
        remainder = true; // Always increment the last array counter

        // Process each of the arrays
        for( i = input.length-1; i >= 0; i-- ){
            temp.unshift(input[i][counters[i].value]); // Add this array's value to the result

            // If the counter to the right rolled over, increment this one.
            if( remainder ){
                remainder = counters[i].increment();
            }
        }
        output.Push(temp); // Collect the results.
    }

    return output;
}

// Input is an array of arrays
console.log(combinations([[0,1], [0,1,2,3], [0,1,2]]));
2
Tom Pietrosanti

Une autre implémentation avec le style récursif ES6

Array.prototype.cartesian = function(a,...as){
  return a ? this.reduce((p,c) => (p.Push(...a.cartesian(...as).map(e => as.length ? [c,...e] : [c,e])),p),[])
           : this;
};

console.log(JSON.stringify([0,1].cartesian([0,1,2,3], [[0],[1],[2]])));
1
Redu