Comment randomiser (mélanger) un tableau JavaScript?

1266

J'ai un tableau comme celui-ci:

var arr1 = ["a", "b", "c", "d"];

Comment puis-je le randomiser / le mélanger?

Cliquez Upvote
la source
6
Juste en lançant ceci ici, vous pouvez visualiser le caractère aléatoire d'une fonction de lecture
août
5
@Blazemonger jsPref est mort. Pouvez-vous simplement poster ici qui est le plus rapide?
eozzy
Qu'en est-il d'une doublure? Le tableau renvoyé est mélangé. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan
La solution réduite a une complexité O (n ^ 2). Essayez de l'exécuter sur un tableau avec un million d'éléments.
riv

Réponses:

1542

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é.

CoolAJ86
la source
13
La réponse ci-dessus saute l'élément 0, la condition ne devrait i--pas l' être --i. En outre, le test if (i==0)...est superflu car si i == 0l' en boucle ne sera jamais entré. L'appel à Math.floorpeut ê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.
RobG
23
@prometheus, tous les RNG sont pseudo-aléatoires sauf s'ils sont connectés à un matériel coûteux.
Phil H
38
@RobG l'implémentation ci-dessus est fonctionnellement correcte. Dans l'algorithme Fisher-Yates, la boucle n'est pas censée s'exécuter pour le premier élément du tableau. Consultez wikipedia où il existe d'autres implémentations qui ignorent également le premier élément. Consultez également cet article qui explique pourquoi il est important que la boucle ne s'exécute pas pour le premier élément.
Théon
34
@nikola "pas aléatoire du tout" est une qualification un peu forte pour moi. Je dirais qu'il est suffisamment aléatoire à moins que vous ne soyez un cryptographe, auquel cas vous n'utilisez probablement pas Math.Random () en premier lieu.
toon81
20
Ugh, yoda ( 0 !== currentIndex).
ffxsam
746

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).


ÉDITER: Mise à jour vers ES6 / ECMAScript 2015

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]];
    }
}
Laurens Holst
la source
22
ps Le même algorithme que la réponse de ChristopheD, mais avec explication et implémentation plus propre.
Laurens Holst
12
Les gens attribuent la mauvaise personne à l'algorithme. Ce n'est pas le shuffle de Fisher-Yates mais le shuffle de Durstenfeld . Le véritable algorithme original de Fisher-Yates s'exécute en temps n ^ 2, pas en temps n
Pacerier
7
Cela n'est pas obligatoire return arraycar 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.
Joel Trauger
5
L'implémentation de cette réponse favorise l'extrémité inférieure du tableau. Découvert à la dure . 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.
Marjan Venema
13
@MarjanVenema Je ne sais pas si vous regardez toujours cet espace, mais cette réponse est correcte, et le changement que vous proposez introduit en fait un parti pris. Voir blog.codinghorror.com/the-danger-of-naivete pour un bon résumé de cette erreur.
user94559
134

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();
});
impasse
la source
13
j'aime cette solution, assez pour donner un aléatoire de base
Alex K
147
Downvoter car ce n'est pas vraiment si aléatoire. Je ne sais pas pourquoi il y a tant de votes positifs. N'utilisez pas cette méthode. Il semble joli, mais n'est pas complètement correct. Voici les résultats après 10 000 itérations sur le nombre de fois que chaque numéro de votre tableau atteint l'indice [0] (je peux aussi donner les autres résultats): 1 = 29,19%, 2 = 29,53%, 3 = 20,06%, 4 = 11,91%, 5 = 5,99%, 6 = 3,32%
radtad
8
C'est bien si vous avez besoin de randomiser un tableau relativement petit et de ne pas traiter de choses cryptographiques. Je suis totalement d'accord pour dire que si vous avez besoin de plus de hasard, vous devez utiliser une solution plus complexe.
Deadrunk
21
C'est aussi la moins efficace de toutes les méthodes disponibles .
Blazemonger
12
Le problème est qu'il n'est pas déterministe, ce qui donnera des résultats erronés (si 1> 2 et 2> 3, il faut lui donner 1> 3, mais cela ne le garantira pas. Cela confondra le tri et donnera le résultat commenté) par @radtad).
MatsLindh
73

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;
}
con
la source
42
Vraiment aucun avantage à cela, à mon humble avis, sauf peut-être piétiner sur la mise en œuvre de quelqu'un d'autre ..
user2864740
2
S'il est utilisé dans le prototype Array, il doit être nommé autrement que simplement shuffle .
Wolf
57
On pourrait (ou devrait) éviter d'étendre les prototypes natifs: javascriptweblog.wordpress.com/2011/12/05/…
Wédney Yuri
12
Vous ne devriez pas faire ça; chaque baie affectée par cela ne peut plus être itérée en toute sécurité en utilisant for ... in. N'étendez pas les prototypes natifs.
18
@TinyGiant En fait: n'utilisez pas de for...inboucles pour parcourir les tableaux.
Conor O'Brien
69

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)
  1. Nous mettons chaque élément du tableau dans un objet et lui donnons une clé de tri aléatoire
  2. Nous trions en utilisant la clé aléatoire
  3. Nous démappons pour obtenir les objets originaux

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.

