web-dev-qa-db-fra.com

Recherche de toutes les combinaisons de valeurs de tableau JavaScript

Comment puis-je produire toutes les combinaisons des valeurs dans N nombre de tableaux JavaScript de longueurs variables?

Disons que j'ai un nombre N de tableaux JavaScript, par exemple.

var first = ['a', 'b', 'c', 'd'];
var second = ['e'];
var third =  ['f', 'g', 'h', 'i', 'j'];

(Trois tableaux dans cet exemple, mais son nombre N de tableaux pour le problème.)

Et je veux sortir toutes les combinaisons de leurs valeurs, pour produire

aef
aeg
aeh
aei
aej
bef
beg
....
dej

EDIT: Voici la version que je travaille, en utilisant la réponse acceptée de ffriend comme base.

var allArrays = [['a', 'b'], ['c', 'z'], ['d', 'e', 'f']];

 function allPossibleCases(arr) {
  if (arr.length === 0) {
    return [];
  } 
else if (arr.length ===1){
return arr[0];
}
else {
    var result = [];
    var allCasesOfRest = allPossibleCases(arr.slice(1));  // recur with the rest of array
    for (var c in allCasesOfRest) {
      for (var i = 0; i < arr[0].length; i++) {
        result.Push(arr[0][i] + allCasesOfRest[c]);
      }
    }
    return result;
  }

}
var r=allPossibleCases(allArrays);
 //outputs ["acd", "bcd", "azd", "bzd", "ace", "bce", "aze", "bze", "acf", "bcf", "azf", "bzf"]
46
Yahel

Ce ne sont pas des permutations, voir définitions de permutations de Wikipedia.

Mais vous pouvez y parvenir avec récursivité

var allArrays = [['a', 'b'], ['c'], ['d', 'e', 'f']]

function allPossibleCases(arr) {
  if (arr.length == 1) {
    return arr[0];
  } else {
    var result = [];
    var allCasesOfRest = allPossibleCases(arr.slice(1));  // recur with the rest of array
    for (var i = 0; i < allCasesOfRest.length; i++) {
      for (var j = 0; j < arr[0].length; j++) {
        result.Push(arr[0][j] + allCasesOfRest[i]);
      }
    }
    return result;
  }

}

Vous pouvez également le faire avec des boucles, mais ce sera un peu délicat et nécessitera l’implémentation de votre propre pile analogique.

53
ffriend

Vous n'avez pas besoin de récursion ni de boucles fortement imbriquées, ni même de générer/stocker l'intégralité du tableau de permutations en mémoire.

Puisque le nombre de permutations est le produit des longueurs de chacun des tableaux (appelez cette numPerms), vous pouvez créer une fonction getPermutation(n) qui renvoie une permutation unique entre index 0 et numPerms - 1 en calculant les index nécessaires pour récupérer ses caractères, basé sur n.

Comment est-ce fait? Si vous envisagez de créer des permutations sur des tableaux contenant chacun: [0, 1, 2, ... 9], c'est très simple ... la 245e permutation (n = 245) est "245", plutôt de manière intuitive, ou:

arrayHundreds[Math.floor(n / 100) % 10]
+ arrayTens[Math.floor(n / 10) % 10]
+ arrayOnes[Math.floor(n / 1) % 10]

La complication de votre problème est que les tailles de tableau diffèrent. Nous pouvons contourner ce problème en remplaçant les n/100, n/10, etc ... par d'autres diviseurs. Nous pouvons facilement pré-calculer un tableau de diviseurs à cette fin. Dans l'exemple ci-dessus, le diviseur de 100 était égal à arrayTens.length * arrayOnes.length. Par conséquent, nous pouvons calculer que le diviseur pour un tableau donné est le produit des longueurs des tableaux restants. Le dernier tableau a toujours un diviseur de 1. En outre, au lieu de modifier par 10, nous modifions par la longueur du tableau actuel.

Exemple de code est ci-dessous:

var allArrays = [first, second, third, ...];

// Pre-calculate divisors
var divisors = [];
for (var i = allArrays.length - 1; i >= 0; i--) {
   divisors[i] = divisors[i + 1] ? divisors[i + 1] * allArrays[i + 1].length : 1;
}

function getPermutation(n) {
   var result = "", curArray;

   for (var i = 0; i < allArrays.length; i++) {
      curArray = allArrays[i];
      result += curArray[Math.floor(n / divisors[i]) % curArray.length];
   }

   return result;
}
21
David Tang

Les réponses fournies me paraissent trop difficiles. Donc ma solution est:

var allArrays = new Array(['a', 'b'], ['c', 'z'], ['d', 'e', 'f']);

function getPermutation(array, prefix) {
    prefix = prefix || '';
    if (!array.length) {
        return prefix;
    }

    var result = array[0].reduce(function (result, value) {
        return result.concat(getPermutation(array.slice(1), prefix + value));
    }, []);
    return result;
}

console.log(getPermutation(allArrays));
16
sectus

Je suggère une fonction génératrice récursive simple comme suit:

