web-dev-qa-db-fra.com

Permutations sans appel de fonction récursif

Condition préalable: algorithme pour générer toutes les combinaisons possibles d'un ensemble, sans duplication, ni appel récursif d'une fonction pour renvoyer des résultats.

La majorité, sinon toutes les réponses fournies à Permutations en JavaScript? appelle de manière récursive une fonction depuis une boucle ou une autre fonction pour renvoyer les résultats. 

Exemple d'appel de fonction récursif dans la boucle

function p(a, b, res) {
  var b = b || [], res = res || [], len = a.length;
  if (!len) 
    res.Push(b)
  else 
    for (var i = 0; i < len 
         // recursive call to `p` here
       ; p(a.slice(0, i).concat(a.slice(i + 1, len)), b.concat(a[i]), res)
       , i++
    );
  return res
}

p(["a", "b", "c"]);

La Question actuelle tente de créer la permutation donnée dans un processus linéaire, en s’appuyant sur la permutation précédente.

Par exemple, étant donné un tableau 

var arr = ["a", "b", "c"];

déterminer le nombre total de permutations possibles 

for (var len = 1, i = k = arr.length; len < i ; k *= len++);

k devrait retourner 6, ou le nombre total de permutations possibles de arr["a", "b", "c"]

Avec le nombre total de permutations individuelles déterminées pour un ensemble, le tableau résultant, qui contiendrait les six permutations, pourrait être créé et rempli à l'aide de Array.prototype.slice(), Array.prototype.concat() et Array.prototype.reverse().

var res = new Array(new Array(k));

res[0] = arr;

res[1] = res[0].slice(0,1).concat(res[0].slice(-2).reverse());

res[2] = res[1].slice(-1).concat(res[1].slice(0,2));

res[3] = res[2].slice(0,1).concat(res[2].slice(-2).reverse());

res[4] = res[3].slice(-2).concat(res[3].slice(0,1));

res[5] = res[4].slice(0,1).concat(res[4].slice(-2).reverse());

Tentative de reproduction des résultats basés sur le motif affiché sur le graphique pour Un algorithme de permutation lexicographique ordonnée basé sur celui publié dans Practical Algorithms en C++ at Calcul des permutations et questions d'entrevue d'emploi .

Il semble y avoir un motif qui pourrait être étendu si le jeu d’entrées était, par exemple 

["a", "b", "c", "d", "e"]

où 120 permutations seraient attendues.

Exemple de tentative de remplissage d'un tableau en s'appuyant uniquement sur une permutation antérieure

// returns duplicate entries at `j`
var arr = ["a", "b", "c", "d", "e"], j = [];
var i = k = arr.length;
arr.forEach(function(a, b, array) {
 if (b > 1) {
  k *= b;
  if (b === i -1) {
    for (var q = 0;j.length < k;q++) {
      if (q === 0) {
       j[q] = array;
      } else {
       j[q] = !(q % i) 
              ? array.slice(q % i).reverse().concat(array.slice(0, q % i)) 
              : array.slice(q % i).concat(array.slice(0, q % i));
      }
    }
  }
 }
})

cependant, vous n’avez pas encore été en mesure de faire les ajustements nécessaires aux paramètres pour .slice(), .concat(), .reverse() au-dessus de js pour passer d’une permutation à l’autre; en n'utilisant que l'entrée de tableau précédente dans res pour déterminer la permutation actuelle, sans utiliser récursif.

Remarqué même, nombre impair d'appels et essayé d'utiliser modulus % et le tableau d'entrée .length pour appeler .reverse() ou ne pas suivre le tableau ["a", "b", "c", "d", "e"], sans produire de résultats sans entrées en double.

Le résultat attendu est que le modèle ci-dessus pourrait être réduit à deux lignes appelées successivement pour la durée du processus jusqu'à ce que toutes les permutations soient terminées, res rempli; un pour chaque appel à .reverse(), appel sans .reverse(); par exemple, après que res[0] soit rempli

// odd , how to adjust `.slice()` , `.concat()` parameters 
// for array of unknown `n` `.length` ?
res[i] = res[i - 1].slice(0,1).concat(res[i - 1].slice(-2).reverse());
// even    
res[i] = res[1 - 1].slice(-1).concat(res[i - 1].slice(0,2));

