Come randomizzare (shuffle) un array JavaScript?


1266

Ho un array come questo:

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

Come posso randomizzarlo / mescolarlo?



6
Basta lanciarlo qui per visualizzare quanto è casuale una funzione di shuffle con questo visualizzatore creato da Mike Bostock: bost.ocks.org/mike/shuffle/compare.html
agosto

5
@Blazemonger jsPref è morto. Puoi semplicemente pubblicare qui qual è il più veloce?
eozzy,

Che ne dici di un one-liner? L'array restituito viene mischiato. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

La soluzione di riduzione ha complessità O (n ^ 2). Prova a eseguirlo su un array con un milione di elementi.
riv

Risposte:


1542

L'algoritmo shuffle di fatto imparziale è Shuffle Fisher-Yates (aka Knuth).

Vedi https://github.com/coolaj86/knuth-shuffle

Puoi vedere un'ottima visualizzazione qui (e il post originale collegato a questo )

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

Qualche informazione in più sull'algoritmo utilizzato.


13
La risposta sopra salta elemento 0, la condizione deve essere i--non --i. Inoltre, il test if (i==0)...è superfluo poiché se i == 0il ciclo while non verrà mai inserito. La chiamata a Math.floorpuò essere effettuata più velocemente usando ...| 0. I tempi o tempj possono essere rimossi e il valore può essere assegnato direttamente a myArray [i] o j come appropriato.
RobG

23
@prometheus, tutti gli RNG sono pseudo-casuali a meno che non siano collegati a hardware costoso.
Phil H,

38
@RobG l'implementazione sopra è funzionalmente corretta. Nell'algoritmo Fisher-Yates, il loop non è pensato per essere eseguito per il primo elemento dell'array. Dai un'occhiata a Wikipedia dove ci sono altre implementazioni che salta anche il primo elemento. Dai un'occhiata anche a questo articolo che spiega perché è importante che il ciclo non venga eseguito per il primo elemento.
theon

34
@nikola "non è affatto casuale" è una qualifica un po 'forte per me. Direi che è sufficientemente casuale a meno che tu non sia un crittografo, nel qual caso probabilmente non stai usando Math.Random () in primo luogo.
toon81,

20
Ugh, yoda ( 0 !== currentIndex).
ffxsam,

746

Ecco un'implementazione JavaScript del Durstenfeld shuffle , una versione ottimizzata di 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;
    }
}

Prende un elemento casuale per ogni elemento originale dell'array e lo esclude dall'estrazione successiva, come scegliere casualmente da un mazzo di carte.

Questa intelligente esclusione scambia l'elemento selezionato con quello corrente, quindi seleziona l'elemento casuale successivo dal resto, eseguendo il ciclo indietro per un'efficienza ottimale, assicurando che il prelievo casuale sia semplificato (può sempre iniziare da 0) e quindi saltare l'elemento finale.

Il tempo di esecuzione dell'algoritmo è O(n). Si noti che lo shuffle viene eseguito sul posto, quindi se non si desidera modificare l'array originale, innanzitutto eseguirne una copia .slice(0).


EDIT: aggiornamento a ES6 / ECMAScript 2015

Il nuovo ES6 ci consente di assegnare due variabili contemporaneamente. Ciò è particolarmente utile quando vogliamo scambiare i valori di due variabili, in quanto possiamo farlo in una riga di codice. Ecco una forma più breve della stessa funzione, usando questa funzione.

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]];
    }
}

22
ps Lo stesso algoritmo della risposta di ChristopheD, ma con spiegazione e implementazione più pulita.
Laurens Holst,

12
Le persone attribuiscono la persona sbagliata per l'algoritmo. Non è shuffle Fisher-Yates ma shuffle Durstenfeld . Il vero algoritmo originale Fisher-Yates viene eseguito in n ^ 2 volte, non in n ora
Pacerier

7
Non è necessario return arraypoiché JavaScript passa array per riferimento quando utilizzato come argomento di funzione. Presumo che questo sia per risparmiare spazio sullo stack, ma è una piccola caratteristica interessante. L'esecuzione della riproduzione casuale sull'array consente di riprodurre in ordine casuale l'array originale.
Joel Trauger,

