Jak losowo (przetasować) tablicę JavaScript?


1263

Mam taką tablicę:

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

Jak mogę go losowo / losowo przetasować?



6
Tylko rzucanie to tutaj, że można wyobrazić sobie, jak przypadkowy funkcja Shuffle rzeczywiście jest z tym wizualizer Mike Bostock wykonane: bost.ocks.org/mike/shuffle/compare.html
Aug

5
@Blazemonger jsPref nie żyje. Czy możesz po prostu opublikować tutaj, który jest najszybszy?
eozzy

Co powiesz na jedno-liniowiec? Zwrócona tablica jest tasowana. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

Rozwiązanie redukujące ma złożoność O (n ^ 2). Spróbuj uruchomić go na tablicy z milionem elementów.
riv

Odpowiedzi:


1540

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 .


13
Powyższa odpowiedź przeskakuje elementem 0, warunek powinien być 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 == 0Math.floor...| 0
RobG

23
@prometheus, wszystkie RNG są pseudolosowe, chyba że są podłączone do drogiego sprzętu.
Phil H

38
@RobG powyższa implementacja jest funkcjonalnie poprawna. W algorytmie Fisher-Yatesa pętla nie jest przeznaczona do uruchamiania dla pierwszego elementu w tablicy. Sprawdź wikipedię, gdzie są inne implementacje, które również pomijają pierwszy element. Sprawdź także ten artykuł, który mówi o tym, dlaczego tak ważne jest, aby pętla nie uruchamiała się dla pierwszego elementu.
theon

34
@nikola „wcale nie losowy” to dla mnie trochę mocna kwalifikacja. Argumentowałbym, że jest wystarczająco losowy, chyba że jesteś kryptografem, w którym to przypadku prawdopodobnie nie używasz Math.Random ().
toon81

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

744

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


EDYCJA: Aktualizacja do ES6 / ECMAScript 2015

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

22
ps Ten sam algorytm jak odpowiedź ChristopheD, ale z wyjaśnieniem i czystszą implementacją.
Laurens Holst,

12
Ludzie przypisują niewłaściwą osobę do algorytmu. To nie tasowanie Fishera-Yatesa, ale tasowanie Durstenfelda . Prawdziwy oryginalny algorytm Fishera-Yatesa jest uruchamiany za n ^ 2, a nie n-czas
Pacerier

7
Nie jest to wymagane, return arrayponieważ 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.
Joel Trauger,

5
Implementacja w tej odpowiedzi faworyzuje dolny koniec tablicy. Przekonałem się na własnej skórze . 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.
Marjan Venema

13
@MarjanVenema Nie jestem pewien, czy nadal obserwujesz to miejsce, ale ta odpowiedź jest poprawna, a proponowana zmiana faktycznie wprowadza błąd. Zobacz blog.codinghorror.com/the-danger-of-naivete, aby uzyskać miły opis tego błędu.
user94559,

133

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

13
podoba mi się to rozwiązanie, wystarczające, aby dać podstawowy losowy
Alex K

147
Głosowanie w dół, ponieważ tak naprawdę nie jest tak losowe. Nie wiem, dlaczego ma tak wiele pozytywnych opinii. Nie używaj tej metody. Wygląda ładnie, ale nie jest całkowicie poprawny. Oto wyniki po 10 000 iteracji, ile razy każda liczba w tablicy trafia w indeks [0] (mogę podać również inne wyniki): 1 = 29,19%, 2 = 29,53%, 3 = 20,06%, 4 = 11,91%, 5 = 5,99%, 6 = 3,32%
radtad

8
W porządku, jeśli chcesz losowo relatywnie małej tablicy i nie zajmować się kryptograficznymi rzeczami. Całkowicie się zgadzam, że jeśli potrzebujesz więcej losowości , musisz użyć bardziej złożonego rozwiązania.
deadrunk


12
Problem polega na tym, że nie jest deterministyczny, co da błędne wyniki (jeśli 1> 2 i 2> 3, należy podać, że 1> 3, ale to nie gwarantuje tego. Spowoduje to dezorientację sortowania i skomentowanie wyniku przez @radtad).
MatsLindh,

73

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

42
Naprawdę nie ma z tego żadnej korzyści, IMOHO, z wyjątkiem ewentualnego nadepnięcia na czyjąś implementację.
user2864740

2
Jeśli zostanie użyty w prototypie Array, powinien być nazwany inaczej niż tylko losowo .
Wolf

