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 index
0
de la "colonne" adjacente.
Dans la colonne suivante, 2
serait placé à index
2
, 3
serait placé à index
0
. 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
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
Recherche de la nième permutation lexicographique de «0123456789»
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:
f = len!
, il y a factorial(len)
totales permutations d'un ensemble de len
différents éléments. (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.Cet algorithme est très simple et possède des propriétés intéressantes:
0
correspond à l'ensemble dans l'ordre indiqué.factorial(a.length)-1
est le dernier: l'ensemble a
dans l'ordre inverse.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)
.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;
}
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;
}
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'])));
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 />");
}
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:
i
et j
où i
<j
et perm[i]
> perm[j]
, indices donnés de gauche à droite).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 .
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.
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.
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])));