Jaki jest najlepszy sposób na losowanie tablicy ciągów w .NET? Moja tablica zawiera około 500 ciągów i chciałbym utworzyć nową Arrayz tymi samymi ciągami, ale w losowej kolejności.
Korzystając z pakietu MedallionRandom NuGet, jest to po prostu myArray.Shuffled().ToArray()(lub myArray.Shuffle()jeśli chcesz zmutować bieżącą tablicę)
ChaseMedallion
Odpowiedzi:
171
Jeśli korzystasz z .NET 3.5, możesz użyć następującego chłodu IEnumerable (VB.NET, nie C #, ale pomysł powinien być jasny ...):
Random rnd=newRandom();string[]MyRandomArray=MyArray.OrderBy(x => rnd.Next()).ToArray();
Edycja: OK, a oto odpowiedni kod VB.NET:
Dim rnd AsNewSystem.RandomDimMyRandomArray=MyArray.OrderBy(Function() rnd.Next()).ToArray()
Druga edycja, w odpowiedzi na uwagi, że System.Random „nie jest bezpieczny wątkowo” i „nadaje się tylko do aplikacji zabawek” ze względu na zwracanie sekwencji opartej na czasie: w moim przykładzie Random () jest całkowicie bezpieczna dla wątków, chyba że pozwalasz na ponowne wprowadzenie procedury, w której losujesz tablicę, w takim przypadku będziesz potrzebować czegoś podobnego, lock (MyRandomArray)aby nie uszkodzić danych, co również będzie chronić rnd.
Należy również dobrze zrozumieć, że System.Random jako źródło entropii nie jest zbyt mocne. Jak wspomniano w dokumentacji MSDN , powinieneś używać czegoś pochodzącego z, System.Security.Cryptography.RandomNumberGeneratorjeśli robisz coś związanego z bezpieczeństwem. Na przykład:
dwie uwagi: 1) System.Random nie jest bezpieczny dla wątków (zostałeś ostrzeżony) i 2) System.Random jest oparty na czasie, więc jeśli używasz tego kodu w bardzo współbieżnym systemie, możliwe jest uzyskanie dwóch żądań ta sama wartość (tj. w aplikacjach internetowych)
therealhoff
2
Aby wyjaśnić powyższe, System.Random zasieje się używając bieżącego czasu, więc dwie instancje utworzone jednocześnie wygenerują tę samą „losową” sekwencję. System.Random powinien być używany tylko w aplikacjach zabawek
therealhoff
8
Również ten algorytm ma wartość O (n log n) i jest obciążony algorytmem Qsort. Zobacz moją odpowiedź na O (n) bezstronne rozwiązanie.
Matt Howells
9
O ile OrderByklucze sortowania nie są przechowywane w pamięci podręcznej wewnętrznie, występuje również problem z naruszeniem właściwości przechodnich uporządkowanych porównań. Jeśli kiedykolwiek pojawi się weryfikacja trybu debugowania, która OrderBydała prawidłowe wyniki, teoretycznie może zgłosić wyjątek.
Następująca implementacja używa algorytmu Fishera-Yatesa znanego również jako Shuffle Knutha. Działa w czasie O (n) i tasuje w miejscu, więc jest lepsza niż technika „sortowania losowo”, chociaż zawiera więcej linii kodu. Zobacz tutaj kilka porównawczych pomiarów wydajności. Użyłem System.Random, co jest dobre do celów innych niż kryptograficzne. *
staticclassRandomExtensions{publicstaticvoidShuffle<T>(thisRandom rng, T[]array){int n =array.Length;while(n >1){int k = rng.Next(n--);
T temp =array[n];array[n]=array[k];array[k]= temp;}}}
Stosowanie:
vararray=newint[]{1,2,3,4};var rng =newRandom();
rng.Shuffle(array);
rng.Shuffle(array);// different order from first call to Shuffle
* W przypadku dłuższych tablic, aby (bardzo duża) liczba permutacji była równie prawdopodobna, konieczne byłoby uruchomienie generatora liczb pseudolosowych (PRNG) przez wiele iteracji dla każdej zamiany, aby wytworzyć wystarczającą entropię. Dla tablicy 500-elementowej tylko bardzo mały ułamek możliwych 500! permutacje będzie można uzyskać za pomocą PRNG. Niemniej jednak algorytm Fishera-Yatesa jest bezstronny i dlatego tasowanie będzie tak dobre, jak RNG, którego używasz.
@Ken Kin: Nie, to byłoby złe. Powodem jest to, że new Random()jest inicjowany wartością początkową opartą na bieżącym czasie systemowym, który aktualizuje się co ~ 16 ms.
Matt Howells
W niektórych szybkich testach tego rozwiązania w porównaniu z listą removeAt, jest mała różnica przy 999 elementach. Różnica staje się drastyczna przy 99999 losowych liczb całkowitych, z tym rozwiązaniem przy 3 ms, a drugim przy 1810 ms.
galamdring
18
Szukasz algorytmu tasowania, prawda?
Okay, są na to dwa sposoby: sprytny-ale-ludzie-zawsze-wydają się-niezrozumieli-i-źle-zrozumieli-tak-może-to-nie-tak-sprytny-mimo wszystko sposób i sposób głupi-jak-skały-ale-kogo to obchodzi, ponieważ-działa.
Głupi sposób
Utwórz kopię swojej pierwszej tablicy, ale oznacz każdy ciąg z losową liczbą.
Sortuj zduplikowaną tablicę według liczby losowej.
Ten algorytm działa dobrze, ale upewnij się, że generator liczb losowych prawdopodobnie nie oznaczy dwóch ciągów tą samą liczbą. Z powodu tak zwanego Paradoksu Urodzinowego zdarza się to częściej, niż można by się spodziewać. Jego złożoność czasowa wynosi O ( n log n ).
Sprytny sposób
Opiszę to jako algorytm rekurencyjny:
Aby przetasować tablicę o rozmiarze n (indeksy w zakresie [0 .. n -1]):
jeśli n = 0
nic nie robić
jeśli n > 0
(krok rekurencyjny) tasuje pierwsze n- 1 elementy tablicy
wybierz losowy indeks, x , z zakresu [0 .. n -1]
zamień element o indeksie n -1 z elementem o indeksie x
Iteracyjnym odpowiednikiem jest przeprowadzenie iteratora po tablicy, zamieniając się losowymi elementami w trakcie, ale zauważ, że nie można zamienić elementu po tym, na który wskazuje iterator. Jest to bardzo częsty błąd i prowadzi do stronniczego tasowania.
Ten algorytm jest prosty, ale nie wydajny, O (N 2 ). Wszystkie algorytmy „uporządkowane według” są zwykle O (N log N). Prawdopodobnie nie robi różnicy poniżej setek tysięcy elementów, ale miałoby to miejsce w przypadku dużych list.
var stringlist =...// add your values to stringlistvar r =newRandom();var res =newList<string>(stringlist.Count);while(stringlist.Count>0){var i = r.Next(stringlist.Count);
res.Add(stringlist[i]);
stringlist.RemoveAt(i);}
Powód, dla którego to O (N 2 ) jest subtelny: List.RemoveAt () jest operacją O (N), chyba że usuniesz w kolejności od końca.
Ma to taki sam efekt jak tasowanie knutha, ale nie jest tak wydajne, ponieważ wiąże się z wyludnieniem jednej listy i ponownym zapełnieniem innej. Lepszym rozwiązaniem byłaby wymiana elementów na miejscu.
Nick Johnson,
1
Uważam to za eleganckie i łatwe do zrozumienia, a na strunach 500 nie robi to żadnej różnicy ...
Sklivvz
4
Możesz także zrobić metodę rozszerzenia z Matta Howellsa. Przykład.
namespace System{publicstaticclassMSSystemExtenstions{privatestaticRandom rng =newRandom();publicstaticvoidShuffle<T>(this T[]array){
rng =newRandom();int n =array.Length;while(n >1){int k = rng.Next(n);
n--;
T temp =array[n];array[n]=array[k];array[k]= temp;}}}}
dlaczego odtwarzasz rng przy każdym wywołaniu metody ... Deklarujesz to na poziomie klasy, ale używasz jako lokalnego ...
Yaron
1
Losowanie tablicy jest intensywne, ponieważ musisz zmieniać kilka ciągów. Dlaczego nie po prostu losowo odczytać z tablicy? W najgorszym przypadku możesz nawet utworzyć klasę opakowującą za pomocą metody getNextString (). Jeśli naprawdę potrzebujesz utworzyć losową tablicę, możesz zrobić coś takiego
for i =0-> i=array.length *5
swap two strings in random places
Losowy odczyt z tablicy prawdopodobnie uderzy kilka razy w niektóre elementy i przegapi inne!
Ray Hayes,
Algorytm odtwarzania losowego jest uszkodzony. Musiałbyś naprawdę ustawić swoje arbitralne 5 bardzo wysoko, zanim tasowanie stanie się bezstronne.
Pitarou,
Utwórz tablicę indeksów (liczb całkowitych). Przetasuj indeksy. Po prostu użyj indeksów w tej losowej kolejności. Bez duplikatów, bez tasowania odwołań do ciągów w pamięci (które mogą wywołać internalizację, a co nie).
Christopher
1
Po prostu myśląc z góry mojej głowy, możesz zrobić to:
publicstring[]Randomize(string[] input){List<string> inputList = input.ToList();string[] output =newstring[input.Length];Random randomizer =newRandom();int i =0;while(inputList.Count>0){int index = r.Next(inputList.Count);
output[i++]= inputList[index];
inputList.RemoveAt(index);}return(output);}
Wygeneruj tablicę losowych liczb zmiennoprzecinkowych lub liczb całkowitych o tej samej długości. Posortuj tę tablicę i wykonaj odpowiednie zamiany na tablicy docelowej.
Random r =newRandom();List<string>list=newList(originalArray);List<string> randomStrings =newList();while(list.Count>0){int i = r.Random(list.Count);
randomStrings.Add(list[i]);list.RemoveAt(i);}
Jacco, twoje rozwiązanie to niestandardowy IComparer nie jest bezpieczny. Procedury sortowania wymagają, aby funkcja porównująca spełniała kilka wymagań, aby działała poprawnie. Pierwsza z nich to konsekwencja. Jeśli funkcja porównująca jest wywoływana na tej samej parze obiektów, musi zawsze zwracać ten sam wynik. (porównanie musi być również przechodnie).
Niespełnienie tych wymagań może powodować wiele problemów w procedurze sortowania, w tym możliwość nieskończonej pętli.
Jeśli chodzi o rozwiązania, które kojarzą losową wartość liczbową z każdym wpisem, a następnie sortują je według tej wartości, prowadzą one do nieodłącznego odchylenia w wyniku, ponieważ za każdym razem, gdy dwóm wpisom zostanie przypisana ta sama wartość liczbowa, losowość wyniku będzie zagrożona. (W "stabilnej" procedurze sortowania, cokolwiek jest pierwsze na wejściu, będzie pierwsze w wyjściu. Array.Sort nie jest stabilne, ale nadal istnieje odchylenie oparte na partycjonowaniu wykonanym przez algorytm Quicksort).
Musisz trochę pomyśleć o tym, jakiego poziomu losowości potrzebujesz. Jeśli prowadzisz serwis pokerowy, w którym potrzebujesz kryptograficznych poziomów losowości, aby chronić się przed zdeterminowanym napastnikiem, masz bardzo inne wymagania niż ktoś, kto chce po prostu losować listę odtwarzania utworów.
W przypadku tasowania listy utworów nie ma problemu z używaniem rozstawionego PRNG (takiego jak System.Random). W przypadku strony pokerowej nie jest to nawet opcja i musisz pomyśleć o problemie dużo trudniej niż ktokolwiek ma zamiar zrobić dla ciebie w stackoverflow. (użycie kryptograficznego RNG to dopiero początek, musisz upewnić się, że twój algorytm nie wprowadza odchylenia, że masz wystarczające źródła entropii i że nie ujawniasz żadnego stanu wewnętrznego, który zagrażałby późniejszej losowości).
Odpowiedzi na ten post są już całkiem dobre - użyj implementacji tasowania Fisher-Yatesa Durstenfelda, aby uzyskać szybki i obiektywny wynik. Opublikowano nawet kilka implementacji, chociaż zauważam, że niektóre są w rzeczywistości niepoprawne.
Ok, to ewidentnie uderzenie z mojej strony (przeprasza ...), ale często używam dość ogólnej i silnej kryptograficznie metody.
publicstaticclassEnumerableExtensions{staticreadonlyRNGCryptoServiceProviderRngCryptoServiceProvider=newRNGCryptoServiceProvider();publicstaticIEnumerable<T>Shuffle<T>(thisIEnumerable<T> enumerable){var randomIntegerBuffer =newbyte[4];Func<int> rand =()=>{RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);returnBitConverter.ToInt32(randomIntegerBuffer,0);};returnfrom item in enumerable
let rec =new{item, rnd = rand()}orderby rec.rnd
select rec.item;}}
Shuffle () jest rozszerzeniem dowolnego IEnumerable, więc na przykład pobieranie liczb od 0 do 1000 w kolejności losowej na liście można wykonać za pomocą
Enumerable.Range(0,1000).Shuffle().ToList()
Ta metoda również nie sprawi żadnych niespodzianek, jeśli chodzi o sortowanie, ponieważ wartość sortowania jest generowana i zapamiętywana dokładnie raz na element w sekwencji.
Wydaje mi się, że możesz zwiększyć zarówno wydajność, jak i czytelność, zamiast próbować przetasować tablicę, deklarując drugą tablicę, lepiej spróbuj przekonwertować ją na listę, sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
myArray.Shuffled().ToArray()
(lubmyArray.Shuffle()
jeśli chcesz zmutować bieżącą tablicę)Odpowiedzi:
Jeśli korzystasz z .NET 3.5, możesz użyć następującego chłodu IEnumerable (VB.NET, nie C #, ale pomysł powinien być jasny ...):
Edycja: OK, a oto odpowiedni kod VB.NET:
Druga edycja, w odpowiedzi na uwagi, że System.Random „nie jest bezpieczny wątkowo” i „nadaje się tylko do aplikacji zabawek” ze względu na zwracanie sekwencji opartej na czasie: w moim przykładzie Random () jest całkowicie bezpieczna dla wątków, chyba że pozwalasz na ponowne wprowadzenie procedury, w której losujesz tablicę, w takim przypadku będziesz potrzebować czegoś podobnego,
lock (MyRandomArray)
aby nie uszkodzić danych, co również będzie chronićrnd
.Należy również dobrze zrozumieć, że System.Random jako źródło entropii nie jest zbyt mocne. Jak wspomniano w dokumentacji MSDN , powinieneś używać czegoś pochodzącego z,
System.Security.Cryptography.RandomNumberGenerator
jeśli robisz coś związanego z bezpieczeństwem. Na przykład:...
...
źródło
OrderBy
klucze sortowania nie są przechowywane w pamięci podręcznej wewnętrznie, występuje również problem z naruszeniem właściwości przechodnich uporządkowanych porównań. Jeśli kiedykolwiek pojawi się weryfikacja trybu debugowania, któraOrderBy
dała prawidłowe wyniki, teoretycznie może zgłosić wyjątek.Następująca implementacja używa algorytmu Fishera-Yatesa znanego również jako Shuffle Knutha. Działa w czasie O (n) i tasuje w miejscu, więc jest lepsza niż technika „sortowania losowo”, chociaż zawiera więcej linii kodu. Zobacz tutaj kilka porównawczych pomiarów wydajności. Użyłem System.Random, co jest dobre do celów innych niż kryptograficzne. *
Stosowanie:
* W przypadku dłuższych tablic, aby (bardzo duża) liczba permutacji była równie prawdopodobna, konieczne byłoby uruchomienie generatora liczb pseudolosowych (PRNG) przez wiele iteracji dla każdej zamiany, aby wytworzyć wystarczającą entropię. Dla tablicy 500-elementowej tylko bardzo mały ułamek możliwych 500! permutacje będzie można uzyskać za pomocą PRNG. Niemniej jednak algorytm Fishera-Yatesa jest bezstronny i dlatego tasowanie będzie tak dobre, jak RNG, którego używasz.
źródło
array.Shuffle(new Random());
…?new Random()
jest inicjowany wartością początkową opartą na bieżącym czasie systemowym, który aktualizuje się co ~ 16 ms.Szukasz algorytmu tasowania, prawda?
Okay, są na to dwa sposoby: sprytny-ale-ludzie-zawsze-wydają się-niezrozumieli-i-źle-zrozumieli-tak-może-to-nie-tak-sprytny-mimo wszystko sposób i sposób głupi-jak-skały-ale-kogo to obchodzi, ponieważ-działa.
Głupi sposób
Ten algorytm działa dobrze, ale upewnij się, że generator liczb losowych prawdopodobnie nie oznaczy dwóch ciągów tą samą liczbą. Z powodu tak zwanego Paradoksu Urodzinowego zdarza się to częściej, niż można by się spodziewać. Jego złożoność czasowa wynosi O ( n log n ).
Sprytny sposób
Opiszę to jako algorytm rekurencyjny:
Iteracyjnym odpowiednikiem jest przeprowadzenie iteratora po tablicy, zamieniając się losowymi elementami w trakcie, ale zauważ, że nie można zamienić elementu po tym, na który wskazuje iterator. Jest to bardzo częsty błąd i prowadzi do stronniczego tasowania.
Złożoność czasowa wynosi O ( n ).
źródło
Ten algorytm jest prosty, ale nie wydajny, O (N 2 ). Wszystkie algorytmy „uporządkowane według” są zwykle O (N log N). Prawdopodobnie nie robi różnicy poniżej setek tysięcy elementów, ale miałoby to miejsce w przypadku dużych list.
Powód, dla którego to O (N 2 ) jest subtelny: List.RemoveAt () jest operacją O (N), chyba że usuniesz w kolejności od końca.
źródło
Możesz także zrobić metodę rozszerzenia z Matta Howellsa. Przykład.
Następnie możesz po prostu użyć go tak:
źródło
Losowanie tablicy jest intensywne, ponieważ musisz zmieniać kilka ciągów. Dlaczego nie po prostu losowo odczytać z tablicy? W najgorszym przypadku możesz nawet utworzyć klasę opakowującą za pomocą metody getNextString (). Jeśli naprawdę potrzebujesz utworzyć losową tablicę, możesz zrobić coś takiego
* 5 jest arbitralne.
źródło
Po prostu myśląc z góry mojej głowy, możesz zrobić to:
źródło
Wygeneruj tablicę losowych liczb zmiennoprzecinkowych lub liczb całkowitych o tej samej długości. Posortuj tę tablicę i wykonaj odpowiednie zamiany na tablicy docelowej.
To daje prawdziwie niezależny rodzaj.
źródło
źródło
Jacco, twoje rozwiązanie to niestandardowy IComparer nie jest bezpieczny. Procedury sortowania wymagają, aby funkcja porównująca spełniała kilka wymagań, aby działała poprawnie. Pierwsza z nich to konsekwencja. Jeśli funkcja porównująca jest wywoływana na tej samej parze obiektów, musi zawsze zwracać ten sam wynik. (porównanie musi być również przechodnie).
Niespełnienie tych wymagań może powodować wiele problemów w procedurze sortowania, w tym możliwość nieskończonej pętli.
Jeśli chodzi o rozwiązania, które kojarzą losową wartość liczbową z każdym wpisem, a następnie sortują je według tej wartości, prowadzą one do nieodłącznego odchylenia w wyniku, ponieważ za każdym razem, gdy dwóm wpisom zostanie przypisana ta sama wartość liczbowa, losowość wyniku będzie zagrożona. (W "stabilnej" procedurze sortowania, cokolwiek jest pierwsze na wejściu, będzie pierwsze w wyjściu. Array.Sort nie jest stabilne, ale nadal istnieje odchylenie oparte na partycjonowaniu wykonanym przez algorytm Quicksort).
Musisz trochę pomyśleć o tym, jakiego poziomu losowości potrzebujesz. Jeśli prowadzisz serwis pokerowy, w którym potrzebujesz kryptograficznych poziomów losowości, aby chronić się przed zdeterminowanym napastnikiem, masz bardzo inne wymagania niż ktoś, kto chce po prostu losować listę odtwarzania utworów.
W przypadku tasowania listy utworów nie ma problemu z używaniem rozstawionego PRNG (takiego jak System.Random). W przypadku strony pokerowej nie jest to nawet opcja i musisz pomyśleć o problemie dużo trudniej niż ktokolwiek ma zamiar zrobić dla ciebie w stackoverflow. (użycie kryptograficznego RNG to dopiero początek, musisz upewnić się, że twój algorytm nie wprowadza odchylenia, że masz wystarczające źródła entropii i że nie ujawniasz żadnego stanu wewnętrznego, który zagrażałby późniejszej losowości).
źródło
Odpowiedzi na ten post są już całkiem dobre - użyj implementacji tasowania Fisher-Yatesa Durstenfelda, aby uzyskać szybki i obiektywny wynik. Opublikowano nawet kilka implementacji, chociaż zauważam, że niektóre są w rzeczywistości niepoprawne.
Niedawno napisałem kilka postów na temat implementacji pełnego i częściowego tasowania przy użyciu tej techniki , a (mam nadzieję, że dodam wartość w tym drugim linku), także post o tym, jak sprawdzić, czy Twoja implementacja jest bezstronna , które można wykorzystać do sprawdzenia dowolnego algorytmu tasowania. Na końcu drugiego postu widać efekt prostego błędu w wyborze liczb losowych.
źródło
Ok, to ewidentnie uderzenie z mojej strony (przeprasza ...), ale często używam dość ogólnej i silnej kryptograficznie metody.
Shuffle () jest rozszerzeniem dowolnego IEnumerable, więc na przykład pobieranie liczb od 0 do 1000 w kolejności losowej na liście można wykonać za pomocą
Ta metoda również nie sprawi żadnych niespodzianek, jeśli chodzi o sortowanie, ponieważ wartość sortowania jest generowana i zapamiętywana dokładnie raz na element w sekwencji.
źródło
Nie potrzebujesz skomplikowanych algorytmów.
Tylko jedna prosta linia:
Należy pamiętać, że musimy przekonwertować
Array
DoList
pierwszego, jeśli nie korzystająList
w pierwszej kolejności.Pamiętaj też, że nie jest to wydajne w przypadku bardzo dużych tablic! W przeciwnym razie jest to czyste i proste.
źródło
To jest kompletne działające rozwiązanie konsoli oparte na przykładzie podanym tutaj :
źródło
źródło
Oto prosty sposób korzystania z OLINQ:
źródło
źródło
sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();