57
Można (lub należy) unikać rozszerzania natywnych prototypów: javascriptweblog.wordpress.com/2011/12/05/…
Wédney Yuri

12
Nie powinieneś tego robić; każda pojedyncza tablica, na którą ma to wpływ, nie może już być bezpiecznie iterowana przy użyciu dla ... w. Nie rozszerzaj natywnych prototypów.

18
@TinyGiant Właściwie: nie używaj for...inpętli do iteracji po tablicach.
Conor O'Brien,

69

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)
  1. Umieszczamy każdy element w tablicy w obiekcie i nadajemy mu losowy klucz sortowania
  2. Sortujemy za pomocą losowego klucza
  3. Usuwamy mapowanie, aby uzyskać oryginalne obiekty

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


1
@superluminary Ups, masz rację. Zauważ, że w tej odpowiedzi zastosowano już to samo podejście.
Bergi,

@Bergi - Ach tak, masz rację, chociaż myślę, że moja implementacja jest nieco ładniejsza.
superluminarny

3
Bardzo dobrze. To jest transformacja Schwartziana w js.
Mark Grimes,

@torazaburo - Nie jest tak wydajny jak Fischer Yates, ale jest ładniejszy, a kod jest mniejszy. Kod jest zawsze kompromisem. Gdybym miał dużą tablicę, użyłbym Knutha. Gdybym miał kilkaset przedmiotów, zrobiłbym to.
superluminarny

1
@BenCarp - Uzgodniony, nie jest to najszybsze rozwiązanie i nie chciałbyś używać go na ogromnej tablicy, ale w kodzie jest więcej uwag niż w przypadku surowej prędkości.
superluminarny

64

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

12
Świetna odpowiedź! Dzięki. Wolę to od innych odpowiedzi, ponieważ zachęca ludzi do korzystania z bibliotek zamiast kopiowania i wklejania potencjalnie błędnych funkcji w dowolnym miejscu.
frabcus

60
@frabcus: Nie ma sensu włączać całej biblioteki tylko po to, aby uzyskać shufflefunkcję.
Blender

11
Nie zgadzam się z @Blender. Istnieje wiele powodów, aby dołączyć całą bibliotekę tylko w celu uzyskania potrzebnej funkcji. Jednym z nich jest to, że ryzyko błędu jest mniejsze, gdy piszesz je samodzielnie. Jeśli jest to problem z wydajnością, nie powinieneś go używać. Ale to, że może to być problem z wydajnością, nie oznacza, że ​​tak będzie.
Daniel Kaplan

7
@tieTYT: Więc po co ci reszta biblioteki? Tasowanie Fisher-Yates jest banalne. Nie potrzebujesz biblioteki, aby wybrać losowy element z tablicy (mam nadzieję), więc nie ma powodu, aby używać biblioteki, chyba że faktycznie zamierzasz użyć z niej więcej niż jednej funkcji.
Blender

18
@Blender: Podałem powód. 1) Zapewniam cię, że możesz wprowadzić błąd do dowolnego kodu, który piszesz, bez względu na to, jak trywialny jest. Po co ryzykować? 2) Nie optymalizuj wstępnie. 3) W 99% przypadków, gdy potrzebujesz losowego algo, w Twojej aplikacji nie chodzi o pisanie losowego algo. Chodzi o coś, co wymaga przetasowania. Wykorzystuj pracę innych. Nie myśl o szczegółach implementacji, chyba że musisz.
Daniel Kaplan

50

NOWY!

Krótszy i prawdopodobnie * szybszy algorytm losowania Fisher-Yatesa

  1. używa podczas ---
  2. bitowe do podłogi (liczby do 10 cyfr dziesiętnych (32 bity))
  3. usunięte niepotrzebne zamknięcia i inne rzeczy

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

6
sprawdź wydajność ... 2x szybciej w większości przeglądarek ... ale potrzebuje więcej testerów jsperf ...
cocco

10
js jest językiem, który akceptuje wiele skrótów i różne sposoby pisania .. podczas gdy jest tu wiele wolno czytelnych funkcji, chcę tylko pokazać, jak można to zrobić w bardziej wydajny sposób, oszczędzając również niektóre bajty ... bitowe i Stenografia jest tutaj naprawdę niedoceniana, a sieć jest pełna błędów i powolnego kodu.
cocco

Nie jest to szybki wzrost wydajności. Zamieniając fyi shuffle prototype, fykonsekwentnie 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
Spig

