Dopo che Jon ha già trattato la teoria , ecco un'implementazione:
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'algoritmo è O(n)
, mentre dovrebbe essere l'ordinamento O(n log n)
. A seconda del sovraccarico dell'esecuzione del codice JS rispetto alla sort()
funzione nativa , ciò potrebbe comportare una notevole differenza nelle prestazioni che dovrebbe aumentare con le dimensioni dell'array.
Nei commenti alla risposta di bobobobo , ho affermato che l'algoritmo in questione potrebbe non produrre probabilità distribuite uniformemente (a seconda dell'implementazione di sort()
).
La mia argomentazione segue queste linee: un algoritmo di ordinamento richiede un certo numero c
di confronti, ad esempio c = n(n-1)/2
per Bubblesort. La nostra funzione di confronto casuale rende ugualmente probabile l'esito di ciascun confronto, ovvero ci sono risultati 2^c
altrettanto probabili . Ora, ogni risultato deve corrispondere a una delle n!
permutazioni delle voci dell'array, il che rende impossibile una distribuzione uniforme nel caso generale. (Questa è una semplificazione, poiché il numero effettivo di confronti necessari dipende dall'array di input, ma l'affermazione dovrebbe comunque essere valida.)
Come ha sottolineato Jon, questo da solo non è un motivo per preferire Fisher-Yates all'utilizzo sort()
, poiché il generatore di numeri casuali mapperà anche un numero finito di valori pseudo-casuali alle n!
permutazioni. Ma i risultati di Fisher-Yates dovrebbero essere ancora migliori:
Math.random()
produce un numero pseudo-casuale nell'intervallo [0;1[
. Poiché JS utilizza valori a virgola mobile a precisione doppia, ciò corrisponde a 2^x
possibili valori in cui 52 ≤ x ≤ 63
(sono troppo pigro per trovare il numero effettivo). Una distribuzione di probabilità generata usando Math.random()
smetterà di comportarsi bene se il numero di eventi atomici è dello stesso ordine di grandezza.
Quando si utilizza Fisher-Yates, il parametro rilevante è la dimensione dell'array, che non dovrebbe mai avvicinarsi a 2^52
causa di limitazioni pratiche.
Quando si ordina con una funzione di confronto casuale, la funzione in pratica si preoccupa solo se il valore di ritorno è positivo o negativo, quindi questo non sarà mai un problema. Ma ce n'è una simile: poiché la funzione di confronto è ben educata, i 2^c
risultati possibili sono, come affermato, ugualmente probabili. Se c ~ n log n
poi 2^c ~ n^(a·n)
dove a = const
, il che rende quanto meno possibile che 2^c
abbia la stessa grandezza di (o anche meno di) n!
e che porti quindi a una distribuzione irregolare, anche se l'algoritmo di ordinamento dove mappare uniformemente le permuteioni. Se questo ha qualche impatto pratico è al di là di me.
Il vero problema è che gli algoritmi di ordinamento non sono garantiti per mappare uniformemente sulle permutazioni. È facile vedere che Mergesort fa come è simmetrico, ma il ragionamento su qualcosa come Bubblesort o, soprattutto, Quicksort o Heapsort, non lo è.
La linea di fondo: fintanto che sort()
usa Mergesort, dovresti essere ragionevolmente sicuro, tranne nei casi d'angolo (almeno spero che 2^c ≤ n!
sia un caso d'angolo), in caso contrario, tutte le scommesse sono disattivate.