Mam taką tablicę:
var arr1 = ["a", "b", "c", "d"];
Jak mogę go losowo / losowo przetasować?
Mam taką tablicę:
var arr1 = ["a", "b", "c", "d"];
Jak mogę go losowo / losowo przetasować?
Odpowiedzi:
De facto algorytm tasowania bezstronnego to tasowanie Fishera-Yatesa (inaczej Knuth).
Zobacz https://github.com/coolaj86/knuth-shuffle
Tutaj możesz zobaczyć świetną wizualizację (oraz link do oryginalnego postu )
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);
Więcej informacji na temat zastosowanego algorytmu .
i--
nie --i
. Ponadto, test if (i==0)...
jest zbędny, ponieważ jeśli natomiast pętla nigdy nie zostanie wprowadzona. Wezwanie do można wykonać szybciej za pomocą . Albo Tempa lub tempj można usunąć, a wartość należy przypisać do myArray [I] lub J , odpowiednio. i == 0
Math.floor
...| 0
0 !== currentIndex
).
Oto implementacja JavaScript shuffle Durstenfelda , zoptymalizowanej wersji Fisher-Yatesa:
/* 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;
}
}
Wybiera losowy element dla każdego oryginalnego elementu tablicy i wyklucza go z następnego losowania, jak losowe wybieranie z talii kart.
To sprytne wykluczenie zamienia wybrany element z bieżącym, a następnie wybiera następny losowy element z reszty, zapętlając do tyłu w celu uzyskania optymalnej wydajności, zapewniając, że losowy wybór jest uproszczony (zawsze można rozpocząć od 0), a tym samym pomija ostatni element.
Czas działania algorytmu to O(n)
. Pamiętaj, że losowanie odbywa się w miejscu, więc jeśli nie chcesz modyfikować oryginalnej tablicy, najpierw wykonaj jej kopię .slice(0)
.
Nowy ES6 pozwala nam przypisać dwie zmienne jednocześnie. Jest to szczególnie przydatne, gdy chcemy zamienić wartości dwóch zmiennych, ponieważ możemy to zrobić w jednym wierszu kodu. Oto krótsza forma tej samej funkcji, korzystająca z tej funkcji.
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
return array
ponieważ JavaScript przekazuje tablice przez referencję, gdy jest używany jako argument funkcji. Zakładam, że pozwala to zaoszczędzić miejsce na stosie, ale jest to interesująca mała funkcja. Wykonanie losowania na tablicy spowoduje przetasowanie oryginalnej tablicy.
Math.random() should not be multiplied with the loop counter + 1, but with
array.lengt () `. Zobacz Generowanie losowych liczb całkowitych w JavaScript w określonym zakresie? dla bardzo wyczerpującego wyjaśnienia.
Ostrzeżenie!
Zastosowanie tego algorytmu nie jest zalecane , ponieważ jest nieefektywne i silnie stronnicze ; Zobacz komentarze. Zostawia się go tutaj do wglądu w przyszłości, ponieważ pomysł nie jest tak rzadki.
[1,2,3,4,5,6].sort(function() {
return .5 - Math.random();
});
Można (lub należy) użyć go jako protoype z Array:
Od ChristopheD:
Array.prototype.shuffle = function() {
var i = this.length, j, temp;
if ( i == 0 ) return this;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return this;
}
for...in
pętli do iteracji po tablicach.
Możesz to łatwo zrobić za pomocą mapy i sortowania:
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)
Możesz tasować tablice polimorficzne, a sortowanie jest tak losowe jak Math.random, co jest wystarczające do większości celów.
Ponieważ elementy są sortowane według spójnych kluczy, które nie są regenerowane przy każdej iteracji, a każde porównanie pobiera z tego samego rozkładu, wszelkie nielosowości w rozkładzie Math.random są anulowane.
Prędkość
Złożoność czasowa wynosi O (N log N), podobnie jak szybkie sortowanie. Złożoność przestrzeni wynosi O (N). Nie jest to tak skuteczne jak tasowanie Fischera Yatesa, ale moim zdaniem kod jest znacznie krótszy i bardziej funkcjonalny. Jeśli masz dużą tablicę, z pewnością powinieneś użyć Fischera Yatesa. Jeśli masz małą tablicę zawierającą kilkaset przedmiotów, możesz to zrobić.
Użyj biblioteki underscore.js. Metoda _.shuffle()
jest dobra w tym przypadku. Oto przykład z metodą:
var _ = require("underscore");
var arr = [1,2,3,4,5,6];
// Testing _.shuffle
var testShuffle = function () {
var indexOne = 0;
var stObj = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5
};
for (var i = 0; i < 1000; i++) {
arr = _.shuffle(arr);
indexOne = _.indexOf(arr, 1);
stObj[indexOne] ++;
}
console.log(stObj);
};
testShuffle();
shuffle
funkcję.
NOWY!
Krótszy i prawdopodobnie * szybszy algorytm losowania Fisher-Yatesa
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
}
rozmiar skryptu (z fy jako nazwą funkcji): 90 bajtów
PRÓBNY http://jsfiddle.net/vvpoma8w/
* szybciej prawdopodobnie we wszystkich przeglądarkach oprócz Chrome.
Jeśli masz jakieś pytania, po prostu zapytaj.
EDYTOWAĆ
tak to jest szybsze
WYDAJNOŚĆ: http://jsperf.com/fyshuffle
za pomocą najczęściej głosowanych funkcji.
EDYCJA Nastąpiło przekroczenie obliczeń (nie trzeba --c + 1) i nikt tego nie zauważył
krótszy (4 bajty) i szybszy (przetestuj!).
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
}
Buforowanie gdzie indziej, var rnd=Math.random
a następnie użycie rnd()
również nieznacznie zwiększy wydajność na dużych tablicach.
http://jsfiddle.net/vvpoma8w/2/
Wersja do odczytu (użyj oryginalnej wersji. Jest wolniejsza, zmienne są bezużyteczne, takie jak zamknięcia & ";", sam kod jest również krótszy ... może przeczytaj to Jak „zminimalizować” kod JavaScript , ale nie jesteś w stanie skompresuj następujący kod w minizatorach javascript, takich jak powyższy.)
function fisherYates( array ){
var count = array.length,
randomnumber,
temp;
while( count ){
randomnumber = Math.random() * count-- | 0;
temp = array[count];
array[count] = array[randomnumber];
array[randomnumber] = temp
}
}
fy
i shuffle prototype
, fy
konsekwentnie dostaję się na dole w przeglądarce Chrome 37 w systemie OS X 10.9.5 (81% wolniej ~ 20 tys. Operacji w porównaniu do ~ 100 tys.), A Safari 7.1 - do ~ 8% wolniej. YMMV, ale nie zawsze jest szybszy. jsperf.com/fyshuffle/3
Edycja: ta odpowiedź jest niepoprawna
Zobacz komentarze i https://stackoverflow.com/a/18650169/28234 . Zostaje tutaj w celach informacyjnych, ponieważ pomysł nie jest rzadki.
Bardzo prosty sposób na małe tablice to po prostu:
const someArray = [1, 2, 3, 4, 5];
someArray.sort(() => Math.random() - 0.5);
Prawdopodobnie nie jest zbyt wydajny, ale w przypadku małych tablic działa to dobrze. Oto przykład, dzięki któremu możesz zobaczyć, jak losowy (lub nie) i czy pasuje do Twojej skrzynki użytkownika, czy nie.
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>
Niektóre rozwiązania na tej stronie nie są niezawodne (tylko częściowo losują tablicę). Inne rozwiązania są znacznie mniej wydajne. Za pomocą testShuffleArrayFun
(patrz poniżej) możemy przetestować funkcje tasowania tablicy pod kątem niezawodności i wydajności. Następujące rozwiązania: niezawodne, wydajne i krótkie (przy użyciu składni ES6)
[Testy porównawcze zostały wykonane przy użyciu testShuffleArrayFun
innych rozwiązań w Google Chrome]
Shuffle Array In 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 Czysty, iteracyjny
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
};
Jak widać na tej stronie, w przeszłości oferowane były tutaj nieprawidłowe rozwiązania. Napisałem i użyłem poniższej funkcji do przetestowania dowolnych funkcji randomizujących tablicę czystą (bez skutków ubocznych).
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}`)
}
Inne rozwiązania dla zabawy.
ES6 Pure, rekurencyjny
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 przy użyciu 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 przy użyciu array.reduce
function getShuffledArr (arr){
return arr.reduce(
(newArr, _, i) => {
var rand = i + ( Math.floor( Math.random() * (newArr.length - i) ) );
[newArr[rand], newArr[i]] = [newArr[i], newArr[rand]]
return newArr
}, [...arr]
)
}
[array[i], array[rand]]=[array[rand], array[i]]
? Może możesz nakreślić, jak to działa. Dlaczego decydujesz się na iterację w dół?
Dodanie do odpowiedzi @Laurens Holsts. Jest to skompresowane w 50%.
function shuffleArray(d) {
for (var c = d.length - 1; c > 0; c--) {
var b = Math.floor(Math.random() * (c + 1));
var a = d[c];
d[c] = d[b];
d[b] = a;
}
return d
};
var b =
w pętli zamiast deklarować b poza pętlą i przypisywać ją b =
w pętli?
Zobacz https://stackoverflow.com/a/18650169/28234 . Zostaje tutaj w celach informacyjnych, ponieważ pomysł nie jest rzadki.
//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
jest liczbą losową, która może być dodatnia lub ujemna, więc funkcja sortowania losowo zmienia kolejność elementów.
W ES2015 możesz użyć tego:
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;
}
Stosowanie:
[1, 2, 3, 4, 5, 6, 7].shuffle();
n >>> 0
zamiast ~~n
. Indeksy tablic mogą być wyższe niż 2³¹-1.
Znalazłem ten wariant w odpowiedzi na „usunięte przez autora” na duplikacie tego pytania. W przeciwieństwie do niektórych innych odpowiedzi, które mają już wiele pozytywnych opinii, jest to:
shuffled
nazwa zamiast shuffle
)Oto jsfiddle pokazujący, że jest w użyciu .
Array.prototype.shuffled = function() {
return this.map(function(n){ return [Math.random(), n] })
.sort().map(function(n){ return n[1] });
}
[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });
- nie daje losowego sortowania, a jeśli go użyjesz, możesz się wstydzić: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
.sort(function(a,b){ return a[0] - b[0]; })
jeśli chcesz, aby sortowanie porównywało wartości liczbowo. Domyślny .sort()
komparator to leksykograficzny, co oznacza, że będzie uważany 10
za mniejszy od tego 2
czasu1
mniej niż 2
.
Math.random()
produkuje. (to znaczy porządek leksykograficzny jest taki sam jak porządek numeryczny w przypadku liczb od 0 (włącznie) do 1 (wyłącznie))
var shuffle = function(array) {
temp = [];
originalLength = array.length;
for (var i = 0; i < originalLength; i++) {
temp.push(array.splice(Math.floor(Math.random()*array.length),1));
}
return temp;
};
arr1.sort(() => Math.random() - 0.5);
Możesz to łatwo zrobić za pomocą:
// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);
Odwołaj się do JavaScript Sorting Arrays
Fisher-Yates tasuje w javascript. Zamieszczam to tutaj, ponieważ użycie dwóch funkcji narzędziowych (swap i randInt) wyjaśnia algorytm w porównaniu z innymi odpowiedziami tutaj.
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);
}
}
Przede wszystkim spójrz tutaj aby uzyskać świetne wizualne porównanie różnych metod sortowania w javascript.
Po drugie, jeśli rzucisz okiem na powyższy link, zobaczysz, że random order
sortowanie wydaje się działać stosunkowo dobrze w porównaniu z innymi metodami, a jednocześnie jest niezwykle łatwe i szybkie do wdrożenia, jak pokazano poniżej:
function shuffle(array) {
var random = array.map(Math.random);
array.sort(function(a, b) {
return random[array.indexOf(a)] - random[array.indexOf(b)];
});
}
Edycja : jak wskazali @gregers, funkcja porównywania jest wywoływana z wartościami, a nie indeksami, dlatego musisz jej użyć indexOf
. Zauważ, że ta zmiana sprawia, że kod jest mniej odpowiedni dla większych tablic, ponieważ indexOf
działa w czasie O (n).
Array.prototype.sort
przekazuje dwie wartości jako a
i b
, a nie indeks. Więc ten kod nie działa.
Aktualizacja : tutaj sugeruję stosunkowo prosty (nie ze złożoności punktu widzenia ) i krótki algorytm, który dobrze sobie poradzi z małymi tablicami, ale na pewno będzie kosztował znacznie więcej niż klasyczny algorytm Durstenfelda , gdy masz do czynienia z dużymi tablicami. Możesz znaleźć Durstenfeld w jednej z najlepszych odpowiedzi na to pytanie.
Oryginalna odpowiedź:
Jeśli nie chcesz, aby funkcja odtwarzania losowego mutowała tablicę źródłową , możesz skopiować ją do zmiennej lokalnej, a następnie zrobić resztę za pomocą prostej logiki losowania .
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;
}
Tasowanie logiki : wybierz losowy indeks, a następnie dodaj odpowiedni element do tablicy wyników i usuń go z kopii tablicy źródłowej . Powtarzaj tę akcję, aż tablica źródłowa się opróżni .
A jeśli naprawdę chcesz tego krótko, oto, jak daleko mogę się dostać:
function shuffle(array) {
var result = [], source = array.concat([]);
while (source.length) {
let index = Math.floor(Math.random() * source.length);
result.push(source.splice(index, 1)[0]);
}
return result;
}
splice
ponieważ jest to strasznie nieefektywny sposób robienia tego, co nazywają „wykreślaniem”. Jeśli nie chcesz mutować oryginalnej tablicy, po prostu skopiuj ją, a następnie przetasuj tę kopię w miejscu, używając znacznie bardziej wydajnego wariantu Durstenfeld.
splice
metody, aby utworzyć kopię tak: source = array.slice();
.
Oto najprostszy jeden,
function shuffle(array) {
array.sort(() => Math.random() - 0.5);
}
na przykład możesz to sprawdzić tutaj
jeszcze jedna implementacja Fisher-Yatesa, wykorzystująca tryb ścisły:
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;
}
Wszystkie pozostałe odpowiedzi oparte są na Math.random (), która jest szybka, ale nie nadaje się do randomizacji na poziomie kryptograficznym.
Poniższy kod za pomocą dobrze znanego Fisher-Yates
algorytmu, przy wykorzystaniu Web Cryptography API
na poziomie kryptograficznego randomizacji .
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));
Modyfikacja prosty z CoolAJ86 za odpowiedź , która nie zmienia oryginalnej tablicy:
/**
* 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;
};
Chociaż jest już wiele zalecanych implementacji, ale uważam, że możemy uczynić je krótszym i łatwiejszym przy użyciu pętli forEach, więc nie musimy się martwić o obliczanie długości tablicy, a także możemy bezpiecznie uniknąć używania tymczasowej zmiennej.
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)
Żeby mieć palec w torcie. Tutaj przedstawiam rekurencyjną implementację shuffle Fishera Yatesa (tak myślę). Daje jednolitą losowość.
Uwaga: ~~
(podwójny operator tyldy) w rzeczywistości zachowuje się jak Math.floor()
dla dodatnich liczb rzeczywistych. To tylko skrót.
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])));
Edycja: Powyższy kod to O (n ^ 2) ze względu na zastosowanie, .splice()
ale możemy wyeliminować splatanie i tasowanie w O (n) za pomocą sztuczki wymiany.
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");
Problem polega na tym, że JS nie może współpracować z dużymi rekurencjami. W tym konkretnym przypadku rozmiar tablicy jest ograniczony do około 3000 ~ 7000, w zależności od silnika przeglądarki i niektórych nieznanych faktów.
Losuj tablicę
var arr = ['apple','cat','Adam','123','Zorro','petunia'];
var n = arr.length; var tempArr = [];
for ( var i = 0; i < n-1; i++ ) {
// The following line removes one random element from arr
// and pushes it onto tempArr
tempArr.push(arr.splice(Math.floor(Math.random()*arr.length),1)[0]);
}
// Push the remaining item onto tempArr
tempArr.push(arr[0]);
arr=tempArr;
-1
for za, jak <
nie <=
najkrótsza arrayShuffle
funkcja
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;
}
Z teoretycznego punktu widzenia najbardziej eleganckim sposobem na zrobienie tego, moim skromnym zdaniem, jest uzyskanie pojedynczej liczby losowej od 0 do n! -1 i obliczenie odwzorowania jeden na jeden {0, 1, …, n!-1}
dla wszystkich permutacji (0, 1, 2, …, n-1)
. Tak długo, jak możesz użyć (pseudo-) losowego generatora wystarczająco niezawodnego, aby uzyskać taką liczbę bez znaczącego odchylenia, masz wystarczająco dużo informacji, aby osiągnąć to, czego chcesz, bez potrzeby kilku innych liczb losowych.
Podczas obliczania liczb zmiennoprzecinkowych podwójnej precyzji IEEE754 można oczekiwać, że generator losowy zapewni około 15 miejsc po przecinku. Ponieważ masz 15! = 1 307 674,368,000 (z 13 cyframi), możesz używać następujących funkcji z tablicami zawierającymi do 15 elementów i zakładać, że nie będzie znaczącego odchylenia z tablicami zawierającymi do 14 elementów. Jeśli pracujesz nad problemem o stałym rozmiarze, wymagającym wielokrotnego obliczenia tej operacji losowania, możesz wypróbować następujący kod, który może być szybszy niż inne kody, ponieważ używa Math.random
tylko jednego kodu (wymaga to jednak kilku operacji kopiowania).
Następująca funkcja nie będzie używana, ale i tak ją daję; zwraca indeks danej permutacji (0, 1, 2, …, n-1)
zgodnie z odwzorowaniem jeden na jeden zastosowanym w tym komunikacie (najbardziej naturalny przy wyliczaniu permuacji); przeznaczony jest do pracy z maksymalnie 16 elementami:
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);
}
Odwrotność poprzedniej funkcji (wymagana dla twojego pytania) jest poniżej; przeznaczony jest do pracy z maksymalnie 16 elementami; zwraca permutacji porządku n o (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;
}
Teraz chcesz jedynie:
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]; });
}
Powinien działać dla maksymalnie 16 elementów z niewielkim teoretycznym nastawieniem (choć z praktycznego punktu widzenia nie jest to zauważalne); może być postrzegany jako w pełni użyteczny dla 15 elementów; z tablicami zawierającymi mniej niż 14 elementów możesz bezpiecznie założyć, że nie będzie absolutnie żadnego błędu.