Question: Quels ajustements au modèle ci-dessus sont nécessaires, en particulier des paramètres, ou un index, passé à .slice(), .concat() pour produire toutes les permutations possibles d'un ensemble donné sans utiliser un appel récursif à la fonction en cours de traitement?

var arr = ["a", "b", "c"];

for (var len = 1, i = k = arr.length; len < i; k *= len++);

var res = new Array(new Array(k));

res[0] = arr;

res[1] = res[0].slice(0, 1).concat(res[0].slice(-2).reverse());

res[2] = res[1].slice(-1).concat(res[1].slice(0, 2));

res[3] = res[2].slice(0, 1).concat(res[2].slice(-2).reverse());

res[4] = res[3].slice(-2).concat(res[3].slice(0, 1));

res[5] = res[4].slice(0, 1).concat(res[4].slice(-2).reverse());

console.log(res);


Modifier, mettre à jour

Ont trouvé un processus d'utilisation du modèle décrit ci-dessus pour renvoyer les permutations dans l'ordre lexicographique pour une entrée allant jusqu'à .length 4, à l'aide d'une seule boucle for. Les résultats attendus ne sont pas renvoyés pour le tableau avec .length de 5

La tendance est basée sur le deuxième graphique à "Calcul des permutations et questions d'entretien d'embauche" [ 0 ].

Préférerait ne pas utiliser .splice() ou .sort() pour renvoyer les résultats, bien qu'ils soient utilisés ici en essayant de respecter la dernière "rotation" à chaque colonne. La variable r doit faire référence à la index du premier élément de la permutation suivante, ce qu'elle fait. 

L'utilisation de .splice(), .sort() peut être incluse si leur utilisation suit le modèle du graphique; à js ci-dessous, ce n'est pas le cas.

Pas tout à fait sûr que le problème avec js ci-dessous ne soit que la déclaration qui suit if (i % (total / len) === reset), bien que cette partie ait nécessité le plus de temps; mais ne retourne toujours pas les résultats attendus.

Plus précisément, en référence maintenant au graphique, en faisant pivoter, par exemple, 2 pour indexer 0, 1 pour indexer 2. Vous avez tenté d’atteindre cet objectif en utilisant r, qui est un index négatif, pour parcourir de droite à gauche afin d’extraire le prochain élément à positionner sur index0 de la "colonne" adjacente.

Dans la colonne suivante, 2 serait placé à index2, 3 serait placé à index0. Cette partie, dans la mesure où elle a été capable de saisir ou de déboguer, est jusqu’à présent la zone où l’erreur se produit. 

Encore une fois, renvoie les résultats attendus pour [1,2,3,4], mais pas pour [1,2,3,4,5]

var arr = [1, 2, 3, 4];
for (var l = 1, j = total = arr.length; l < j ; total *= l++);
for (var i = 1
     , reset = 0
     , idx = 0
     , r = 0
     , len = arr.length
     , res = [arr]
     ; i < total; i++) {
  // previous permutation
  var prev = res[i - 1];
  // if we are at permutation `6` here, or, completion of all 
  // permutations beginning with `1`;
  // setting next "column", place `2` at `index` 0;
  // following all permutations beginning with `2`, place `3` at
  // `index` `0`; with same process for `3` to `4`
  if (i % (total / len) === reset) {
    r = --r % -(len);
    var next = prev.slice(r);
    if (r === -1) {
      // first implementation used for setting item at index `-1`
      // to `index` 0
      // would prefer to use single process for all "rotations",
      // instead of splitting into `if` , `else`, though not there, yet
      res[i] = [next[0]].concat(prev.slice(0, 1), prev.slice(1, len - 1)
               .reverse());
    } else {
      // workaround for "rotation" at from `index` `r` to `index` `0`
      // the chart does not actually use the previous permutation here,
      // but rather, the first permutation of that particular "column";
      // here, using `r` `,i`, `len`, would be 
      // `res[i - (i - 1) % (total / len)]`
      var curr = prev.slice();
      // this may be useful, to retrieve `r`, 
      // `prev` without item at `r` `index`
      curr.splice(prev.indexOf(next[0]), 1);
      // this is not optiomal
      curr.sort(function(a, b) {
        return arr.indexOf(a) > arr.indexOf(b)
      });
      // place `next[0]` at `index` `0`
      // place remainder of sorted array at `index` `1` - n
      curr.splice(0, 0, next[0])
      res[i] = curr
    }
    idx = reset;
  } else {
    if (i % 2) {
      // odd
      res[i] = prev.slice(0, len - 2).concat(prev.slice(-2)
              .reverse())
    } else {
      //  even
      --idx
      res[i] = prev.slice(0, len - (len - 1))
               .concat(prev.slice(idx), prev.slice(1, len + (idx)))
    }
  }
}
// try with `arr` : `[1,2,3,4,5]` to return `res` that is not correct;
// how can above `js` be adjusted to return correct results for `[1,2,3,4,5]` ?
console.log(res, res.length)


