128 votes

Est-ce correct d’utiliser la méthode JavaScript Array.sort() pour mixer ?

J'ai été d'aider quelqu'un avec son code JavaScript et mes yeux furent attirés par une section qui ressemblait à ça:

function randOrd(){
  return (Math.round(Math.random())-0.5);
}
coords.sort(randOrd);
alert(coords);

Mon premier était bien: hey, cela ne peut pas fonctionner! Mais ensuite, j'ai fait quelques tests et a constaté que en effet, elle au moins, semble bien randomisés résultats.

Puis j'ai fait quelques recherches sur le web et presque au sommet trouvé un article à partir de laquelle ce code a été plus ceartanly copié. Regardé comme une jolie respectable site et de l'auteur...

Mais mon instinct me dit que ce doit être faux. D'autant que l'algorithme de tri n'est pas spécifié par le standard ECMA. Je pense tri différentes algoritms entraînera la non-uniforme du mélange. Certains algorithmes de tri peut probablement même boucle à l'infini...

Mais qu'en pensez-vous?

Et une autre question... comment pourrais-je aller maintenant et de mesurer la façon aléatoire les résultats de ce brassage de la technique?

mise à jour: j'ai fait quelques mesures et affiché les résultats ci-dessous comme étant l'une des réponses.

118voto

Christoph Points 64389

Après que Jon a déjà couvert la théorie, voici une mise en œuvre:

function shuffle(array) {
    var tmp, current, top = array.length;

    if(top) while(--top) {
    	current = Math.floor(Math.random() * (top + 1));
    	tmp = array[current];
    	array[current] = array[top];
    	array[top] = tmp;
    }

    return array;
}

L'algorithme est - O(n), alors que le tri doit être O(n log n). En fonction de la surcharge de l'exécution de code JS par rapport aux natifs sort() de la fonction, cela pourrait conduire à une notable différence dans la performance, ce qui devrait augmenter avec la taille des matrices.


Dans les commentaires à bobobobo de réponse, j'ai dit que l'algorithme en question peut ne pas produire uniformément répartie probabilités (en fonction de la mise en œuvre de l' sort()).

Mon argument va dans ce sens: Un algorithme de tri nécessite un certain nombre c de comparaisons, par exemple, c = n(n-1)/2 pour Bubblesort. Au hasard de notre fonction de comparaison rend le résultat de chaque comparaison tout aussi probable, c'est à dire il y a 2^c également probables résultats. Maintenant, chaque résultat doit correspondre à l'une des n! permutations de la matrice des entrées, ce qui rend une même distribution de l'impossible dans le cas général. (C'est une simplification, comme le nombre de comparaisons neeeded dépend du tableau d'entrée, mais l'affirmation devrait encore tenir.)

Comme Jon l'a souligné, cela seul n'est pas une raison pour préférer de Fisher-Yates sur l'aide d' sort(), comme le générateur de nombre aléatoires carte en un nombre fini de nombres pseudo-aléatoires les valeurs de l' n! permutations. Mais les résultats de Fisher-Yates devrait être encore mieux:

Math.random() génère un nombre pseudo-aléatoire dans l'intervalle [0;1[. Comme JS utilise la virgule flottante en double précision des valeurs, ce qui correspond à 2^x valeurs possibles où 52 ≤ x ≤ 63 (je suis trop paresseux pour trouver le nombre). Une distribution de probabilité généré à l'aide d' Math.random() va cesser de se comporter ainsi, si le nombre d'événements atomiques est du même ordre de grandeur.

Lors de l'utilisation de Fisher-Yates, le paramètre pertinent est la taille de la matrice, ce qui ne doit jamais s'approcher 2^52 en raison de limitations pratiques.

Lors du tri avec un nombre aléatoire de comparaison de la fonction, la fonction fondamentalement importe seulement si la valeur de retour est positif ou négatif, de sorte que ce ne sera jamais un problème. Mais il y a un similaire: Parce que la fonction de comparaison est bien comporté, l' 2^c résultats possibles sont, comme indiqué, tout aussi probable. Si c ~ n log n alors 2^c ~ n^(a·n)a = const, ce qui le rend au moins possible qu' 2^c est de même ordre de grandeur que (ou même moins) n! et conduisant ainsi à une distribution inégale, même si l'algorithme de tri où la carte sur le permutaions uniformément. Si cela a un impact au-delà de moi.