5
L'implementazione in questa risposta favorisce l'estremità inferiore dell'array. Scoperto nel modo più duro . Math.random() should not be multiplied with the loop counter + 1, but with array.lengt () `. Vedi Generazione di numeri interi casuali in JavaScript in un intervallo specifico? per una spiegazione molto completa.
Marjan Venema,

13
@MarjanVenema Non sono sicuro se stai ancora guardando questo spazio, ma questa risposta è corretta e il cambiamento che stai suggerendo in realtà introduce pregiudizi. Vedi blog.codinghorror.com/the-danger-of-naivete per un bel resoconto di questo errore.
user94559

134

Avvertimento!
L'uso di questo algoritmo non è raccomandato , perché lo è inefficiente e fortemente distorto ; vedi commenti. Viene lasciato qui per riferimento futuro, perché l'idea non è così rara.

[1,2,3,4,5,6].sort(function() {
  return .5 - Math.random();
});

13
mi piace questa soluzione, abbastanza per dare un casuale di base
Alex K,

147
Il downvoting in quanto non è poi così casuale. Non so perché abbia così tanti voti positivi. Non utilizzare questo metodo Sembra carino, ma non è del tutto corretto. Ecco i risultati dopo 10.000 iterazioni su quante volte ciascun numero nell'array raggiunge l'indice [0] (posso dare anche gli altri risultati): 1 = 29,19%, 2 = 29,53%, 3 = 20,06%, 4 = 11,91%, 5 = 5,99%, 6 = 3,32%
radtad

8
Va bene se devi randomizzare array relativamente piccoli e non occuparti di cose crittografiche. Sono totalmente d'accordo sul fatto che se hai bisogno di più casualità devi usare una soluzione più complessa.
deadrunk il


12
Il problema è che non è deterministico, il che darà risultati errati (se 1> 2 e 2> 3, dovrebbe essere dato 1> 3, ma questo non lo garantirà. Ciò confonderà l'ordinamento e darà il risultato commentato di @radtad).
MatsLindh

73

Uno potrebbe (o dovrebbe) usarlo come protoipo da Array:

Da 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;
}

42
Davvero nessun beneficio a questo, IMOHO, tranne che forse calpestare l'implementazione di qualcun altro ..
user2864740

2
Se utilizzato nel prototipo di array, dovrebbe essere chiamato altro che shuffle .
Lupo,

57
Si potrebbe (o dovrebbe) evitare di estendere i prototipi nativi: javascriptweblog.wordpress.com/2011/12/05/…
Wédney Yuri,

12
Non dovresti farlo; ogni singolo array interessato da questo non può più essere iterato in modo sicuro usando per ... in. Non estendere i prototipi nativi.

18
@TinyGiant In realtà: non utilizzare i for...inloop per scorrere su array.
Conor O'Brien,

69

Puoi farlo facilmente con la mappa e ordinare:

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. Inseriamo ogni elemento dell'array in un oggetto e gli assegniamo una chiave di ordinamento casuale
  2. Ordiniamo usando la chiave casuale
  3. Non mappiamo per ottenere gli oggetti originali

Puoi mescolare le matrici polimorfiche e l'ordinamento è casuale come Math.random, che è abbastanza buono per la maggior parte degli scopi.

Poiché gli elementi vengono ordinati in base a chiavi coerenti che non vengono rigenerate per ogni iterazione e ogni confronto viene tolto dalla stessa distribuzione, qualsiasi non casualità nella distribuzione di Math.random viene annullata.

Velocità

La complessità temporale è O (N log N), uguale all'ordinamento rapido. La complessità dello spazio è O (N). Questo non è efficiente come un shuffle Fischer Yates ma, a mio avviso, il codice è significativamente più breve e più funzionale. Se disponi di una vasta gamma, dovresti sicuramente usare Fischer Yates. Se hai un piccolo array con poche centinaia di elementi, potresti farlo.


1
@superluminary Oops, hai ragione. Si noti che questa risposta ha già utilizzato lo stesso approccio.
Bergi,

@Bergi - Ah sì, hai ragione, anche se penso che la mia implementazione sia leggermente più carina.
superluminario

3
Molto bella. Questa è la trasformazione di Schwartz in js.
Mark Grimes,

@torazaburo - Non è performante come Fischer Yates, ma è più bello e il codice è più piccolo. Il codice è sempre un compromesso. Se avessi un array di grandi dimensioni, userei Knuth. Se avessi un paio di centinaia di oggetti, lo farei.
superluminario

