Pomagałem komuś z jego kodem JavaScript i moje oczy zwróciły się na sekcję, która wyglądała tak:
function randOrd(){
return (Math.round(Math.random())-0.5);
}
coords.sort(randOrd);
alert(coords);
Moja pierwsza myśl brzmiała: hej, to nie może zadziałać! Ale potem trochę poeksperymentowałem i stwierdziłem, że rzeczywiście przynajmniej wydaje się dostarczać ładnie zrandomizowanych wyników.
Potem przeszukałem sieć i prawie na górze znalazłem artykuł, z którego ten kod został najdokładniej skopiowany. Wyglądało na całkiem przyzwoitą witrynę i autora ...
Ale przeczucie podpowiada mi, że to musi być złe. Zwłaszcza, że algorytm sortowania nie jest określony przez standard ECMA. Myślę, że różne algorytmy sortowania spowodują różne niejednorodne tasowania. Niektóre algorytmy sortowania mogą prawdopodobnie zapętlić się w nieskończoność ...
Ale co o tym myślisz?
A jako kolejne pytanie ... jak mam teraz zmierzyć, jak losowe są wyniki tej techniki tasowania?
aktualizacja: wykonałem kilka pomiarów i zamieściłem wyniki poniżej jako jedną z odpowiedzi.
źródło
Odpowiedzi:
To nigdy nie był mój ulubiony sposób tasowania, częściowo dlatego, że jest specyficzny dla implementacji, jak mówisz. W szczególności, wydaje mi się, że średnia z obu sortowania biblioteki Java lub .NET (nie wiem który) często można wykryć, jeśli kończy się z niespójnym porównania pomiędzy elementami (np ciebie pierwszego zastrzeżenia
A < B
iB < C
, ale potemC < A
).Kończy się również jako bardziej złożone (pod względem czasu wykonania) przetasowanie, niż naprawdę potrzebujesz.
Preferuję algorytm tasowania, który efektywnie dzieli kolekcję na „shuffled” (na początku kolekcji, początkowo pustą) i „unshuffled” (reszta kolekcji). Na każdym kroku algorytmu wybierz losowy nieprzetasowany element (może to być pierwszy) i zamień go na pierwszy nieprzetasowany element - następnie potraktuj go jako przetasowany (tj. Przesuń w myślach partycję, aby go uwzględnić).
To jest O (n) i wymaga tylko n-1 wywołań generatora liczb losowych, co jest miłe. Tworzy również prawdziwe tasowanie - każdy element ma 1 / n szansy, że znajdzie się w każdym polu, niezależnie od jego pierwotnej pozycji (zakładając rozsądny RNG). Posortowana wersja zbliża się do równomiernego rozkładu (zakładając, że generator liczb losowych nie wybiera dwukrotnie tej samej wartości, co jest wysoce nieprawdopodobne, jeśli zwraca losowe liczby podwójne), ale łatwiej jest mi rozumować co do wersji losowej :)
To podejście nazywa się tasowaniem Fishera-Yatesa .
Uważam, że najlepszą praktyką jest jednorazowe zakodowanie tego tasowania i ponowne użycie go wszędzie tam, gdzie trzeba tasować elementy. Wtedy nie musisz martwić się o implementacje sortowania pod względem niezawodności lub złożoności. To tylko kilka wierszy kodu (których nie będę próbował w JavaScript!)
Artykuł Wikipedii o tasowaniu (aw szczególności sekcja algorytmów tasowania) mówi o sortowaniu losowej projekcji - warto przeczytać sekcję o słabych implementacjach tasowania w ogóle, aby wiedzieć, czego unikać.
źródło
2^x
stany dla każdego indeksu tablicy, tj. Będzie łącznie 2 ^ (xn) stanów, co powinno być trochę większe niż 2 ^ c - szczegóły w mojej zredagowanej odpowiedziPo tym, jak Jon omówił już teorię , oto implementacja:
Algorytm jest
O(n)
, a sortowanie powinnoO(n log n)
. W zależności od obciążenia związanego z wykonywaniem kodu JS w porównaniu zsort()
funkcją natywną może to prowadzić do zauważalnej różnicy w wydajności, która powinna rosnąć wraz z rozmiarami tablic.W komentarzach do odpowiedzi bobobobo stwierdziłem, że omawiany algorytm może nie dawać równomiernie rozłożonych prawdopodobieństw (w zależności od implementacji
sort()
).Mój argument idzie w tym kierunku: algorytm sortowania wymaga pewnej liczby
c
porównań, np.c = n(n-1)/2
Dla Bubblesort. Nasza funkcja porównania losowego sprawia, że wynik każdego porównania jest równie prawdopodobny, tj. Istnieją2^c
równie prawdopodobne wyniki. Teraz każdy wynik musi odpowiadać jednej zn!
permutacji wpisów tablicy, co w ogólnym przypadku uniemożliwia równomierne rozłożenie. (Jest to uproszczenie, ponieważ rzeczywista liczba niezbędnych porównań zależy od tablicy wejściowej, ale stwierdzenie powinno nadal obowiązywać).Jak zauważył Jon, to samo w sobie nie jest powodem, aby preferować Fisher-Yatesa od używania
sort()
, ponieważ generator liczb losowych mapuje również skończoną liczbę wartości pseudolosowych don!
permutacji. Ale wyniki Fisher-Yatesa powinny być nadal lepsze:Math.random()
tworzy liczbę pseudolosową z zakresu[0;1[
. Ponieważ JS używa wartości zmiennoprzecinkowych o podwójnej precyzji, odpowiada to2^x
możliwym wartościom gdzie52 ≤ x ≤ 63
(jestem zbyt leniwy, aby znaleźć rzeczywistą liczbę). Rozkład prawdopodobieństwa wygenerowany za pomocąMath.random()
przestanie działać dobrze, jeśli liczba zdarzeń atomowych jest tego samego rzędu wielkości.Używając Fishera-Yatesa, odpowiednim parametrem jest rozmiar tablicy, która nigdy nie powinna się zbliżać
2^52
ze względu na ograniczenia praktyczne.Podczas sortowania za pomocą funkcji porównania losowego funkcja zasadniczo dba o to, czy wartość zwracana jest dodatnia czy ujemna, więc nigdy nie będzie to problemem. Ale jest podobny: ponieważ funkcja porównania zachowuje się dobrze,
2^c
możliwe wyniki są, jak stwierdzono, równie prawdopodobne. Jeślic ~ n log n
wtedy2^c ~ n^(a·n)
gdziea = const
, co sprawia, że jest przynajmniej możliwe, że2^c
ma taką samą wielkość jak (lub nawet mniej niż),n!
a tym samym prowadzi do nierównomiernego rozkładu, nawet jeśli algorytm sortowania zakłada równomierne mapowanie na permutacje. Jeśli to ma jakikolwiek praktyczny wpływ, jest poza mną.Prawdziwym problemem jest to, że algorytmy sortowania nie gwarantują równomiernego odwzorowania na permutacje. Łatwo zauważyć, że Mergesort działa tak, jak jest symetryczny, ale rozumowanie o czymś takim jak Bubblesort lub, co ważniejsze, Quicksort lub Heapsort, nie jest.
Podsumowując: tak długo, jak
sort()
używasz Mergesort, powinieneś być w miarę bezpieczny, z wyjątkiem przypadków narożnych (przynajmniej mam nadzieję, że2^c ≤ n!
jest to przypadek narożny), jeśli nie, wszystkie zakłady są wyłączone.źródło
Zrobiłem kilka pomiarów tego, jak losowe są wyniki tego losowego sortowania ...
Moja technika polegała na pobraniu małej tablicy [1, 2, 3, 4] i utworzeniu jej wszystkich (4! = 24) permutacji. Następnie zastosowałbym funkcję tasowania do tablicy wiele razy i policzyłbym, ile razy każda permutacja jest generowana. Dobry algorytm tasowania rozłożyłby wyniki dość równomiernie na wszystkie permutacje, podczas gdy zły nie dałby takiego jednolitego wyniku.
Korzystając z poniższego kodu testowałem w Firefox, Opera, Chrome, IE6 / 7/8.
Zaskakujące jest dla mnie, że losowe sortowanie i prawdziwe tasowanie stworzyły równie jednolite rozkłady. Wygląda więc na to, że (jak wielu sugerowało) główne przeglądarki używają sortowania przez scalanie. Nie oznacza to oczywiście, że nie może być przeglądarki, która robi inaczej, ale powiedziałbym, że oznacza to, że ta metoda losowego sortowania jest wystarczająco niezawodna, aby można ją było zastosować w praktyce.EDYCJA: Ten test nie zmierzył poprawnie losowości lub jej braku. Zobacz inną odpowiedź, którą opublikowałem.
Ale jeśli chodzi o wydajność, funkcja shuffle nadana przez Cristopha była wyraźnym zwycięzcą. Nawet w przypadku małych czteroelementowych tablic rzeczywiste tasowanie przebiegało około dwa razy szybciej niż sortowanie losowe!
źródło
Co ciekawe, Microsoft zastosował tę samą technikę w swojej losowej-stronie-przeglądarki.
Użyli nieco innej funkcji porównania:
Dla mnie wygląda prawie tak samo, ale okazało się, że nie jest taki przypadkowy ...
Zrobiłem więc ponownie kilka testów z tą samą metodologią, co w połączonym artykule i rzeczywiście - okazało się, że metoda losowego sortowania dała błędne wyniki. Nowy kod testowy tutaj:
źródło
sort()
ma zwrócić liczbę większą niż, mniejszą lub równą zeru, w zależności od porównaniaa
ib
. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )Umieściłem prostą stronę testową w mojej witrynie, pokazującą stronniczość Twojej obecnej przeglądarki w porównaniu z innymi popularnymi przeglądarkami używającymi różnych metod odtwarzania losowego. Pokazuje straszną stronniczość zwykłego używania
Math.random()-0.5
, kolejne „losowe” tasowanie, które nie jest stronnicze, oraz wspomnianą powyżej metodę Fishera-Yatesa.Widać, że w niektórych przeglądarkach istnieje aż 50% szansa, że pewne elementy w ogóle nie zmienią się podczas „tasowania”!
Uwaga: możesz nieco przyspieszyć implementację tasowania Fisher-Yates przez @Christoph dla Safari, zmieniając kod na:
Wyniki testu: http://jsperf.com/optimized-fisher-yates
źródło
Myślę, że jest to dobre w przypadkach, w których nie jesteś wybredny w dystrybucji i chcesz, aby kod źródłowy był mały.
W JavaScript (gdzie źródło jest stale przesyłane), mały ma wpływ na koszty przepustowości.
źródło
arr = arr.map(function(n){return [Math.random(),n]}).sort().map(function(n){return n[1]});
, co ma tę zaletę, że nie jest zbyt strasznie dużo dłuższe i właściwie dystrybuowane. Istnieją również bardzo skompresowane warianty odtwarzania losowego Knuth / FY.arr = arr.map(function(n){return [Math.random(),n];}).sort().map(function(n){return n[1];});
.To z pewnością hack. W praktyce nieskończenie zapętlony algorytm jest mało prawdopodobny. Jeśli sortujesz obiekty, możesz zapętlić tablicę coords i zrobić coś takiego:
(a następnie ponownie przejrzyj je, aby usunąć sortValue)
Wciąż jednak hack. Jeśli chcesz zrobić to ładnie, musisz to zrobić na własnej skórze :)
źródło
Minęły cztery lata, ale chciałbym zwrócić uwagę, że metoda losowego komparatora nie będzie poprawnie dystrybuowana, niezależnie od używanego algorytmu sortowania.
Dowód:
n
elementów istnieją dokładnien!
permutacje (tj. Możliwe przetasowania).Jedyne rozmiary, które mogłyby być poprawnie rozmieszczone, to n = 0,1,2.
W ramach ćwiczenia spróbuj narysować drzewo decyzyjne różnych algorytmów sortowania dla n = 3.
Istnieje luka w dowodzie: jeśli algorytm sortowania zależy od spójności komparatora i ma nieograniczony czas działania z niespójnym komparatorem, może mieć nieskończoną sumę prawdopodobieństw, które mogą sumować się do 1/6, nawet jeśli każdy mianownik w sumie to potęga 2. Spróbuj znaleźć jeden.
Ponadto, jeśli komparator ma stałą szansę na udzielenie którejkolwiek odpowiedzi (np.
(Math.random() < P)*2 - 1
StałejP
), powyższy dowód jest ważny. Jeśli zamiast tego komparator zmieni swoje kursy na podstawie wcześniejszych odpowiedzi, możliwe będzie wygenerowanie uczciwych wyników. Znalezienie takiego komparatora dla danego algorytmu sortowania mogłoby być pracą badawczą.źródło
Jeśli używasz D3, jest wbudowana funkcja tasowania (używając Fisher-Yates):
A oto Mike omawia szczegóły na ten temat:
http://bost.ocks.org/mike/shuffle/
źródło
Oto podejście wykorzystujące pojedynczą tablicę:
Podstawowa logika to:
Kod:
źródło
Czy możesz użyć
Array.sort()
funkcji do tasowania tablicy - tak.Czy wyniki są wystarczająco losowe - Nie.
Rozważ następujący fragment kodu:
Przykładowe dane wyjściowe:
W idealnym przypadku liczby powinny być równomiernie rozłożone (w powyższym przykładzie wszystkie zliczenia powinny wynosić około 20). Ale tak nie jest. Najwyraźniej dystrybucja zależy od tego, jaki algorytm sortowania jest zaimplementowany przez przeglądarkę i jak iteruje elementy tablicy do sortowania.
Więcej informacji znajduje się w tym artykule:
Array.sort () nie powinien być używany do shuffle tablicy
źródło
Nie ma w tym nic złego.
Funkcja, którą przekazujesz do .sort () zwykle wygląda podobnie
Twoim zadaniem w sortowaniuFunc jest powrót:
Powyższa funkcja sortowania porządkuje rzeczy.
Jeśli wrócisz losowo i + jako to, co masz, otrzymasz losową kolejność.
Jak w MySQL:
źródło
shuffle()
wystarczy napisać tylko raz, więc nie stanowi to problemu: po prostu umieść fragment kodu w skarbcu kodu i odkop go, kiedy tylko potrzebujesz