J'ai un tableau comme celui-ci:
var arr1 = ["a", "b", "c", "d"];
Comment puis-je le randomiser / le mélanger?
J'ai un tableau comme celui-ci:
var arr1 = ["a", "b", "c", "d"];
Comment puis-je le randomiser / le mélanger?
Réponses:
L'algorithme de shuffle de facto non biaisé est le shuffle de Fisher-Yates (alias Knuth).
Voir https://github.com/coolaj86/knuth-shuffle
Vous pouvez voir une grande visualisation ici (et le post original lié à cela )
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
// Used like so
var arr = [2, 11, 37, 42];
shuffle(arr);
console.log(arr);
Quelques informations supplémentaires sur l'algorithme utilisé.
i--
pas l' être --i
. En outre, le test if (i==0)...
est superflu car si i == 0
l' en boucle ne sera jamais entré. L'appel à Math.floor
peut être fait plus rapidement en utilisant ...| 0
. Soit tempi ou tempj peuvent être supprimés et la valeur directement affectée à myArray [i] ou j selon le cas.
0 !== currentIndex
).
Voici une implémentation JavaScript du shuffle Durstenfeld , une version optimisée de Fisher-Yates:
/* Randomize array in-place using Durstenfeld shuffle algorithm */
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
Il sélectionne un élément aléatoire pour chaque élément du tableau d'origine et l'exclut du prochain tirage, comme la sélection aléatoire d'un jeu de cartes.
Cette exclusion intelligente échange l'élément sélectionné avec l'élément actuel, puis sélectionne l'élément aléatoire suivant dans le reste, en boucle vers l'arrière pour une efficacité optimale, en garantissant que la sélection aléatoire est simplifiée (elle peut toujours commencer à 0), et ainsi sauter l'élément final.
Le temps d'exécution de l'algorithme est O(n)
. Notez que le shuffle est fait sur place, donc si vous ne voulez pas modifier le tableau d'origine, faites-en d'abord une copie avec .slice(0)
.
Le nouveau ES6 nous permet d'affecter deux variables à la fois. C'est particulièrement pratique lorsque nous voulons échanger les valeurs de deux variables, car nous pouvons le faire sur une seule ligne de code. Voici une forme plus courte de la même fonction, en utilisant cette fonction.
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
return array
car JavaScript transmet les tableaux par référence lorsqu'ils sont utilisés comme arguments de fonction. Je suppose que c'est pour économiser de l'espace sur la pile, mais c'est une petite fonctionnalité intéressante. L'exécution du shuffle sur la baie va mélanger la baie d'origine.
Math.random() should not be multiplied with the loop counter + 1, but with
array.lengt () `. Voir Générer des nombres entiers aléatoires en JavaScript dans une plage spécifique? pour une explication très complète.
Attention!
L'utilisation de cet algorithme n'est pas recommandée , car il est inefficace et fortement biaisé ; voir les commentaires. Il est laissé ici pour référence future, car l'idée n'est pas si rare.
[1,2,3,4,5,6].sort(function() {
return .5 - Math.random();
});
On pourrait (ou devrait) l'utiliser comme un prototype de Array:
De ChristopheD:
Array.prototype.shuffle = function() {
var i = this.length, j, temp;
if ( i == 0 ) return this;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return this;
}
for...in
boucles pour parcourir les tableaux.
Vous pouvez le faire facilement avec la carte et trier:
let unshuffled = ['hello', 'a', 't', 'q', 1, 2, 3, {cats: true}]
let shuffled = unshuffled
.map((a) => ({sort: Math.random(), value: a}))
.sort((a, b) => a.sort - b.sort)
.map((a) => a.value)
Vous pouvez mélanger les tableaux polymorphes et le tri est aussi aléatoire que Math.random, ce qui est assez bon pour la plupart des utilisations.
Comme les éléments sont triés en fonction de clés cohérentes qui ne sont pas régénérées à chaque itération et que chaque comparaison tire de la même distribution, tout caractère non aléatoire dans la distribution de Math.random est annulé.
La vitesse
La complexité temporelle est O (N log N), identique au tri rapide. La complexité de l'espace est O (N). Ce n'est pas aussi efficace qu'un shuffle Fischer Yates mais, à mon avis, le code est nettement plus court et plus fonctionnel. Si vous avez un grand tableau, vous devez certainement utiliser Fischer Yates. Si vous avez un petit tableau avec quelques centaines d'articles, vous pouvez le faire.
Utilisez la bibliothèque underscore.js. La méthode _.shuffle()
est bien pour ce cas. Voici un exemple avec la méthode:
var _ = require("underscore");
var arr = [1,2,3,4,5,6];
// Testing _.shuffle
var testShuffle = function () {
var indexOne = 0;
var stObj = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5
};
for (var i = 0; i < 1000; i++) {
arr = _.shuffle(arr);
indexOne = _.indexOf(arr, 1);
stObj[indexOne] ++;
}
console.log(stObj);
};
testShuffle();
shuffle
fonction.
NOUVEAU!
Algorithme de shuffle Fisher-Yates plus court et probablement * plus rapide
function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
c=a.length;while(c)b=Math.random()*(--c+1)|0,d=a[c],a[c]=a[b],a[b]=d
}
taille du script (avec fy comme nom de fonction): 90 octets
DEMO http://jsfiddle.net/vvpoma8w/
* plus rapide probablement sur tous les navigateurs sauf Chrome.
Si vous avez des questions, vous avez juste à me les poser.
ÉDITER
oui c'est plus rapide
PERFORMANCE: http://jsperf.com/fyshuffle
en utilisant les fonctions les plus votées.
EDIT Il y avait un calcul en excès (pas besoin de --c + 1) et personne n'a remarqué
plus court (4 octets) et plus rapide (testez-le!).
function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
c=a.length;while(c)b=Math.random()*c--|0,d=a[c],a[c]=a[b],a[b]=d
}
La mise en cache ailleurs var rnd=Math.random
, puis son utilisation rnd()
augmenteraient également légèrement les performances sur les grands tableaux.
http://jsfiddle.net/vvpoma8w/2/
Version lisible (utilisez la version originale. C'est plus lent, les variables sont inutiles, comme les fermetures & ";", le code lui-même est également plus court ... peut-être lire ceci Comment "minimiser" le code Javascript , btw vous ne pouvez pas compresser le code suivant dans un minifieur javascript comme celui ci-dessus.)
function fisherYates( array ){
var count = array.length,
randomnumber,
temp;
while( count ){
randomnumber = Math.random() * count-- | 0;
temp = array[count];
array[count] = array[randomnumber];
array[randomnumber] = temp
}
}
fy
et shuffle prototype
, j'obtiens fy
toujours le bas dans Chrome 37 sur OS X 10.9.5 (81% plus lent ~ 20k ops comparé à ~ 100k) et Safari 7.1 c'est jusqu'à ~ 8% plus lent. YMMV, mais ce n'est pas toujours plus rapide. jsperf.com/fyshuffle/3
Modifier: cette réponse est incorrecte
Voir les commentaires et https://stackoverflow.com/a/18650169/28234 . Il est laissé ici pour référence car l'idée n'est pas rare.
Un moyen très simple pour les petits tableaux est simplement le suivant:
const someArray = [1, 2, 3, 4, 5];
someArray.sort(() => Math.random() - 0.5);
Ce n'est probablement pas très efficace, mais pour les petits tableaux, cela fonctionne très bien. Voici un exemple pour que vous puissiez voir à quel point il est aléatoire (ou non) et s'il correspond à votre cas d'utilisation ou non.
const resultsEl = document.querySelector('#results');
const buttonEl = document.querySelector('#trigger');
const generateArrayAndRandomize = () => {
const someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
someArray.sort(() => Math.random() - 0.5);
return someArray;
};
const renderResultsToDom = (results, el) => {
el.innerHTML = results.join(' ');
};
buttonEl.addEventListener('click', () => renderResultsToDom(generateArrayAndRandomize(), resultsEl));
<h1>Randomize!</h1>
<button id="trigger">Generate</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>
Certaines solutions de cette page ne sont pas fiables (elles ne randomisent que partiellement le tableau). D'autres solutions sont nettement moins efficaces. Avec testShuffleArrayFun
(voir ci-dessous), nous pouvons tester les fonctions de réarrangement des baies pour la fiabilité et les performances. Les solutions suivantes sont: fiables, efficaces et courtes (utilisant la syntaxe ES6)
[Des tests de comparaison ont été effectués à l'aide d' testShuffleArrayFun
autres solutions, dans Google Chrome]
Shuffle Array en place
function getShuffledArr (array){
for (var i = array.length - 1; i > 0; i--) {
var rand = Math.floor(Math.random() * (i + 1));
[array[i], array[rand]] = [array[rand], array[i]]
}
}
ES6 pur, itératif
const getShuffledArr = arr => {
const newArr = arr.slice()
for (let i = newArr.length - 1; i > 0; i--) {
const rand = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
}
return newArr
};
Comme vous pouvez le voir sur cette page, des solutions incorrectes ont été proposées ici par le passé. J'ai écrit et j'ai utilisé la fonction suivante pour tester toutes les fonctions de randomisation de tableau pures (sans effets secondaires).
function testShuffleArrayFun(getShuffledArrayFun){
const arr = [0,1,2,3,4,5,6,7,8,9]
var countArr = arr.map(el=>{
return arr.map(
el=> 0
)
}) // For each possible position in the shuffledArr and for
// each possible value, we'll create a counter.
const t0 = performance.now()
const n = 1000000
for (var i=0 ; i<n ; i++){
// We'll call getShuffledArrayFun n times.
// And for each iteration, we'll increment the counter.
var shuffledArr = getShuffledArrayFun(arr)
shuffledArr.forEach(
(value,key)=>{countArr[key][value]++}
)
}
const t1 = performance.now()
console.log(`Count Values in position`)
console.table(countArr)
const frequencyArr = countArr.map( positionArr => (
positionArr.map(
count => count/n
)
))
console.log("Frequency of value in position")
console.table(frequencyArr)
console.log(`total time: ${t1-t0}`)
}
D'autres solutions juste pour le plaisir.
ES6 pur, récursif
const getShuffledArr = arr => {
if (arr.length === 1) {return arr};
const rand = Math.floor(Math.random() * arr.length);
return [arr[rand], ...getShuffledArr(arr.filter((_, i) => i != rand))];
};
ES6 Pure à l'aide de array.map
function getShuffledArr (arr){
return [...arr].map( (_, i, arrCopy) => {
var rand = i + ( Math.floor( Math.random() * (arrCopy.length - i) ) );
[arrCopy[rand], arrCopy[i]] = [arrCopy[i], arrCopy[rand]]
return arrCopy[i]
})
}
ES6 Pure à l'aide de array.reduce
function getShuffledArr (arr){
return arr.reduce(
(newArr, _, i) => {
var rand = i + ( Math.floor( Math.random() * (newArr.length - i) ) );
[newArr[rand], newArr[i]] = [newArr[i], newArr[rand]]
return newArr
}, [...arr]
)
}
[array[i], array[rand]]=[array[rand], array[i]]
? Vous pouvez peut-être expliquer comment cela fonctionne. Pourquoi choisissez-vous d'itérer vers le bas?
Ajout à la réponse de @Laurens Holsts. Ceci est compressé à 50%.
function shuffleArray(d) {
for (var c = d.length - 1; c > 0; c--) {
var b = Math.floor(Math.random() * (c + 1));
var a = d[c];
d[c] = d[b];
d[b] = a;
}
return d
};
var b =
dans une boucle au lieu de déclarer b en boucle extérieure et de l'affecter avec b =
dans une boucle?
Voir https://stackoverflow.com/a/18650169/28234 . Il est laissé ici pour référence car l'idée n'est pas rare.
//one line solution
shuffle = (array) => array.sort(() => Math.random() - 0.5);
//Demo
let arr = [1, 2, 3];
shuffle(arr);
alert(arr);
https://javascript.info/task/shuffle
Math.random() - 0.5
est un nombre aléatoire qui peut être positif ou négatif, de sorte que la fonction de tri réorganise les éléments de manière aléatoire.
Avec ES2015, vous pouvez utiliser celui-ci:
Array.prototype.shuffle = function() {
let m = this.length, i;
while (m) {
i = (Math.random() * m--) >>> 0;
[this[m], this[i]] = [this[i], this[m]]
}
return this;
}
Usage:
[1, 2, 3, 4, 5, 6, 7].shuffle();
n >>> 0
place de ~~n
. Les indices matriciels peuvent être supérieurs à 2³¹-1.
J'ai trouvé cette variante suspendue dans les réponses "supprimé par l'auteur" sur un double de cette question. Contrairement à certaines des autres réponses qui ont déjà beaucoup de votes positifs, c'est:
shuffled
nom plutôt que shuffle
)Voici un jsfiddle le montrant en cours d'utilisation .
Array.prototype.shuffled = function() {
return this.map(function(n){ return [Math.random(), n] })
.sort().map(function(n){ return n[1] });
}
[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });
- il ne donne pas un tri aléatoire, et si vous l'utilisez, vous pouvez vous retrouver gêné: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
.sort(function(a,b){ return a[0] - b[0]; })
si vous souhaitez que le tri compare les valeurs numériquement. Le .sort()
comparateur par défaut est lexicographique, ce qui signifie qu'il considérera 10
qu'il est inférieur à 2
puisqu'il 1
est inférieur à 2
.
Math.random()
produit. (c'est-à-dire que l'ordre lexicographique est le même que l'ordre numérique lorsqu'il s'agit de nombres de 0 (inclus) à 1 (exclusif))
var shuffle = function(array) {
temp = [];
originalLength = array.length;
for (var i = 0; i < originalLength; i++) {
temp.push(array.splice(Math.floor(Math.random()*array.length),1));
}
return temp;
};
arr1.sort(() => Math.random() - 0.5);
Vous pouvez le faire facilement avec:
// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);
Veuillez vous référer aux tableaux de tri JavaScript
Fisher-Yates shuffle en javascript. Je poste ceci ici parce que l'utilisation de deux fonctions utilitaires (swap et randInt) clarifie l'algorithme par rapport aux autres réponses ici.
function swap(arr, i, j) {
// swaps two elements of an array in place
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function randInt(max) {
// returns random integer between 0 and max-1 inclusive.
return Math.floor(Math.random()*max);
}
function shuffle(arr) {
// For each slot in the array (starting at the end),
// pick an element randomly from the unplaced elements and
// place it in the slot, exchanging places with the
// element in the slot.
for(var slot = arr.length - 1; slot > 0; slot--){
var element = randInt(slot+1);
swap(arr, element, slot);
}
}
Tout d'abord, jetez un œil ici pour une grande comparaison visuelle des différentes méthodes de tri en javascript.
Deuxièmement, si vous jetez un coup d'œil au lien ci-dessus, vous constaterez que le random order
tri semble fonctionner relativement bien par rapport aux autres méthodes, tout en étant extrêmement facile et rapide à mettre en œuvre comme indiqué ci-dessous:
function shuffle(array) {
var random = array.map(Math.random);
array.sort(function(a, b) {
return random[array.indexOf(a)] - random[array.indexOf(b)];
});
}
Edit : comme l'a souligné @gregers, la fonction de comparaison est appelée avec des valeurs plutôt qu'avec des indices, c'est pourquoi vous devez utiliser indexOf
. Notez que cette modification rend le code moins adapté aux tableaux plus grands car il indexOf
s'exécute en temps O (n).
Array.prototype.sort
passe en deux valeurs en tant que a
et b
, pas l'index. Donc, ce code ne fonctionne pas.
Mise à jour : ici, je suggère un algorithme relativement simple (pas du point de vue de la complexité ) et court qui fera très bien l'affaire avec des tableaux de petite taille, mais cela va certainement coûter beaucoup plus cher que l' algorithme Durstenfeld classique lorsque vous traitez avec des tableaux énormes. Vous pouvez trouver le Durstenfeld dans l'une des meilleures réponses à cette question.
Réponse originale:
Si vous ne souhaitez pas que votre fonction de lecture aléatoire mute le tableau source , vous pouvez le copier dans une variable locale, puis faire le reste avec une simple logique de lecture aléatoire .
function shuffle(array) {
var result = [], source = array.concat([]);
while (source.length) {
let index = Math.floor(Math.random() * source.length);
result.push(source[index]);
source.splice(index, 1);
}
return result;
}
Shuffling logic : prenez un index aléatoire, puis ajoutez l'élément correspondant au tableau de résultats et supprimez-le de la copie du tableau source . Répétez cette action jusqu'à ce que le tableau source soit vide .
Et si vous voulez vraiment que ce soit court, voici jusqu'où je pourrais aller:
function shuffle(array) {
var result = [], source = array.concat([]);
while (source.length) {
let index = Math.floor(Math.random() * source.length);
result.push(source.splice(index, 1)[0]);
}
return result;
}
splice
étant horriblement inefficace de faire ce qu'ils ont appelé "supprimer". Si vous ne voulez pas muter le tableau d'origine, copiez-le, puis mélangez cette copie en place en utilisant la variante Durstenfeld beaucoup plus efficace.
splice
méthode pour créer une copie comme ceci: source = array.slice();
.
Voici le PLUS FACILE ,
function shuffle(array) {
return array.sort(() => Math.random() - 0.5);
}
pour plus d'exemple, vous pouvez le vérifier ici
encore une autre implémentation de Fisher-Yates, en utilisant le mode strict:
function shuffleArray(a) {
"use strict";
var i, t, j;
for (i = a.length - 1; i > 0; i -= 1) {
t = a[i];
j = Math.floor(Math.random() * (i + 1));
a[i] = a[j];
a[j] = t;
}
return a;
}
Toutes les autres réponses sont basées sur Math.random () qui est rapide mais ne convient pas pour la randomisation au niveau cryptographique.
Le code ci-dessous utilise l' Fisher-Yates
algorithme bien connu tout en l'utilisant Web Cryptography API
pour le niveau cryptographique de randomisation .
var d = [1,2,3,4,5,6,7,8,9,10];
function shuffle(a) {
var x, t, r = new Uint32Array(1);
for (var i = 0, c = a.length - 1, m = a.length; i < c; i++, m--) {
crypto.getRandomValues(r);
x = Math.floor(r / 65536 / 65536 * m) + i;
t = a [i], a [i] = a [x], a [x] = t;
}
return a;
}
console.log(shuffle(d));
Une simple modification de la réponse de CoolAJ86 qui ne modifie pas le tableau d'origine:
/**
* Returns a new array whose contents are a shuffled copy of the original array.
* @param {Array} The items to shuffle.
* https://stackoverflow.com/a/2450976/1673761
* https://stackoverflow.com/a/44071316/1673761
*/
const shuffle = (array) => {
let currentIndex = array.length;
let temporaryValue;
let randomIndex;
const newArray = array.slice();
// While there remains elements to shuffle...
while (currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// Swap it with the current element.
temporaryValue = newArray[currentIndex];
newArray[currentIndex] = newArray[randomIndex];
newArray[randomIndex] = temporaryValue;
}
return newArray;
};
Bien qu'il existe un certain nombre d'implémentations déjà recommandées, mais je pense que nous pouvons le rendre plus court et plus facile à utiliser pour la boucle forEach, nous n'avons donc pas à nous soucier du calcul de la longueur du tableau et nous pouvons également éviter en toute sécurité d'utiliser une variable temporaire.
var myArr = ["a", "b", "c", "d"];
myArr.forEach((val, key) => {
randomIndex = Math.ceil(Math.random()*(key + 1));
myArr[key] = myArr[randomIndex];
myArr[randomIndex] = val;
});
// see the values
console.log('Shuffled Array: ', myArr)
Juste pour avoir un doigt dans la tarte. Ici, je présente une implémentation récursive du shuffle de Fisher Yates (je pense). Il donne un caractère aléatoire uniforme.
Remarque: L' ~~
opérateur (tilde double) se comporte en fait comme Math.floor()
pour les nombres réels positifs. C'est juste un raccourci.
var shuffle = a => a.length ? a.splice(~~(Math.random()*a.length),1).concat(shuffle(a))
: a;
console.log(JSON.stringify(shuffle([0,1,2,3,4,5,6,7,8,9])));
Edit: Le code ci-dessus est O (n ^ 2) en raison de l'emploi de .splice()
mais nous pouvons éliminer l'épissure et le shuffle dans O (n) par le truc de swap.
var shuffle = (a, l = a.length, r = ~~(Math.random()*l)) => l ? ([a[r],a[l-1]] = [a[l-1],a[r]], shuffle(a, l-1))
: a;
var arr = Array.from({length:3000}, (_,i) => i);
console.time("shuffle");
shuffle(arr);
console.timeEnd("shuffle");
Le problème est que JS ne peut pas coopérer avec de grosses récursions. Dans ce cas particulier, la taille de votre tableau est limitée à 3000 ~ 7000, selon le moteur de votre navigateur et certains faits inconnus.
Tableau aléatoire
var arr = ['apple','cat','Adam','123','Zorro','petunia'];
var n = arr.length; var tempArr = [];
for ( var i = 0; i < n-1; i++ ) {
// The following line removes one random element from arr
// and pushes it onto tempArr
tempArr.push(arr.splice(Math.floor(Math.random()*arr.length),1)[0]);
}
// Push the remaining item onto tempArr
tempArr.push(arr[0]);
arr=tempArr;
-1
for n comme vous <
ne l' avez pas utilisé<=
la arrayShuffle
fonction la plus courte
function arrayShuffle(o) {
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
}
D'un point de vue théorique, la façon la plus élégante de le faire, à mon humble avis, est d'obtenir un seul nombre aléatoire entre 0 et n! -1 et de calculer un mappage un à un de {0, 1, …, n!-1}
toutes les permutations de(0, 1, 2, …, n-1)
. Tant que vous pouvez utiliser un générateur (pseudo-) aléatoire suffisamment fiable pour obtenir un tel nombre sans biais significatif, vous disposez de suffisamment d'informations pour obtenir ce que vous voulez sans avoir besoin de plusieurs autres nombres aléatoires.
Lorsque vous calculez avec des nombres flottants double précision IEEE754, vous pouvez vous attendre à ce que votre générateur aléatoire fournisse environ 15 décimales. Étant donné que vous avez 15! = 1 307 674 368 000 (avec 13 chiffres), vous pouvez utiliser les fonctions suivantes avec des tableaux contenant jusqu'à 15 éléments et supposer qu'il n'y aura pas de biais significatif avec les tableaux contenant jusqu'à 14 éléments. Si vous travaillez sur un problème de taille fixe nécessitant de calculer plusieurs fois cette opération de lecture aléatoire, vous souhaiterez peut-être essayer le code suivant qui peut être plus rapide que les autres codes car il utiliseMath.random
qu'une seule fois (il implique cependant plusieurs opérations de copie).
La fonction suivante ne sera pas utilisée, mais je la donne quand même; il retourne l'index d'une permutation donnée (0, 1, 2, …, n-1)
selon le mappage un à un utilisé dans ce message (le plus naturel lors de l'énumération des permations); il est destiné à fonctionner avec jusqu'à 16 éléments:
function permIndex(p) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
var tail = [];
var i;
if (p.length == 0) return 0;
for(i=1;i<(p.length);i++) {
if (p[i] > p[0]) tail.push(p[i]-1);
else tail.push(p[i]);
}
return p[0] * fact[p.length-1] + permIndex(tail);
}
La réciproque de la fonction précédente (requise pour votre propre question) est ci-dessous; il est destiné à fonctionner avec jusqu'à 16 éléments; il renvoie la permutation d'ordre n de (0, 1, 2, …, s-1)
:
function permNth(n, s) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
var i, j;
var p = [];
var q = [];
for(i=0;i<s;i++) p.push(i);
for(i=s-1; i>=0; i--) {
j = Math.floor(n / fact[i]);
n -= j*fact[i];
q.push(p[j]);
for(;j<i;j++) p[j]=p[j+1];
}
return q;
}
Maintenant, ce que vous voulez, c'est simplement:
function shuffle(p) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000];
return permNth(Math.floor(Math.random()*fact[p.length]), p.length).map(
function(i) { return p[i]; });
}
Il devrait fonctionner jusqu'à 16 éléments avec un peu de biais théorique (bien que imperceptible d'un point de vue pratique); il peut être considéré comme pleinement utilisable pour 15 éléments; avec des tableaux contenant moins de 14 éléments, vous pouvez considérer en toute sécurité qu'il n'y aura absolument aucun biais.