Le vrai problème est que les algorithmes de tri ne sont pas garanties à la carte sur les permutations de manière homogène. Il est facile de voir que Mergesort fait comme il est symétrique, mais le raisonnement à propos de quelque chose comme Bubblesort ou, plus important encore, Quicksort ou Heapsort, ne l'est pas.


La ligne du bas: tant Que sort() utilise Mergesort, vous devriez être raisonnablement sûr, sauf en cas de coin (au moins j'espère qu' 2^c ≤ n! est un coin de cas), si ce n'est, tous les paris sont éteints.

110voto

Jon Skeet Points 692016

Il n'a jamais été ma façon préférée de brassage, en partie parce qu'il est spécifique à l'implémentation comme vous le dites. En particulier, il me semble me rappeler que la bibliothèque standard de tri de Java ou de .NET (pas sûr) peut souvent détecter si vous vous retrouvez avec une comparaison incohérents entre certains éléments (par exemple, de la première réclamation A < B et B < C, mais ensuite, C < A).

Elle aussi finit plus complexe (en termes de temps d'exécution) shuffle que vous avez vraiment besoin.

Je préfère la lecture aléatoire d'un algorithme efficace de partitions de la collection "mélangés" (au début de la collection, initialement vide) et "unshuffled" (le reste de la collection). À chaque étape de l'algorithme, choisir aléatoirement unshuffled élément (qui pourrait être le premier) et il échange avec le premier unshuffled élément - puis le traiter comme mélangées (c'est à dire mentalement déplacer la partition de l'inclure).

Ce est O(n) et ne nécessite que n-1 appels pour le générateur de nombre aléatoire, ce qui est agréable. Il a également produit une véritable aléatoire - pour tout élément a un 1/n de risque de se retrouver dans chaque espace, quelle que soit sa position d'origine (en supposant que raisonnable RNG). La version triée se rapproche d'une distribution uniforme (en supposant que le générateur de nombre aléatoire n'est pas choisir la même valeur à deux reprises, ce qui est hautement improbable si elle retourne aléatoire en double) mais je trouve plus facile de raisonner sur le shuffle version :)

Cette approche est appelée un shuffle de Fisher-Yates.

- Je ne serait-il considérer comme les meilleures pratiques de code jusqu'à ce shuffle, puis les réutiliser partout où vous avez besoin de mélanger les éléments. Ensuite, vous n'avez pas besoin de vous soucier de tri mises en œuvre dans les conditions de la fiabilité ou de la complexité. C'est seulement quelques lignes de code (je ne vais pas tenter en JavaScript!)

L' article de Wikipedia sur le brassage (et en particulier les algorithmes de la section) parle d'un tri aléatoire projection - il est intéressant de lire la section sur les pauvres implémentations de brassage en général, de sorte que vous savez ce qu'il faut éviter.

16voto

Rene Saarsoo Points 6144

J'ai fait quelques mesures de façon aléatoire les résultats de cette aléatoires de tri sont...

Ma technique a été de prendre un petit tableau [1,2,3,4] et de créer tous les (4! = 24) permutations. Puis j'applique le brassage de la fonction de la matrice d'un grand nombre de fois et compter combien de fois chaque permutation est généré. Un bon brassage algorithme permettrait de distribuer les résultats assez uniformément dans toutes les permutations, tandis qu'un mauvais on ne pourrait pas créer uniforme.

À l'aide du code ci-dessous je l'ai testé dans Firefox, Opera, Chrome, IE6/7/8.

Étonnamment pour moi, le hasard de tri et le réel shuffle à la fois créé également des distributions uniformes. Il semble donc que (comme beaucoup l'ont suggéré) les principaux navigateurs sont à l'aide de fusion de tri. Bien sûr, cela ne veut pas dire, qu'il ne peut pas être un browser, qui ne différemment, mais je dirais que cela signifie, que ce hasard-tri-méthode est suffisamment fiable pour une utilisation dans la pratique.

EDIT: Ce test n'a pas vraiment mesuré correctement le caractère aléatoire ou l'absence de celui-ci. Voir la réponse que j'ai posté.

Mais du côté de la performance de la fonction de lecture aléatoire donné par Cristoph était un gagnant clair. Même pour les petits à quatre éléments des matrices de la vraie shuffle effectuées deux fois plus rapide que le hasard de tri!

// La fonction de lecture aléatoire posté par Cristoph.
var shuffle = fonction(tableau) {
 var tmp, actuel, top = array.longueur;

 si(en haut) while(--haut de page) {
 actuel = Math.floor(Math.random() * (haut + 1));
 tmp = tableau[courant];
 tableau[en cours] = tableau[haut];
 tableau[haut] = tmp;
}

 return tableau;
};

// l'aléatoire de fonction de tri
var rnd = function() {
 de retour en Mathématiques.round(Math.random())-0.5;
};
var randSort = function(A) {
 de retour A. sort(rnd);
};

var permutations = function(A) {
 si (A. length == 1) {
 de retour [d'Un];
}
 else {
 var perms = [];
 for (var i=0; i<A. length; i++) {
 var x = A. tranche(i, i+1);
 var xs = A. slice(0, i).concat(A. tranche(i+1));
 var subperms = permutations(xs);
 for (var j=0; j<subperms.longueur; j++) {
perms.push(x.concat(subperms[j]));
}
}
 retour permanentes;
}
};

var test = function(A, itérations, func) {
 // initialisation des permutations
 var stats = {};
 var perms = permutations(A);
 for (var i dans perms){
 stats[""+prems[i]] = 0;
}

 // lecture aléatoire de nombreuses fois et de recueillir des statistiques
 var start=new Date();
 for (var i=0; i<itérations; i++) {
 var mélangées = func ();
stats[""+mélangées]++;
}
 var fin=new Date();

 // format de résultat
 var arr=[];
 for (var i dans les stats) {
 arr.push(i+" "+stats[i]);
}
 retour arr.join("\n")+"\n\nTime prise: "+ ((fin - début)/1000) + " secondes.";
};

alert("random sorte:" + test([1,2,3,4], 100000, randSort));
alert("shuffle:" + test([1,2,3,4], 100000, shuffle));

11voto

Rene Saarsoo Points 6144

Il est intéressant de noter, Microsoft a utilisé la même technique dans leur pick-aléatoire-navigateur-page.

Ils ont utilisé un peu différent en fonction de comparaison:

function RandomSort(a,b) {
    return (0.5 - Math.random());
}

Ressemble presque la même chose pour moi, mais il s'est avéré être pas tellement aléatoire...

J'ai donc fait quelques lancement pour test de nouveau avec la même méthode utilisée dans l'article lié, et, en effet, avéré que le hasard-tri-méthode produit des résultats inexacts. Nouveau test de code ici:

function shuffle(arr) {
  arr.sort(function(a,b) {
    return (0.5 - Math.random());
  });
}

function shuffle2(arr) {
  arr.sort(function(a,b) {
    return (Math.round(Math.random())-0.5);
  });
}

function shuffle3(array) {
  var tmp, current, top = array.length;

  if(top) while(--top) {
    current = Math.floor(Math.random() * (top + 1));
    tmp = array[current];
    array[current] = array[top];
    array[top] = tmp;
  }

  return array;
}

var counts = [
  [0,0,0,0,0],
  [0,0,0,0,0],
  [0,0,0,0,0],
  [0,0,0,0,0],
  [0,0,0,0,0]
];

var arr;
for (var i=0; i<100000; i++) {
  arr = [0,1,2,3,4];
  shuffle3(arr);
  arr.forEach(function(x, i){ counts[x][i]++;});
}

alert(counts.map(function(a){return a.join(", ");}).join("\n"));

9voto

Phrogz Points 112337

J'ai placé une simple page de test sur mon site web montrant le biais de votre navigateur actuel par rapport à d'autres navigateurs populaires à l'aide de différentes méthodes de lecture aléatoire. Il montre la terrible biais de simplement en utilisant Math.random()-0.5, un autre "aléatoire" shuffle qui n'est pas biaisée, et le Fisher-Yates méthode mentionnée ci-dessus.

Vous pouvez voir que sur certains navigateurs, il est aussi élevé que 50% de chances que certains éléments ne seront pas changer de place pendant le "shuffle"!

Remarque: vous pouvez faire de la mise en œuvre de l'shuffle de Fisher-Yates par @Christoph légèrement plus rapide pour Safari, en changeant le code:

function shuffle(array) {
  for (var tmp, cur, top=array.length; top--;){
    cur = (Math.random() * (top + 1)) << 0;
    tmp = array[cur]; array[cur] = array[top]; array[top] = tmp;
  }
  return array;
}

Résultats du Test: http://jsperf.com/optimized-fisher-yates

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X