superluminaire
la source
1
@superluminary Oups, vous avez raison. Notez que cette réponse a déjà utilisé la même approche.
Bergi
@Bergi - Ah oui, vous avez raison, même si je pense que mon implémentation est un peu plus jolie.
superluminaire
3
Très agréable. Il s'agit de la transformation schwartzienne en js.
Mark Grimes
@torazaburo - Ce n'est pas aussi performant que Fischer Yates, mais c'est plus joli et le code est plus petit. Le code est toujours un compromis. Si j'avais un grand tableau, j'utiliserais Knuth. Si j'avais quelques centaines d'articles, je le ferais.
superluminaire
1
@BenCarp - D'accord, ce n'est pas la solution la plus rapide et vous ne voudriez pas l'utiliser sur un tableau massif, mais il y a plus de considérations dans le code que la vitesse brute.
superluminaire
64

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();
vn_grv
la source
12
Très bonne réponse! Merci. Je le préfère aux autres réponses, car il encourage les gens à utiliser des bibliothèques plutôt que de copier et coller des fonctions potentiellement boguées partout.
frabcus
60
@frabcus: Inutile d'inclure une bibliothèque entière juste pour obtenir une shufflefonction.
Blender
11
Je ne suis pas d'accord avec @Blender. Il existe de nombreuses raisons d'inclure une bibliothèque entière juste pour obtenir une fonction dont vous avez besoin. L'un d'eux est qu'il y a moins de risque de bogue lorsque vous l'écrivez vous-même. S'il s'agit d'un problème de performances, vous ne devez pas l'utiliser. Mais ce n'est pas parce qu'il pourrait s'agir d'un problème de performances qu'il le sera.
Daniel Kaplan
7
@tieTYT: Alors pourquoi avez-vous besoin du reste de la bibliothèque? Le shuffle de Fisher-Yates est trivial à mettre en œuvre. Vous n'avez pas besoin d'une bibliothèque pour choisir un élément aléatoire dans un tableau (j'espère), donc il n'y a aucune raison d'utiliser une bibliothèque à moins que vous n'utilisiez réellement plus d'une fonction.
Blender
18
@Blender: J'ai donné une raison. 1) Je vous assure que vous pouvez introduire un bogue dans n'importe quel code que vous écrivez, aussi banal soit-il. Pourquoi le risquer? 2) Ne pré-optimisez pas. 3) 99% du temps lorsque vous avez besoin d'un algo shuffle, votre application ne consiste pas à écrire un algo shuffle. Il s'agit de quelque chose qui a besoin d' un remaniement. Tirez parti du travail des autres. Ne pensez pas aux détails de l'implémentation, sauf si vous le devez.
Daniel Kaplan
50

NOUVEAU!

Algorithme de shuffle Fisher-Yates plus court et probablement * plus rapide

  1. il utilise tout ---
  2. bit à étage (nombres jusqu'à 10 chiffres décimaux (32 bits))
  3. supprimé les fermetures inutiles et autres choses

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
 }
}
cocco
la source
6
vérifier les performances ... 2x plus rapide sur la plupart des navigateurs ... mais a besoin de plus de testeurs jsperf ...
cocco
10
js est un langage qui accepte de nombreux raccourcis et différentes façons de l'écrire .. alors qu'il y a beaucoup de fonctions lisibles ici, j'aime juste montrer comment cela pourrait être fait de manière plus performante, en économisant également quelques octets ... au niveau du bit et la sténographie est vraiment sous-estimée ici et le Web est plein de bogues et de code lent.
cocco
Pas une augmentation de performance de slam dunk. En échangeant le fyet shuffle prototype, j'obtiens fytoujours 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
Spig
vérifiez à nouveau les statistiques ... j'ai déjà écrit que le chrome est plus lent parce qu'ils ont optimisé les mathématiques, sur tous les autres au niveau du bit et est plus rapide. vérifiez IE, firefox mais aussi les appareils mobiles. Ce serait bien aussi de voir l'opéra ...
cocco
1
C'est une réponse terrible. SO n'est pas une compétition d'obscurcissement.
Puppy
39

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>