1
@BenCarp - D'accordo, non è la soluzione più veloce e non vorresti usarla su un array enorme, ma ci sono più considerazioni nel codice che velocità raw.
superluminario

64

Utilizzare la libreria underscore.js. Il metodo _.shuffle()è carino per questo caso. Ecco un esempio con il metodo:

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();

12
Bella risposta! Grazie. Lo preferisco alle altre risposte, in quanto incoraggia le persone a usare le librerie piuttosto che copiare e incollare funzioni potenzialmente difettose ovunque.
frabcus,

60
@frabcus: non ha senso includere un'intera libreria solo per ottenere una shufflefunzione.
Blender,

11
Non sono d'accordo con @Blender. Ci sono molti motivi per includere un'intera libreria solo per ottenere una funzione di cui hai bisogno. Uno di questi è che c'è meno rischio di un bug quando lo scrivi tu stesso. Se si tratta di un problema di prestazioni, non dovresti usarlo. Ma solo perché potrebbe essere un problema di prestazioni non significa che lo sarà.
Daniel Kaplan,

7
@tieTYT: Allora perché hai bisogno del resto della biblioteca? Il shuffle Fisher-Yates è banale da implementare. Non hai bisogno di una libreria per scegliere un elemento casuale da un array (spero), quindi non c'è motivo di usare una libreria a meno che tu non utilizzi effettivamente più di una funzione da essa.
Blender,

18
@Blender: ho dato un motivo per cui. 1) Ti assicuro che puoi introdurre un bug in qualsiasi codice che scrivi, non importa quanto sia banale. Perché rischiare? 2) Non pre-ottimizzare. 3) Il 99% delle volte in cui hai bisogno di un shuffle algo, la tua app non riguarda la scrittura di un shuffle algo. Si tratta di qualcosa che ha bisogno di un algo casuale. Sfrutta il lavoro degli altri. Non pensare ai dettagli di implementazione a meno che non sia necessario.
Daniel Kaplan,

50

NUOVO!

Algoritmo shuffle Fisher-Yates più corto e probabilmente * più veloce

  1. usa mentre ---
  2. bit a bit (numeri fino a 10 cifre decimali (32 bit))
  3. rimosso chiusure non necessarie e altre cose

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
}

dimensione dello script (con fy come nome della funzione): 90 byte

DEMO http://jsfiddle.net/vvpoma8w/

* probabilmente più veloce su tutti i browser tranne Chrome.

Se hai domande, chiedi pure.

MODIFICARE

si è più veloce

PRESTAZIONI: http://jsperf.com/fyshuffle

utilizzando le funzioni più votate.

EDIT C'è stato un calcolo in eccesso (non è necessario --c + 1) e nessuno se ne è accorto

più corto (4 byte) e più veloce (testalo!).

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 memorizzazione nella cache da qualche altra parte var rnd=Math.randome quindi l'utilizzo rnd()aumenterebbe leggermente le prestazioni su array di grandi dimensioni.

http://jsfiddle.net/vvpoma8w/2/

Versione leggibile (usa la versione originale. Questa è più lenta, i var sono inutili, come le chiusure & ";", il codice stesso è anche più corto ... forse leggi questo Come 'minimizzare' il codice Javascript , ma non puoi comprimere il seguente codice in un minifigure javascript come quello sopra.)

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

6
controlla le prestazioni ... 2x più veloce sulla maggior parte dei browser ... ma ha bisogno di più tester jsperf ...
cocco

10
js è un linguaggio che accetta molte scorciatoie e diversi modi per scriverlo .. mentre ci sono molte funzioni di lettura lenta qui, mi piace solo mostrare come potrebbe essere fatto in un modo più performante, salvando anche alcuni byte ... bit a bit e la stenografia è davvero sottovalutata qui e il web è pieno di buggy e codice lento.
cocco,

Non un aumento di schiacciata schiacciata. Scambiando fye shuffle prototype, ottengo fycostantemente il fondo in Chrome 37 su OS X 10.9.5 (81% più lento ~ 20k op rispetto a ~ 100k) e Safari 7.1 è fino a ~ 8% più lento. YMMV, ma non è sempre più veloce. jsperf.com/fyshuffle/3
Spig