// Generate cartesian product of given iterables:
function* cartesian(head, ...tail) {
  let remainder = tail.length ? cartesian(...tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

// Example:
const first  = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third  = ['f', 'g', 'h', 'i', 'j'];

console.log(...cartesian(first, second, third));

14
le_m

Copie de la réponse de le_m pour prendre Array of Arrays directement:

function *combinations(arrOfArr) {
  let [head, ...tail] = arrOfArr
  let remainder = tail.length ? combinations(tail) : [[]];
  for (let r of remainder) for (let h of head) yield [h, ...r];
}

J'espère que ça fait gagner du temps à quelqu'un.

6
Vikas Gautam

Vous pouvez utiliser un retour arrière typique:

function cartesianProductConcatenate(arr) {
  var data = new Array(arr.length);
  return (function* recursive(pos) {
    if(pos === arr.length) yield data.join('');
    else for(var i=0; i<arr[pos].length; ++i) {
      data[pos] = arr[pos][i];
      yield* recursive(pos+1);
    }
  })(0);
}

J'ai utilisé des fonctions de générateur pour éviter d'attribuer tous les résultats simultanément, mais si vous le souhaitez, vous pouvez

[...cartesianProductConcatenate([['a', 'b'], ['c', 'z'], ['d', 'e', 'f']])];
// ["acd","ace","acf","azd","aze","azf","bcd","bce","bcf","bzd","bze","bzf"]
5
Oriol

Si vous recherchez une fonction compatible avec le flux pouvant gérer des tableaux à deux dimensions avec n'importe quel type d'élément, vous pouvez utiliser la fonction ci-dessous.

const getUniqueCombinations = <T>(items : Array<Array<T>>, prepend : Array<T> = []) : Array<Array<T>> => {
    if(!items || items.length === 0) return [prepend];

    let out = [];

    for(let i = 0; i < items[0].length; i++){
        out = [...out, ...getUniqueCombinations(items.slice(1), [...prepend, items[0][i]])];
    }

    return out;
}

Une visualisation de l'opération:

dans:

[
    [Obj1, Obj2, Obj3],
    [Obj4, Obj5],
    [Obj6, Obj7]
]

en dehors:

[
    [Obj1, Obj4, Obj6 ],
    [Obj1, Obj4, Obj7 ],
    [Obj1, Obj5, Obj6 ],
    [Obj1, Obj5, Obj7 ],
    [Obj2, Obj4, Obj6 ],
    [Obj2, Obj4, Obj7 ],
    [Obj2, Obj5, Obj6 ],
    [Obj2, Obj5, Obj7 ],
    [Obj3, Obj4, Obj6 ],
    [Obj3, Obj4, Obj7 ],
    [Obj3, Obj5, Obj6 ],
    [Obj3, Obj5, Obj7 ]
]
1

Meilleure façon de trouver les combinaisons

const arr1= [ 'a', 'b', 'c', 'd' ];
const arr2= [ '1', '2', '3' ];
const arr3= [ 'x', 'y', ];

const all = [arr1, arr2, arr3];

const output = all.reduce((acc, cu) => { 
    let ret = [];
      acc.map(obj => {
        cu.map(obj_1 => {
          ret.Push(obj + '-' + obj_1) 
        });
      });
      return ret;
   })

console.log(output);

1
kathir

Vous pouvez aussi utiliser cette fonction:

const result = (arrayOfArrays) => arrayOfArrays.reduce((t, i) => { let ac = []; for (const ti of t) { for (const ii of i) { ac.Push(ti + '/' + ii) } } return ac })

result([['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']])
// which will output [ 'a/e/f', 'a/e/g', 'a/e/h','a/e/i','a/e/j','b/e/f','b/e/g','b/e/h','b/e/i','b/e/j','c/e/f','c/e/g','c/e/h','c/e/i','c/e/j','d/e/f','d/e/g','d/e/h','d/e/i','d/e/j']

Bien sûr, vous pouvez supprimer le + '/' dans ac.Push(ti + '/' + ii) pour éliminer la barre oblique du résultat final. Et vous pouvez remplacer ces for (... of ...) par des fonctions forEach (plus un point-virgule respectif avant return ac), quelles que soient celles avec lesquelles vous êtes le plus à l'aise.

0
Renato Echevarria

Vous pouvez adopter une approche en une seule ligne en générant un produit cartésien.

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

var items = [['a', 'b', 'c', 'd'], ['e'], ['f', 'g', 'h', 'i', 'j']],
    result = items.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; }

0
Nina Scholz

Voici une version adaptée du couple de réponses ci-dessus, qui produit les résultats dans l'ordre spécifié dans l'OP et renvoie des chaînes de caractères au lieu de tableaux:

function *cartesianProduct(...arrays) {
  if (!arrays.length) yield [];
  else {
    const [tail, ...head] = arrays.reverse();
    const beginning = cartesianProduct(...head.reverse());
    for (let b of beginning) for (let t of tail) yield b + t;
  }
}

const first = ['a', 'b', 'c', 'd'];
const second = ['e'];
const third =  ['f', 'g', 'h', 'i', 'j'];
console.log([...cartesianProduct(first, second, third)])
0
Klortho

Une approche tableau sans récursion:

const combinations = [['1', '2', '3'], ['4', '5', '6'], ['7', '8']];
let outputCombinations = combinations[0]

combinations.slice(1).forEach(row => {
  outputCombinations = outputCombinations.reduce((acc, existing) =>
    acc.concat(row.map(item => existing + item))
  , []);
});

console.log(outputCombinations);
0
Tom Banister