sprawdź statystyki jeszcze raz ... już napisałem chrom jest wolniejszy, ponieważ zoptymalizowali matematykę, na wszystkich innych bitowych podłogach i jednocześnie jest szybszy. sprawdź IE, Firefox, ale także urządzenia mobilne. Miło byłoby też zobaczyć operę ...
cocco

1
To okropna odpowiedź. SO nie jest konkursem zaciemniającym.
Szczeniak

39

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>


Fajny, ale czy za każdym razem generuje kompletne losowe elementy?
DDD

Nie jestem pewien, czy dobrze cię zrozumiałem. Takie podejście rzeczywiście losowo przetasuje tablicę (choć pseudolosowo) za każdym razem, gdy wywołasz tablicę sortowania - z oczywistych powodów nie jest to sortowanie stabilne.
Kris Selbekk

4
Z tych samych powodów, które wyjaśniono na stackoverflow.com/a/18650169/28234 . O wiele bardziej prawdopodobne jest pozostawienie wczesnych elementów w pobliżu początku tablicy.
AlexC

7
Jest to świetny, łatwy w użyciu jeden linijka, gdy trzeba zmieszać tablicę, ale nie przejmuj się zbytnio losowymi wynikami. Czasami to ostatnie kilka cali do perfekcji zajmuje więcej czasu niż jest to warte.
Daniel Griscom,

1
Byłoby pięknie, gdyby to zadziałało, ale tak nie jest. Ze względu na sposób szybkiego wyszukiwania niespójny komparator prawdopodobnie pozostawi elementy tablicy blisko ich pierwotnej pozycji. Twoja tablica nie będzie kodowana.
superluminarny

39

Niezawodny, wydajny, krótki

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

Test niezawodności i wydajności

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

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

Gdzie jest ES6 (ES2015)? [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ół?
szeryf

@sheriffderek Tak, funkcja ES6, której używam, to przypisanie dwóch zmiennych jednocześnie, co pozwala nam zamienić dwie zmienne w jednym wierszu kodu.
Ben Carp

Podziękowania dla @sheriffderek, który zasugerował rosnący algorytm. Algorytm wstępujący można udowodnić indukcyjnie.
Ben Carp

23

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

3
Powinniśmy zachęcać ludzi do używania _.shuffle zamiast wklejania kodu z przepełnienia stosu; i powinniśmy zniechęcać ludzi do kompresowania odpowiedzi na przepełnienie stosu. Po to jest jsmin.
David Jones

45
@DavidJones: Dlaczego miałbym dołączać całą bibliotekę 4kb tylko po to, aby przetasować tablicę?
Blender

1
Wywołanie nazwy @KingKongFrog również nie sprzyja zgromadzeniu rozsądnej społeczności.
pszenicy

2
czy efektywnie jest robić to var b = w pętli zamiast deklarować b poza pętlą i przypisywać ją b = w pętli?
Alex K

2
@Brian nie zrobi różnicy; podnoszenie ma miejsce podczas analizowania kodu źródłowego. Prawdopodobnie nie był zaangażowany.
user2864740

23

Edycja: ta odpowiedź jest niepoprawna

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.


17

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

4
Aby obciąć, należy użyć n >>> 0zamiast ~~n. Indeksy tablic mogą być wyższe niż 2³¹-1.
Oriol


14

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:

  1. Właściwie losowy
  2. Nie na miejscu (stąd shufflednazwa zamiast shuffle)
  3. Nie ma go tutaj w wielu wariantach

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

(Podejrzewam, że został usunięty, ponieważ jest to bardzo nieefektywny sposób losowego przydzielania tablicy, szczególnie w przypadku większych tablic ... podczas gdy zaakceptowana odpowiedź i kilka innych klonów tej odpowiedzi losowo losuje na miejscu).
WiredPrairie

1
Tak, ale biorąc pod uwagę, że dobrze znana zła odpowiedź wciąż jest pełna głosów, należy przynajmniej wspomnieć o nieefektywnym, ale poprawnym rozwiązaniu.
Daniel Martin

[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
Daniel Martin

3
Musisz użyć, .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 10za mniejszy od tego 2czasu1 mniej niż 2.
4castle,

@ 4castle Dobra, zaktualizowałem kod, ale zamierzam go cofnąć: rozróżnienie między porządkiem leksykograficznym a porządkiem numerycznym nie ma znaczenia dla liczb w zakresie, który 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))
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;
};

Nie jest to oczywiście tak optymalne jak algorytm Fishera-Yatesa, ale czy zadziałałoby w przypadku wywiadów technicznych?
davidatthepark