Kris Selbekk
la source
Bien, mais génère-t-il un élément aléatoire complet à chaque fois?
DDD du
Je ne sais pas si je vous ai bien compris. Cette approche va en effet mélanger le tableau de manière aléatoire (quoique pseudo-aléatoire) chaque fois que vous appelez le tableau de tri - ce n'est pas un tri stable, pour des raisons évidentes.
Kris Selbekk
4
Pour les mêmes raisons que celles expliquées sur stackoverflow.com/a/18650169/28234 . Cela est beaucoup plus susceptible de laisser des éléments précoces près du début du tableau.
AlexC
7
C'est une excellente ligne simple pour quand vous devez brouiller un tableau, mais ne vous souciez pas trop que les résultats soient académiquement prouvés au hasard. Parfois, ces derniers centimètres à la perfection prennent plus de temps qu'il n'en vaut la peine.
Daniel Griscom
1
Ce serait bien si cela fonctionnait, mais ce n'est pas le cas. En raison du fonctionnement de la recherche rapide, un comparateur incohérent laissera probablement les éléments du tableau près de leur position d'origine. Votre tableau ne sera pas brouillé.
superluminaire
39

Fiable, efficace, court

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' testShuffleArrayFunautres 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
    };

Test de fiabilité et de performance

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}`)
    }

Autres solutions

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]
        )
    }
Ben Carp
la source
Alors, où est l'ES6 (ES2015)? [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?
sheriffderek du
@sheriffderek Oui, la fonctionnalité ES6 que j'utilise est l'affectation de deux vars à la fois, ce qui nous permet d'échanger deux vars en une seule ligne de code.
Ben Carp
Nous remercions @sheriffderek qui a suggéré l'algorithme ascendant. L'algorithme ascendant a pu être prouvé par induction.
Ben Carp du
23

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
};
KingKongFrog
la source
3
Nous devrions encourager les gens à utiliser _.shuffle plutôt que de coller du code à partir d'un débordement de pile; et, nous devrions décourager les gens de compresser leurs réponses de débordement de pile. C'est à ça que sert jsmin.
David Jones
45
@DavidJones: Pourquoi devrais-je inclure une bibliothèque entière de 4 Ko juste pour mélanger un tableau?
Blender
1
L'appel de nom @KingKongFrog n'est pas non plus propice à un assemblage d'une communauté raisonnable.
fromages
2
est-il efficace de faire var b = dans une boucle au lieu de déclarer b en boucle extérieure et de l'affecter avec b = dans une boucle?
Alex K
2
@Brian ne fera aucune différence; le levage se produit lorsque le code source est analysé. Non probablement impliqué.
user2864740
23

Modifier: cette réponse est incorrecte

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.

hakiko
la source
17

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();
BrunoLM
la source
4
Pour tronquer, vous devez utiliser à la n >>> 0place de ~~n. Les indices matriciels peuvent être supérieurs à 2³¹-1.
Oriol
1
Une telle destruction permet une implémentation aussi propre +1
lukejacksonn
14

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:

  1. En fait aléatoire
  2. Pas en place (d'où le shufflednom plutôt que shuffle)
  3. Pas déjà présent ici avec plusieurs variantes

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] });
}
Daniel Martin
la source
(Je soupçonne qu'il a été supprimé car c'est un moyen très inefficace de randomiser le tableau, en particulier pour les tableaux plus grands ... alors que la réponse acceptée, et un certain nombre d'autres clones de cette réponse randomisent en place).
WiredPrairie
1
Oui, mais étant donné que la mauvaise réponse bien connue est toujours avec un tas de votes, une solution inefficace mais correcte devrait au moins être mentionnée.
Daniel Martin
[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
Daniel Martin
3
Vous devez utiliser .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 10qu'il est inférieur à 2puisqu'il 1est inférieur à 2.
4castle
@ 4castle D'accord, j'ai mis à jour le code, mais je vais y revenir: la distinction entre l'ordre lexicographique et l'ordre numérique n'a pas d'importance pour les nombres dans la plage qui 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))
Daniel Martin
14
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;
};
Tophe
la source
Ce n'est évidemment pas aussi optimal que l'algorithme de Fisher-Yates, mais cela fonctionnerait-il pour des entretiens techniques?
davidatthepark
@Andrea Le code a été rompu car la longueur du tableau est modifiée à l'intérieur de la boucle for. Avec la dernière modification, cela est corrigé.
Charlie Wallace
12
arr1.sort(() => Math.random() - 0.5);
Sam Doidge
la source
1
Pourquoi moins 0,5? Que signifie ce chiffre?
Sartheris Stormhammer
1
@SartherisStormhammer car nous utilisons une fonction compareFunction pour le tri, et si cela renvoie un nombre supérieur à 0, les éléments comparés seront ordonnés uniquement dans le sens. -0,5 sur Math.random () nous donnera un nombre négatif ~ 50% du temps, ce qui nous donne l'ordre inverse.
Sam Doidge
Solution simple et directe. Merci
deanwilliammills
9

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

Tính Ngô Quang
la source
Cet algorithme s'est avéré depuis longtemps défectueux.
Veuillez me le prouver. Je me suis basé sur w3schools
Tính Ngô Quang
4
Vous pouvez lire le fil sur css-tricks.com/snippets/javascript/shuffle-array ou sur news.ycombinator.com/item?id=2728914 . W3schools a toujours été et demeure une horrible source d'informations.
Pour une bonne discussion sur les raisons pour lesquelles ce n'est pas une bonne approche, voir stackoverflow.com/questions/962802/…
Charlie Wallace
8

Une solution récursive:

function shuffle(a,b){
    return a.length==0?b:function(c){
        return shuffle(a,(b||[]).concat(c));
    }(a.splice(Math.floor(Math.random()*a.length),1));
};
julien
la source
8

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);
  }
}
Daniel Patru
la source
7

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 ordertri 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 indexOfs'exécute en temps O (n).

Milo Wielondek
la source
Array.prototype.sortpasse en deux valeurs en tant que aet b, pas l'index. Donc, ce code ne fonctionne pas.
Greg
@gregers vous avez raison, j'ai édité la réponse. Merci.
Milo Wielondek
1
Ce n'est pas très aléatoire. Selon l'implémentation de sort, un élément à l'indice de tableau le plus bas peut nécessiter plus de comparaisons pour atteindre l'indice le plus élevé que l'élément à côté de l'indice le plus élevé. Cela signifie qu'il est moins probable que l'élément à l'indice le plus bas atteigne l'indice le plus élevé.
1 'OU 1 -
7

une fonction de lecture aléatoire qui ne change pas le tableau source

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;
}
Evgenia Manolova
la source
Il s'agit essentiellement de l'algorithme original de Fisher-Yates, votre manière 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.
@torazaburo, merci pour vos commentaires. J'ai mis à jour ma réponse, pour qu'il soit clair que j'offre plutôt une solution attrayante, plutôt qu'une solution à grande échelle
Evgenia Manolova
Nous pourrions également utiliser la spliceméthode pour créer une copie comme ceci: source = array.slice();.
Taiga
6

Voici le PLUS FACILE ,

function shuffle(array) {
  return array.sort(() => Math.random() - 0.5);
}

pour plus d'exemple, vous pouvez le vérifier ici

Aljohn Yamaro
la source
5

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;
}
Raphael C
la source
Quelle est la valeur ajoutée de l'utilisation stricte par rapport à la réponse acceptée?
shortstuffsushi
Pour en savoir plus sur le mode strict et son influence sur les performances, vous pouvez en lire plus ici: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Raphael C
Hmm, pourriez-vous indiquer quelque chose de spécifique dans le document référencé? Rien là-dedans ne semble faire référence à "l'amélioration des performances", à part un vague commentaire en haut à propos de la difficulté potentielle d'optimisation du moteur js. Dans ce cas, il n'est pas clair pour moi quelle utilisation stricte améliorerait.
shortstuffsushi
Le mode strict existe depuis un certain temps, et il y a suffisamment de lectures pour que chacun puisse se faire sa propre opinion s'il doit toujours l'utiliser ou non et pourquoi. Jslint, par exemple, indique assez clairement que vous devez toujours utiliser le mode strict. Douglas Crockford a écrit un grand nombre d'articles et de superbes vidéos sur les raisons pour lesquelles il est important de toujours utiliser le mode strict non seulement comme bonne pratique mais aussi comment il est interprété différemment par les moteurs de navigateur js tels que V8. Je vous conseille fortement de le faire sur Google et de vous faire votre propre opinion à ce sujet.
Raphael C
Voici un vieux fil sur les perfs en mode strict, un peu vieux mais toujours d'actualité: stackoverflow.com/questions/3145966/…
Raphael C
5

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-Yatesalgorithme bien connu tout en l'utilisant Web Cryptography APIpour 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));

Marcin Malinowski
la source
4

Solution moderne en ligne courte utilisant les fonctionnalités ES6:

['a','b','c','d'].map(x => [Math.random(), x]).sort(([a], [b]) => a - b).map(([_, x]) => x);

(à des fins éducatives)

icl7126
la source
4

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;
};
abumalick
la source
4

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)
Hafizur Rahman
la source
4

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.

Redu
la source
3

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; 
vickisys
la source
Il ne devrait pas y avoir de -1for n comme vous <ne l' avez pas utilisé<=
Mohebifar
3

la arrayShufflefonction 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;
}
Tusko Trush
la source
Apparemment, vous faites Sattolo au lieu de Fisher-Yates (Knuth, impartial).
Arthur2e5
3

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.

Thomas Baruchel
la source
Certainement élégant!
Gershom