controlla di nuovo le statistiche ... ho già scritto Chrome è più lento perché hanno ottimizzato la matematica, su tutti gli altri bit bit e mentre è più veloce. controlla IE, firefox ma anche i dispositivi mobili. Sarebbe anche bello vedere l'opera ...
cocco

1
Questa è una risposta terribile. SO non è una competizione di oscuramento.
Puppy,

39

Modifica: questa risposta non è corretta

Vedi commenti e https://stackoverflow.com/a/18650169/28234 . Viene lasciato qui come riferimento perché l'idea non è rara.


Un modo molto semplice per piccoli array è semplicemente questo:

const someArray = [1, 2, 3, 4, 5];

someArray.sort(() => Math.random() - 0.5);

Probabilmente non è molto efficiente, ma per piccoli array funziona bene. Ecco un esempio in modo da poter vedere quanto è casuale (o meno) e se si adatta al tuo caso d'uso o meno.

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>


Bello, ma genera elementi casuali completi ogni volta?
DDD

Non sono sicuro di averti capito correttamente. Questo approccio infatti mescolerà l'array in modo casuale (anche se pseudo-casuale) ogni volta che si chiama l'array di ordinamento - non è un ordinamento stabile, per ovvie ragioni.
Kris Selbekk,

4
Per gli stessi motivi illustrati su stackoverflow.com/a/18650169/28234 . È molto più probabile che questo lasci gli elementi iniziali vicino all'inizio dell'array.
AlexC,

7
Questo è un ottimo, facile da usare per quando è necessario rimescolare un array, ma non importa troppo se i risultati sono accademicamente provabili in modo casuale. A volte, gli ultimi pochi centimetri alla perfezione richiedono più tempo di quanto valga la pena.
Daniel Griscom,

1
Sarebbe bello se funzionasse, ma non lo fa. A causa del modo in cui funziona la ricerca rapida, è probabile che un comparatore incoerente lasci gli elementi dell'array vicino alla loro posizione originale. Il tuo array non verrà criptato.
superluminario

39

Affidabile, efficiente, breve

Alcune soluzioni in questa pagina non sono affidabili (randomizzano solo parzialmente l'array). Altre soluzioni sono significativamente meno efficienti. Con testShuffleArrayFun(vedi sotto) possiamo testare le funzioni di shuffle di array per affidabilità e prestazioni. Le seguenti soluzioni sono: affidabili, efficienti e brevi (usando la sintassi ES6)

[I test di confronto sono stati eseguiti utilizzando testShuffleArrayFuncontro altre soluzioni, in Google Chrome]

Shuffle Array Sul posto

    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 puro, iterativo

    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 di affidabilità e prestazioni

Come puoi vedere in questa pagina, ci sono state soluzioni errate offerte qui in passato. Ho scritto e usato la seguente funzione per testare qualsiasi funzione di randomizzazione di array pura (senza effetti collaterali).

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

Altre soluzioni

Altre soluzioni solo per divertimento.

ES6 puro, ricorsivo

    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 utilizzando 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 utilizzando 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]
        )
    }

Quindi, dov'è l'ES6 (ES2015)? [array[i], array[rand]]=[array[rand], array[i]]? Forse puoi delineare come funziona. Perché scegli di scorrere verso il basso?
Sheriffderek,

@sheriffderek Sì, la funzione ES6 che sto usando è l'assegnazione di due VAR contemporaneamente, che ci consente di scambiare due VAR in una riga di codice.
Ben Carp,

Ringraziamo @sheriffderek che ha suggerito l'algoritmo ascendente. L'algoritmo ascendente potrebbe essere dimostrato in induzione.
Ben Carp,

23

Aggiungendo alla risposta @Laurens Holsts. Questo è compresso al 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
};

3
Dovremmo incoraggiare le persone a usare _.shuffle piuttosto che incollare il codice dallo stack overflow; e dovremmo scoraggiare le persone dal comprimere le loro risposte di overflow dello stack. Ecco a cosa serve jsmin.
David Jones,

45
@DavidJones: Perché dovrei includere un'intera libreria da 4kb solo per mescolare un array?
Blender,

1
Anche la chiamata del nome @KingKongFrog non è favorevole a un assemblaggio di una comunità ragionevole.
Wheaties

2
è efficiente fare var b = in un ciclo invece di dichiarare b ciclo esterno e assegnarlo con b = in un ciclo?
Alex K,