Ressources:

Génération de permutation avec Javascript

(Compte à rebours) Lexicographie de la tête QuickPerm: (Anciennement Example_03 ~ Palindromes)

Génération de toutes les permutations [non récursive] (Tentative de transfert de C++ à javascript jsfiddle http://jsfiddle.net/tvvvjf3p/ )

Calcul de permutation sans récurrence - Partie 2

permutations d'une chaîne utilisant l'itération

itération-permutation

Permutations par swapping

Evaluation des algorithmes de permutation

algorithme de permutation sans récursivité? Java

Algorithme non récursif pour la permutation complète avec des éléments répétitifs?

Permutations de chaînes en Java (non récursives)

Générer des permutations paresseusement

Comment générer toutes les permutations d'une liste en Python

Toutes les permutations d'un ensemble ou d'une chaîne peuvent-elles être générées en un temps O (n log n)?

Recherche de la nième permutation lexicographique de «0123456789»

Combinaisons et permutations

29
guest271314

Voici une solution simple pour calculer la nième permutation d'une chaîne:

function string_nth_permutation(str, n) {
    var len = str.length, i, f, res;

    for (f = i = 1; i <= len; i++)
        f *= i;

    if (n >= 0 && n < f) {
        for (res = ""; len > 0; len--) {
            f /= len;
            i = Math.floor(n / f);
            n %= f;
            res += str.charAt(i);
            str = str.substring(0, i) + str.substring(i + 1);
        }
    }
    return res;
}

L'algorithme suit ces étapes simples:

  • d'abord, calculez f = len!, il y a factorial(len) totales permutations d'un ensemble de len différents éléments. 
  • en tant que premier élément, divisez le nombre de permutation par (len-1)! et choisissez l'élément au décalage résultant. Il existe (len-1)! différentes permutations qui ont un élément donné comme premier élément.
  • retirez l'élément choisi de l'ensemble et utilisez le reste de la division comme numéro de permutation pour continuer.
  • effectuez ces étapes avec le reste de la série, dont la longueur est réduite de un.

Cet algorithme est très simple et possède des propriétés intéressantes:

  • Il calcule la nième permutation directement.
  • Si l'ensemble est commandé, les permutations sont générées dans l'ordre lexicographique.
  • Cela fonctionne même si les éléments d'ensemble ne peuvent pas être comparés les uns aux autres, tels que des objets, des tableaux, des fonctions ...
  • Le numéro de permutation 0 correspond à l'ensemble dans l'ordre indiqué.
  • Le numéro de permutation factorial(a.length)-1 est le dernier: l'ensemble a dans l'ordre inverse.
  • Les permutations en dehors de cette plage sont retournées comme indéfinies.

Il peut facilement être converti pour gérer un ensemble stocké sous forme de tableau:

function array_nth_permutation(a, n) {
    var b = a.slice();  // copy of the set
    var len = a.length; // length of the set
    var res;            // return value, undefined
    var i, f;

    // compute f = factorial(len)
    for (f = i = 1; i <= len; i++)
        f *= i;

    // if the permutation number is within range
    if (n >= 0 && n < f) {
        // start with the empty set, loop for len elements
        for (res = []; len > 0; len--) {
            // determine the next element:
            // there are f/len subsets for each possible element,
            f /= len;
            // a simple division gives the leading element index
            i = Math.floor(n / f);
            // alternately: i = (n - n % f) / f;
            res.Push(b.splice(i, 1)[0]);
            // reduce n for the remaining subset:
            // compute the remainder of the above division
            n %= f;
            // extract the i-th element from b and Push it at the end of res
        }
    }
    // return the permutated set or undefined if n is out of range
    return res;
}

clarification:

  • f est d'abord calculé en tant que factorial(len).
  • Pour chaque étape, f est divisé par len, donnant exactement la factorielle précédente.
  • n divisé par cette nouvelle valeur de f donne le numéro d'emplacement parmi les len logements qui ont le même élément initial. Javascript n'a pas de division intégrale, nous pourrions utiliser (n / f) ... 0) pour convertir le résultat de la division en partie intégrante, mais cela introduit une limitation aux ensembles de 12 éléments. Math.floor(n / f) permet de créer des ensembles de 18 éléments maximum. Nous pourrions aussi utiliser (n - n % f) / f, probablement plus efficace aussi.
  • n doit être réduit au nombre de permutations dans cet emplacement, c’est-à-dire au reste de la division n / f.