@Andrea Kod został uszkodzony z powodu zmiany długości tablicy w pętli for. Przy ostatniej edycji jest to poprawiane.
Charlie Wallace,

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

1
Dlaczego minus 0,5? Co oznacza ta liczba?
Sartheris Stormhammer

1
@SartherisStormhammer, ponieważ do sortowania używamy funkcji porównajFunkcja, a jeśli to zwróci liczbę większą niż 0, porównywane elementy zostaną uporządkowane tylko w kierunku. -0.5 na Math.random () da nam liczbę ujemną ~ 50% czasu, co da nam odwrotną kolejność.
Sam Doidge

Proste i najprostsze rozwiązanie. Dzięki
deanwilliammills

9

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


Ten algorytm od dawna okazał się wadliwy.

Proszę, udowodnij mi. Opierałem się na w3schools
Tính Ngô Quang

4
Możesz przeczytać wątek na css-tricks.com/snippets/javascript/shuffle-array lub na news.ycombinator.com/item?id=2728914 . W3schools zawsze było i pozostaje okropnym źródłem informacji.

Aby uzyskać dobrą dyskusję na temat tego, dlaczego nie jest to dobre podejście, zobacz stackoverflow.com/questions/962802/...
Charlie Wallace

8

Rozwiązanie rekurencyjne:

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

7

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 ordersortowanie 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ż indexOfdziała w czasie O (n).


Array.prototype.sortprzekazuje dwie wartości jako ai b, a nie indeks. Więc ten kod nie działa.
gregers

@gregers masz rację, zredagowałem odpowiedź. Dzięki.
Milo Wielondek

1
To nie jest zbyt losowe. W zależności od implementacji sortowania element o najniższym indeksie tablicowym może wymagać więcej porównań w celu uzyskania najwyższego indeksu niż element obok najwyższego indeksu. Oznacza to, że element o najniższym indeksie ma mniejsze szanse na uzyskanie najwyższego indeksu.
1 'LUB 1 -

7

funkcja losowania, która nie zmienia tablicy źródłowej

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

Jest to zasadniczo oryginalny algorytm Fishera-Yatesa, spliceponieważ 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.

@torazaburo, dziękuję za opinię. Zaktualizowałem swoją odpowiedź, aby wyjaśnić, że raczej oferuję ładnie wyglądające rozwiązanie niż super-skalujące
Evgenia Manolova

Możemy również użyć splicemetody, aby utworzyć kopię tak: source = array.slice();.
Taiga

6

Oto najprostszy jeden,

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

na przykład możesz to sprawdzić tutaj


5

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

Jaką wartość zapewnia dodanie ścisłego użycia w stosunku do zaakceptowanej odpowiedzi?
shortstuffsushi,

Aby dowiedzieć się więcej o trybie ścisłym i jego wpływie na wydajność, przeczytaj o nim tutaj: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Raphael C

Hmm, czy mógłbyś wskazać coś konkretnego z przywołanego dokumentu? Wydaje się, że nic nie wskazuje na „poprawę wydajności”, poza niejasnym komentarzem na górze na temat potencjalnie utrudniającym optymalizację silnika js. W tym przypadku nie jest dla mnie jasne, jakie zastosowanie ścisłe poprawiłoby.
shortstuffsushi,

Tryb ścisły istnieje już od dłuższego czasu i istnieje wystarczająca ilość odczytów, aby każdy mógł wydać własną opinię, czy powinien zawsze go używać, czy nie i dlaczego. Na przykład Jslint wyjaśnia, że ​​należy zawsze używać trybu ścisłego. Douglas Crockford napisał sporo artykułów i świetnych filmów, dlaczego ważne jest, aby zawsze stosować tryb ścisły nie tylko jako dobrą praktykę, ale także jak inaczej jest on interpretowany przez silniki js przeglądarki, takie jak V8. Zdecydowanie radzę go Google'owi wyrazić na ten temat.
Raphael C

Oto stary wątek o perfs w trybie ścisłym, nieco stary, ale wciąż aktualny: stackoverflow.com/questions/3145966/…
Raphael C

5

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-Yatesalgorytmu, przy wykorzystaniu Web Cryptography APIna 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));


4

Nowoczesne krótkie rozwiązanie wbudowane wykorzystujące funkcje ES6:

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

(do celów edukacyjnych)


4

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

4

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)

4

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


3

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; 

Nie powinno być -1for za, jak <nie <=
używałeś


3

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


Zdecydowanie elegancki!
Gershom
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.