2
@Brian Non farà la differenza; il sollevamento avviene quando viene analizzato il codice sorgente. Probabilmente non è coinvolto.
user2864740

23

Modifica: questa risposta non è corretta

Vedi https://stackoverflow.com/a/18650169/28234 . Viene lasciato qui come riferimento perché l'idea non è rara.

//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 è un numero casuale che può essere positivo o negativo, quindi la funzione di ordinamento riordina gli elementi in modo casuale.


17

Con ES2015 puoi usare questo:

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

Uso:

[1, 2, 3, 4, 5, 6, 7].shuffle();

4
Per troncare, è necessario utilizzare n >>> 0invece di ~~n. Gli indici di array possono essere superiori a 2³¹-1.
Oriol,

1
Una ristrutturazione come questa comporta un'implementazione così
chiara

14

Ho trovato questa variante appesa nelle risposte "cancellate dall'autore" su un duplicato di questa domanda. A differenza di alcune delle altre risposte che hanno già molti voti, questo è:

  1. In realtà casuale
  2. Non sul posto (da qui il shufflednome piuttosto che shuffle)
  3. Non già presente qui con più varianti

Ecco un jsfiddle che lo mostra in uso .

Array.prototype.shuffled = function() {
  return this.map(function(n){ return [Math.random(), n] })
             .sort().map(function(n){ return n[1] });
}

(Sospetto che sia stato eliminato in quanto è un modo molto inefficiente per randomizzare l'array, specialmente per array più grandi ... mentre la risposta accettata e un certo numero di altri cloni di quella risposta si randomizzano sul posto).
WiredPrairie,

1
Sì, ma dato che la nota risposta sbagliata è ancora piena di voti, almeno una soluzione inefficiente ma corretta dovrebbe essere menzionata.
Daniel Martin,

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });- non dà un ordinamento casuale, e se lo usi puoi finire in imbarazzo: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Daniel Martin

3
È necessario utilizzare .sort(function(a,b){ return a[0] - b[0]; })se si desidera che l'ordinamento confronti i valori numericamente. Il .sort()comparatore predefinito è lessicografico, il che significa che considererà 10meno di 2poiché 1è minore di 2.
4castle,

@ 4castle Okay, ho aggiornato il codice, ma ho intenzione di ripristinarlo: la distinzione tra ordine lessicografico e ordine numerico non ha importanza per i numeri nell'intervallo che Math.random()produce. (vale a dire, l'ordine lessicografico è lo stesso dell'ordine numerico quando si tratta di numeri da 0 (incluso) a 1 (esclusivo))
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;
};

Questo ovviamente non è ottimale come l'algoritmo Fisher-Yates, ma funzionerebbe per le interviste tecniche?
davidatthepark,

@Andrea Il codice è stato rotto a causa del fatto che la lunghezza dell'array è cambiata all'interno del ciclo for. Con l'ultima modifica questo è corretto.
Charlie Wallace,

12
arr1.sort(() => Math.random() - 0.5);

1
Perché meno 0,5? Cosa significa quel numero?
Sartheris Stormhammer,

1
@SartherisStormhammer perché stiamo usando un compareFunction per l'ordinamento e se questo restituisce un numero maggiore di 0, gli elementi confrontati verranno ordinati solo in direzione. -0.5 su Math.random () ci darà un numero negativo ~ 50% delle volte, il che ci dà l'ordine inverso.
Sam Doidge

Soluzione semplice e diretta. Grazie
deanwilliammills il

9

Puoi farlo facilmente con:

// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);

Fare riferimento alle matrici di ordinamento JavaScript


Questo algoritmo ha da tempo dimostrato di essere difettoso.

Per favore, dimostramelo. Ho basato su w3schools
Ngô Quang il

4
Puoi leggere la discussione su css-tricks.com/snippets/javascript/shuffle-array o su news.ycombinator.com/item?id=2728914 . W3schools è sempre stata e rimane un'orribile fonte di informazioni.

Per una buona discussione sul perché questo non è un buon approccio, consultare stackoverflow.com/questions/962802/…
Charlie Wallace,

8

Una soluzione ricorsiva:

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

8

Fisher-Yates in ordine casuale in javascript. Sto pubblicando questo qui perché l'uso di due funzioni di utilità (swap e randInt) chiarisce l'algoritmo rispetto alle altre risposte qui.

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