Nous pourrions utiliser i différemment dans la deuxième boucle, en stockant le reste de la division, en évitant Math.floor() et l'opérateur extra %. Voici une alternative pour cette boucle qui peut être même moins lisible:

        // start with the empty set, loop for len elements
        for (res = []; len > 0; len--) {
            i = n % (f /= len);
            res.Push(b.splice((n - i) / f, 1)[0]);
            n = i;
        }
18
chqrlie

Je pense que ceci post devrait vous aider. L'algorithme devrait être facilement traduisible en JavaScript (je pense qu'il est déjà compatible à plus de 70% avec JavaScript).

slice et reverse sont de mauvais appels à utiliser si vous recherchez l'efficacité. L'algorithme décrit dans l'article suit l'implémentation la plus efficace de la fonction next_permutation, qui est même intégrée dans certains langages de programmation (comme C++, par exemple).

EDIT

Comme j'ai parcouru l'algorithme une fois de plus, je pense que vous pouvez simplement supprimer les types de variables et que vous devriez être prêt à utiliser JavaScript.

EDIT

Version JavaScript:

function nextPermutation(array) {
    // Find non-increasing suffix
    var i = array.length - 1;
    while (i > 0 && array[i - 1] >= array[i])
        i--;
    if (i <= 0)
        return false;

    // Find successor to pivot
    var j = array.length - 1;
    while (array[j] <= array[i - 1])
        j--;
    var temp = array[i - 1];
    array[i - 1] = array[j];
    array[j] = temp;

    // Reverse suffix
    j = array.length - 1;
    while (i < j) {
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
        i++;
        j--;
    }
    return true;
}
8
Boris Strandjev

Une méthode pour créer des permutations consiste à ajouter chaque élément dans tous les espaces entre les éléments dans tous les résultats obtenus jusqu'à présent. Cela peut être fait sans récursivité en utilisant des boucles et une file d'attente.

Code JavaScript:

function ps(a){
  var res = [[]];

  for (var i=0; i<a.length; i++){
    while(res[res.length-1].length == i){
      var l = res.pop();
      for (var j=0; j<=l.length; j++){
        var copy = l.slice();
        copy.splice(j,0,a[i]);
        res.unshift(copy);
      }
    }
  }
  return res;
}

console.log(JSON.stringify(ps(['a','b','c','d'])));
7
גלעד ברקן

Voici une autre solution, inspirée de l’algorithme de Steinhaus-Johnson-Trotter :

function p(input) {
  var i, j, k, temp, base, current, outputs = [[input[0]]];
  for (i = 1; i < input.length; i++) {
    current = [];
    for (j = 0; j < outputs.length; j++) {
      base = outputs[j];
      for (k = 0; k <= base.length; k++) {
        temp = base.slice();
        temp.splice(k, 0, input[i]);
        current.Push(temp);
      }
    }
    outputs = current;
  }
  return outputs;
}

// call

var outputs = p(["a", "b", "c", "d"]);
for (var i = 0; i < outputs.length; i++) {
  document.write(JSON.stringify(outputs[i]) + "<br />");
}

6
sp00m

J'ose ajouter une autre réponse visant à répondre à votre question concernant slice, concat, reverse.