7

Prima di tutto, dai un'occhiata qui per un ottimo confronto visivo di diversi metodi di ordinamento in JavaScript.

In secondo luogo, se dai una rapida occhiata al link sopra troverai che l' random orderordinamento sembra funzionare relativamente bene rispetto agli altri metodi, pur essendo estremamente facile e veloce da implementare come mostrato di seguito:

function shuffle(array) {
  var random = array.map(Math.random);
  array.sort(function(a, b) {
    return random[array.indexOf(a)] - random[array.indexOf(b)];
  });
}

Modifica : come sottolineato da @gregers, la funzione di confronto viene chiamata con valori anziché indici, motivo per cui è necessario utilizzare indexOf. Si noti che questa modifica rende il codice meno adatto per array più grandi in quanto indexOfviene eseguito nel tempo O (n).


Array.prototype.sortpassa due valori come ae bnon l'indice. Quindi questo codice non funziona.
Gregers

@grers hai ragione, ho modificato la risposta. Grazie.
Milo Wielondek,

1
Questo non è molto casuale. A seconda dell'implementazione dell'ordinamento, un elemento con l'indice di array più basso potrebbe richiedere più confronti per raggiungere l'indice più alto rispetto all'elemento accanto all'indice più alto. Ciò significa che è meno probabile che l'elemento nell'indice più basso raggiunga l'indice più alto.
1 "OPPURE 1 -

7

una funzione shuffle che non cambia l'array di origine

Aggiornamento : Qui sto suggerendo un algoritmo relativamente semplice (non dal punto di vista della complessità ) e breve che andrà benissimo con array di piccole dimensioni, ma sicuramente costerà molto di più rispetto al classico algoritmo di Durstenfeld quando si affrontano array enormi. Puoi trovare Durstenfeld in una delle migliori risposte a questa domanda.

Risposta originale:

Se non si desidera che la funzione shuffle muti l' array di origine , è possibile copiarla in una variabile locale, quindi fare il resto con una semplice logica di shuffle .

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

Logica casuale: raccogliere un indice casuale, quindi aggiungere l'elemento corrispondente all'array dei risultati ed eliminarlo dalla copia dell'array di origine . Ripetere questa azione fino a quando l'array di origine si svuota .

E se lo vuoi davvero breve, ecco quanto potrei arrivare:

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

Questo è essenzialmente l'algoritmo originale Fisher-Yates, con il tuo spliceessere un modo orribilmente inefficiente per fare ciò che chiamavano "colpire". Se non vuoi mutare l'array originale, copialo e poi mescola quella copia in posizione usando la variante di Durstenfeld molto più efficiente.

@torazaburo, grazie per il tuo feedback. Ho aggiornato la mia risposta, per chiarire che sto piuttosto offrendo una soluzione di bell'aspetto, piuttosto che una super-ridimensionamento
Evgenia Manolova,

Potremmo anche utilizzare il splicemetodo per creare una copia in questo modo: source = array.slice();.
Taiga,

6

Ecco il più FACILE ,

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

per ulteriori esempi, puoi verificarlo qui


5

ancora un'altra implementazione di Fisher-Yates, usando la modalità rigorosa:

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

Quale valore fornisce l'aggiunta dell'uso rigoroso rispetto alla risposta accettata?
shortstuffsushi,

Per saperne di più sulla modalità rigorosa e su come influenza le prestazioni, puoi leggere qui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Raphael C

Hmm, potresti indicare qualcosa di specifico dal documento di riferimento? Nulla sembra fare riferimento al "miglioramento delle prestazioni", a parte un vago commento all'inizio che potrebbe rendere difficile l'ottimizzazione del motore js. In questo caso, non mi è chiaro quale uso di rigoroso migliorerebbe.
shortstuffsushi,

La modalità Strict è in circolazione da un po 'di tempo e ci sono letture sufficienti per chiunque di esprimere la propria opinione se dovrebbero sempre usarla o no e perché. Ad esempio Jslint chiarisce abbastanza che dovresti sempre usare la modalità rigorosa. Douglas Crockford ha scritto parecchi articoli e alcuni fantastici video sul perché è importante utilizzare sempre la modalità rigorosa non solo come una buona pratica, ma anche come viene interpretata in modo diverso dai motori js del browser come V8. Ti consiglio vivamente di farlo su Google e di esprimere la tua opinione al riguardo.
Raffaello C,