La réponse est que c'est possible (ou presque), mais cela ne serait pas très efficace. Ce que vous faites dans votre algorithme est le suivant:

  • Recherchez la première inversion dans le tableau de permutation, de droite à gauche (inversion dans ce cas définie par i et ji <j et perm[i]> perm[j], indices donnés de gauche à droite).
  • placer le plus grand nombre de l'inversion
  • concaténer les nombres traités dans l’ordre inversé, qui sera identique à l’ordre trié, puisqu’aucune inversion n’a été observée.
  • concaténer le deuxième nombre de l'inversion (toujours trié selon le nombre précédent, aucune inversion n'ayant été observée)

C’est principalement ce que fait ma première réponse, mais d’une manière un peu plus optimale.

Exemple

Considérez les permutations 9,10, 11, 8, 7, 6, 5, 4, 3,2,1 La première inversion de droite à gauche est 10, 11. Et vraiment le La permutation suivante est: 9,11,1,2,3,4,5,6,7,8,9,10 = 9concat (11) concat (rev (8,7,6,5,4 , 3,2,1)) concat (10)

Code source J'inclus ici le code source tel que je l’imagine:

var nextPermutation = function(arr) {
  for (var i = arr.length - 2; i >= 0; i--) {
     if (arr[i] < arr[i + 1]) {
        return arr.slice(0, i).concat([arr[i + 1]]).concat(arr.slice(i + 2).reverse()).concat([arr[i]]);
     }
  }
  // return again the first permutation if calling next permutation on last.
  return arr.reverse();
}

console.log(nextPermutation([9, 10, 11, 8, 7, 6, 5, 4, 3, 2, 1]));
console.log(nextPermutation([6, 5, 4, 3, 2, 1]));
console.log(nextPermutation([1, 2, 3, 4, 5, 6]));

Le code est disponible pour jsfiddle ici .

5
Boris Strandjev

Voici un extrait d’une approche que j’ai moi-même proposée mais que j’ai aussi naturellement pu trouver. décrite ailleurs :

generatePermutations = function(arr) {
  if (arr.length < 2) {
    return arr.slice();
  }
  var factorial = [1];
  for (var i = 1; i <= arr.length; i++) {
    factorial.Push(factorial[factorial.length - 1] * i);
  }

  var allPerms = [];
  for (var permNumber = 0; permNumber < factorial[factorial.length - 1]; permNumber++) {
    var unused = arr.slice();
    var nextPerm = [];
    while (unused.length) {
      var nextIndex = Math.floor((permNumber % factorial[unused.length]) / factorial[unused.length - 1]);
      nextPerm.Push(unused[nextIndex]);
      unused.splice(nextIndex, 1);
    }
    allPerms.Push(nextPerm);
  }
  return allPerms;
};
Enter comma-separated string (e.g. a,b,c):
<br/>
<input id="arrInput" type="text" />
<br/>
<button onclick="perms.innerHTML = generatePermutations(arrInput.value.split(',')).join('<br/>')">
  Generate permutations
</button>
<br/>
<div id="perms"></div>

Explication

Puisqu'il y a des permutations factorial(arr.length) pour un tableau donné arr, chaque nombre compris entre 0 et factorial(arr.length)-1 code une permutation particulière. Pour dé-coder un numéro de permutation, supprimez à plusieurs reprises des éléments de arr jusqu'à ce qu'il ne reste plus d'éléments. L'index exact de l'élément à supprimer est donné par la formule (permNumber % factorial(arr.length)) / factorial(arr.length-1). D'autres formules pourraient être utilisées pour déterminer l'index à supprimer, tant qu'il conserve le mappage univoque entre nombre et permutation.

Exemple

Voici comment toutes les permutations seraient générées pour le tableau (a,b,c,d):

#    Perm      1st El        2nd El      3rd El    4th El
0    abcd   (a,b,c,d)[0]   (b,c,d)[0]   (c,d)[0]   (d)[0]
1    abdc   (a,b,c,d)[0]   (b,c,d)[0]   (c,d)[1]   (c)[0]
2    acbd   (a,b,c,d)[0]   (b,c,d)[1]   (b,d)[0]   (d)[0]
3    acdb   (a,b,c,d)[0]   (b,c,d)[1]   (b,d)[1]   (b)[0]
4    adbc   (a,b,c,d)[0]   (b,c,d)[2]   (b,c)[0]   (c)[0]
5    adcb   (a,b,c,d)[0]   (b,c,d)[2]   (b,c)[1]   (b)[0]
6    bacd   (a,b,c,d)[1]   (a,c,d)[0]   (c,d)[0]   (d)[0]
7    badc   (a,b,c,d)[1]   (a,c,d)[0]   (c,d)[1]   (c)[0]
8    bcad   (a,b,c,d)[1]   (a,c,d)[1]   (a,d)[0]   (d)[0]
9    bcda   (a,b,c,d)[1]   (a,c,d)[1]   (a,d)[1]   (a)[0]
10   bdac   (a,b,c,d)[1]   (a,c,d)[2]   (a,c)[0]   (c)[0]
11   bdca   (a,b,c,d)[1]   (a,c,d)[2]   (a,c)[1]   (a)[0]
12   cabd   (a,b,c,d)[2]   (a,b,d)[0]   (b,d)[0]   (d)[0]
13   cadb   (a,b,c,d)[2]   (a,b,d)[0]   (b,d)[1]   (b)[0]
14   cbad   (a,b,c,d)[2]   (a,b,d)[1]   (a,d)[0]   (d)[0]
15   cbda   (a,b,c,d)[2]   (a,b,d)[1]   (a,d)[1]   (a)[0]
16   cdab   (a,b,c,d)[2]   (a,b,d)[2]   (a,b)[0]   (b)[0]
17   cdba   (a,b,c,d)[2]   (a,b,d)[2]   (a,b)[1]   (a)[0]
18   dabc   (a,b,c,d)[3]   (a,b,c)[0]   (b,c)[0]   (c)[0]
19   dacb   (a,b,c,d)[3]   (a,b,c)[0]   (b,c)[1]   (b)[0]
20   dbac   (a,b,c,d)[3]   (a,b,c)[1]   (a,c)[0]   (c)[0]
21   dbca   (a,b,c,d)[3]   (a,b,c)[1]   (a,c)[1]   (a)[0]
22   dcab   (a,b,c,d)[3]   (a,b,c)[2]   (a,b)[0]   (b)[0]
23   dcba   (a,b,c,d)[3]   (a,b,c)[2]   (a,b)[1]   (a)[0]

Notez que chaque permutation # est de la forme:

(firstElIndex * 3!) + (secondElIndex * 2!) + (thirdElIndex * 1!) + (fourthElIndex * 0!)

ce qui est fondamentalement le processus inverse de la formule donnée dans l'explication.

5
heenenee

Un code C++ assez simple sans récursivité.

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <string>

// Integer data
void print_all_permutations(std::vector<int> &data) {
    std::stable_sort(std::begin(data), std::end(data));
    do {
        std::copy(data.begin(), data.end(), std::ostream_iterator<int>(std::cout, " ")), std::cout << '\n';
    } while (std::next_permutation(std::begin(data), std::end(data)));
}

// Character data (string)
void print_all_permutations(std::string &data) {
    std::stable_sort(std::begin(data), std::end(data));
    do {
        std::copy(data.begin(), data.end(), std::ostream_iterator<char>(std::cout, " ")), std::cout << '\n';
    } while (std::next_permutation(std::begin(data), std::end(data)));
}

int main()
{
    std::vector<int> v({1,2,3,4});
    print_all_permutations(v);

    std::string s("abcd");
    print_all_permutations(s);

    return 0;
}

Nous pouvons trouver la prochaine permutation d'une séquence en temps linéaire.

3
Venki

Voici une réponse de @le_m . Cela pourrait être utile.

L'algorithme très efficace suivant utilise la méthode de Heap pour générer toutes les permutations de N éléments avec une complexité d'exécution de O (N!):

function permute(permutation) {
  var length = permutation.length,
      result = [permutation.slice()],
      c = new Array(length).fill(0),
      i = 1, k, p;

  while (i < length) {
    if (c[i] < i) {
      k = i % 2 && c[i];
      p = permutation[i];
      permutation[i] = permutation[k];
      permutation[k] = p;
      ++c[i];
      i = 1;
      result.Push(permutation.slice());
    } else {
      c[i] = 0;
      ++i;
    }
  }
  return result;
}

console.log(JSON.stringify(permute([1, 2, 3, 4])));

0
jo_va