Ecco un vecchio thread sui perf in modalità rigorosa, un po 'vecchio ma ancora rilevante: stackoverflow.com/questions/3145966/…
Raphael C

5

Tutte le altre risposte sono basate su Math.random () che è veloce ma non adatto alla randomizzazione a livello crittografico.

Il codice seguente utilizza l' Fisher-Yatesalgoritmo ben noto durante l'utilizzo Web Cryptography APIper il livello crittografico di randomizzazione .

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


4

Moderna soluzione inline corta che utilizza le funzionalità ES6:

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

(a fini didattici)


4

Una semplice modifica della risposta di CoolAJ86 che non modifica l'array originale:

 /**
 * 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;
};

4

Sebbene ci siano già un certo numero di implementazioni già consigliate, ma ritengo che possiamo renderlo più breve e più facile usando forEach loop, quindi non dobbiamo preoccuparci di calcolare la lunghezza dell'array e possiamo anche evitare di usare una variabile temporanea.

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)

4

Solo per avere un dito nella torta. Qui presento un'implementazione ricorsiva di Fisher Yates shuffle (credo). Dà casualità uniforme.

Nota: l' ~~operatore (doppia tilde) si comporta in effetti come Math.floor()per i numeri reali positivi. È solo una scorciatoia.

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])));

Modifica: il codice sopra è O (n ^ 2) a causa dell'impiego di .splice()ma possiamo eliminare la giunzione e la riproduzione casuale in O (n) con il trucco di scambio.

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");

Il problema è che JS non può cooperare con grandi ricorsioni. In questo caso particolare, la dimensione dell'array è limitata, ad esempio 3000 ~ 7000, a seconda del motore del browser e di alcuni fatti sconosciuti.


3

Randomizza l'array

 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; 

Non ci dovrebbe essere una -1per n, come si è utilizzato <non<=
Mohebifar

3

la arrayShufflefunzione più breve

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

Apparentemente stai facendo Sattolo invece di Fisher-Yates (Knuth, imparziale).
Arthur2e5,

3

Da un punto di vista teorico, il modo più elegante di farlo, secondo la mia modesta opinione, è ottenere un singolo numero casuale compreso tra 0 e n! -1 e calcolare un mapping uno a uno da {0, 1, …, n!-1}tutte le permutazioni di(0, 1, 2, …, n-1) . Finché puoi usare un generatore (pseudo-) casuale abbastanza affidabile per ottenere un tale numero senza alcun pregiudizio significativo, hai abbastanza informazioni in esso per raggiungere quello che vuoi senza bisogno di molti altri numeri casuali.

Quando si esegue il calcolo con numeri mobili IEEE754 a precisione doppia, è possibile aspettarsi che il generatore casuale fornisca circa 15 decimali. Dato che hai 15! = 1.307.674.368.000 (con 13 cifre), puoi usare le seguenti funzioni con array che contengono fino a 15 elementi e supporre che non ci saranno errori di bias significativi con array che contengono fino a 14 elementi. Se lavori su un problema di dimensioni fisse che richiede di calcolare molte volte questa operazione shuffle, potresti provare il seguente codice che potrebbe essere più veloce di altri codici poiché utilizzaMath.random solo una volta (tuttavia comporta diverse operazioni di copia).

La seguente funzione non verrà utilizzata, ma la do comunque; restituisce l'indice di una data permutazione (0, 1, 2, …, n-1)secondo la mappatura one to one utilizzata in questo messaggio (la più naturale quando si enumerano le permuazioni); è progettato per funzionare con un massimo di 16 elementi:

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

Il reciproco della funzione precedente (richiesto per la propria domanda) è inferiore; è progettato per funzionare con un massimo di 16 elementi; restituisce la permutazione dell'ordine n di (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;
}

Ora, ciò che vuoi semplicemente è:

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]; });
}

Dovrebbe funzionare per un massimo di 16 elementi con un po 'di pregiudizio teorico (sebbene impercettibile dal punto di vista pratico); può essere visto come completamente utilizzabile per 15 elementi; con array che contengono meno di 14 elementi, puoi tranquillamente considerare che non ci sarà alcun bias.


Decisamente elegante!
Gershom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.