Zastanawiałem się, jaki byłby najlepszy sposób na uzyskanie dobrej losowości w bashu, tj. Jaka byłaby procedura uzyskania losowej dodatniej liczby całkowitej między MIN
i MAX
taka, że
- Zakres może być dowolnie duży (lub, powiedzmy, do 2 32 -1);
- Wartości są równomiernie rozłożone (tzn. Bez uprzedzeń);
- Jest wydajny.
Skutecznym sposobem na uzyskanie losowości w bash jest użycie $RANDOM
zmiennej. Jednak to tylko próbki wartości od 0 do 2 15 -1, które mogą nie być wystarczająco duże do wszystkich celów. Ludzie zwykle używają modulo, aby uzyskać pożądany zakres, np.
MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))
To dodatkowo tworzy błąd, chyba że $MAX
podzielisz 2 15 -1 = 32767. Np. Jeśli $MIN
wynosi 0 i $MAX
wynosi 9, wówczas wartości od 0 do 7 są nieco bardziej prawdopodobne niż wartości 8 i 9, ponieważ $RANDOM
nigdy nie będzie to 32768 lub 32769. Ta tendencja pogarsza się wraz ze wzrostem zakresu, np. Jeśli $MIN
wynosi 0 i $MAX
wynosi 9999, a liczba od 0 do 2767 mają prawdopodobieństwo 4 / 32767 , a liczba 2768 do 9999 mają tylko prawdopodobieństwo 3 / 32767 .
Tak więc, chociaż powyższa metoda spełnia warunek 3, nie spełnia warunków 1 i 2.
Najlepszą metodą, jaką do tej pory wymyśliłem, próbując spełnić warunki 1 i 2, było /dev/urandom
:
MIN=0
MAX=1234567890
while
rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
[ -z $rnd ] && rnd=0
(( $rnd < $MIN || $rnd > $MAX ))
do :
done
Zasadniczo po prostu zbieraj losowość od /dev/urandom
( /dev/random
możesz rozważyć użycie zamiast tego, jeśli pożądany jest silny kryptograficznie generator liczb pseudolosowych i jeśli masz dużo czasu, a może sprzętowy generator liczb losowych), usuń każdy znak, który nie jest cyfrą dziesiętną, złóż wyjście na długość $MAX
i wycięcie wiodących zer. Jeśli zdarzyło nam się dostać tylko 0, to $rnd
jest puste, więc w tym przypadku ustaw rnd
na 0
. Sprawdź, czy wynik jest poza naszym zakresem, a jeśli tak, to powtórz. Zmusiłem „ciało” pętli while do osłony tutaj, aby wymusić wykonanie ciała co najmniej raz, w duchu naśladowania do ... while
pętli, ponieważ rnd
na początek jest niezdefiniowany.
Myślę, że spełniłem tutaj warunki 1 i 2, ale teraz spieprzyłem warunek 3. To trochę powolne. Zajmuje to mniej więcej sekundę (dziesięć sekund, kiedy mam szczęście). W rzeczywistości nie ma nawet gwarancji, że pętla się zakończy (chociaż prawdopodobieństwo zakończenia zbiega się z 1 wraz ze wzrostem czasu).
Czy istnieje skuteczny sposób na uzyskanie bezstronnych losowych liczb całkowitych, w ramach wcześniej określonego i potencjalnie dużego zakresu, w bash? (Będę kontynuował badanie, kiedy czas pozwoli, ale tymczasem myślałem, że ktoś tutaj może mieć fajny pomysł!)
Tabela odpowiedzi
Najbardziej podstawowym (i stąd przenośnym) pomysłem jest wygenerowanie losowego ciągu bitów na tyle długo, aby był wystarczający. Istnieją różne sposoby generowania losowego ciągu bitów, albo używając wbudowanej
$RANDOM
zmiennej bash, albo używającod
i/dev/urandom
(lub/dev/random
). Jeśli liczba losowa jest większa niż$MAX
, zacznij od nowa.Alternatywnie można użyć narzędzi zewnętrznych.
- Rozwiązanie Perla
- Pro: dość przenośny, prosty, elastyczny
- Kontra: nie dla bardzo dużych liczb powyżej 2 32 -1
- Rozwiązanie Python
- Pro: prosty, elastyczny, działa nawet dla dużych liczb
- Contra: mniej przenośny
- Rozwiązanie Zsh
- Pro: dobre dla osób, które i tak używają Zsh
- Contra: prawdopodobnie jeszcze mniej przenośny
- Rozwiązanie Perla
źródło
rand=$(command)
zrobić, jeślicommand
zwróci liczbę całkowitą spełniającą Twoje wymagania?dd if=/dev/urandom 2>/dev/null
i przepuszczając good -t d
(unikając objazdu przez base64), ale nie jest dla mnie jasne, jak przebiega konwersja i czy rzeczywiście jest bezstronna. Jeśli możesz rozwinąć swój pomysł w wydajny, działający skrypt i wyjaśnić, dlaczego nie ma uprzedzeń, byłby to świetna odpowiedź. :)python
lubperl
lub swój ulubiony język, ale nie jest dostępny wszędzie. Wolałbym coś bardziej przenośnego. Cóż,awk
przypuszczam , że losowa funkcja byłaby w porządku. Ale im bardziej przenośny, tym lepiej :)perl -e 'print int(rand(2**32-1))');
. To jest cholernie przenośne i będzie bardzo szybkie. Awk nie chce tego wyciąć, ponieważ większość implementacji zaczyna się od tego samego ziarna. Otrzymujesz taką samą liczbę losową przy kolejnych uruchomieniach. Zmienia się tylko w tym samym przebiegu.Odpowiedzi:
Widzę stąd inną ciekawą metodę .
Ta również wydaje się być dobrą opcją. Odczytuje 4 bajty z losowego urządzenia i formatuje je jako liczbę całkowitą bez znaku między
0
i2^32-1
.źródło
/dev/urandom
chyba że wiesz, że potrzebujesz/dev/random
;/dev/random
bloki w systemie Linux.od
polecenia są różne. Oba drukują tylko 4-bajtowe liczby całkowite bez znaku: 1. - z openssl, 2. - z/dev/random
./dev/urandom
zamiast/dev/random
- Nie widzę powodu, aby używać/dev/random
, i może być naprawdę kosztowny / wolny lub spowalniać inne części systemu. (Nie krępuj się, edytuj z powrotem i wyjaśnij, czy jest to naprawdę potrzebne.)I
oznacza,sizeof(int)
że może to być mniej niż4
w zasadzie. btw,od -DAn
zawodzi,(2**32-1)
aleod -N4 -tu4 -An
nadal działa.Dziękuję wszystkim za wszystkie wspaniałe odpowiedzi. Skończyło się na następującym rozwiązaniu, którym chciałbym się podzielić.
Zanim przejdę do bardziej szczegółowych informacji o tym, dlaczego i jak, oto tl; dr : mój błyszczący nowy skrypt :-)
Zapisz to
~/bin/rand
i masz do dyspozycji słodką losową funkcję w bash, która może próbkować liczbę całkowitą w danym arbitralnym zakresie. Zakres ten może zawierać ujemnych i dodatnich liczb całkowitych, i może wynosić do 2 60 -1 długość:Wszystkie pomysły innych odpowiedzi były świetne. Odpowiedzi terdon , JF Sebastian i jimmij wykorzystali narzędzia zewnętrzne do wykonania zadania w prosty i wydajny sposób. Jednak wolałem prawdziwe rozwiązanie bash dla maksymalnej przenośności, a może trochę po prostu z miłości do bash;)
Użyto odpowiedzi Ramesha i 10b0
/dev/urandom
lub/dev/random
w połączeniu zod
. To dobrze, jednak ich podejścia miały tę wadę, że były w stanie próbkować losowe liczby całkowite w zakresie od 0 do 2 8n -1 dla niektórych n, ponieważ ta metoda próbkuje bajty, tj. Ciągi bitów o długości 8. Są to dość duże skoki z zwiększenie n.Wreszcie odpowiedź Falco opisuje ogólną ideę, jak można to zrobić dla dowolnych zakresów (nie tylko potęg dwóch). Zasadniczo dla danego zakresu
{0..max}
możemy określić, jaka jest następna potęga dwóch, tj. Dokładnie, ile bitów jest wymaganych do przedstawieniamax
jako ciąg bitów . Następnie możemy pobrać próbkę tylu bitów i sprawdzić, czy to dzielenie jako liczba całkowita jest większe niżmax
. Jeśli tak, powtórz. Ponieważ próbkujemy tyle bitów, ile potrzeba do przedstawieniamax
, prawdopodobieństwo każdej iteracji jest większe lub równe 50% sukcesu (50% w najgorszym przypadku, 100% w najlepszym przypadku). To jest bardzo wydajne.Mój skrypt jest w zasadzie konkretną implementacją odpowiedzi Falco, napisaną czystym bashem i bardzo wydajną, ponieważ wykorzystuje wbudowane operacje bitowe basha do próbkowania ciągów bitów o pożądanej długości. Dodatkowo honoruje pomysł Eliasza Kagana, który sugeruje użycie wbudowanej
$RANDOM
zmiennej poprzez połączenie łańcuchów bitów wynikających z powtarzanych wywołań$RANDOM
. W rzeczywistości wdrożyłem zarówno możliwości użycia, jak/dev/urandom
i$RANDOM
. Domyślnie powyższy skrypt używa$RANDOM
. (I ok, jeśli korzystamy/dev/urandom
, potrzebujemy od i tr , ale są one wspierane przez POSIX.)Jak to działa?
Zanim przejdę do tego, dwie obserwacje:
Okazuje się, że bash nie obsługuje liczb całkowitych większych niż 2 63 -1. Sam zobacz:
Wygląda na to, że bash wewnętrznie używa 64-bitowych liczb całkowitych ze znakiem do przechowywania liczb całkowitych. Więc przy 2 63 „zawija się” i otrzymujemy ujemną liczbę całkowitą. Dlatego nie możemy mieć nadziei na uzyskanie zakresu większego niż 2 63 -1 z dowolną funkcją losową, której używamy. Bash po prostu nie może tego znieść.
Ilekroć chcemy próbkować wartość w dowolnym przedziale pomiędzy
min
imax
ewentualniemin != 0
, możemy po prostu próbkować wartość pomiędzy0
imax-min
zamiast, a następnie dodaćmin
do wyniku końcowego. Działa to nawet jeślimin
i ewentualnie równieżmax
są ujemne , ale musimy być ostrożni, aby spróbować wartość pomiędzy0
i wartość bezwzględnąmax-min
. Zatem możemy skupić się na tym, jak próbkować losową wartość pomiędzy0
i dowolną dodatnią liczbą całkowitąmax
. Reszta jest łatwa.Krok 1: Określ, ile bitów jest potrzebnych do przedstawienia liczby całkowitej (logarytmu)
Tak więc dla danej wartości
max
chcemy wiedzieć, ile bitów jest potrzebnych do przedstawienia jej jako ciągu bitów. Jest tak, że później możemy losowo próbkować tylko tyle bitów, ile potrzeba, co czyni skrypt tak wydajnym.Zobaczmy. Ponieważ w przypadku
n
bitów możemy reprezentować do wartości 2 n -1, wówczas liczban
bitów potrzebna do reprezentowania dowolnej wartościx
wynosi pułap (log 2 (x + 1)). Potrzebujemy więc funkcji do obliczenia pułapu logarytmu do podstawy 2. Jest to raczej oczywiste:Potrzebujemy warunku,
n>0
więc jeśli będzie on zbyt duży, owija się i staje się ujemny, pętla z pewnością się zakończy.Krok 2: Próbkuj losowo ciąg bitów o długości
n
Najbardziej przenośne pomysły to użycie
/dev/urandom
(lub nawet/dev/random
jeśli jest to uzasadnione) lub wbudowana$RANDOM
zmienna bash . Zobaczmy, jak to zrobić w$RANDOM
pierwszej kolejności.Opcja A: Używanie
$RANDOM
Wykorzystuje to pomysł wspomniany przez Eliasza Kagana. Zasadniczo, ponieważ
$RANDOM
próbki 15-bitowej liczby całkowitej, możemy użyć$((RANDOM<<15|RANDOM))
do próbki 30-bitowej liczby całkowitej. Oznacza to, że przesuń pierwsze wywołanie o$RANDOM
15 bitów w lewo i zastosuj bitowe lub drugie wywołanie$RANDOM
, skutecznie łącząc dwa niezależnie próbkowane ciągi bitów (lub przynajmniej tak niezależne, jak wbudowane bash$RANDOM
).Możemy to powtórzyć, aby uzyskać 45-bitową lub 60-bitową liczbę całkowitą. Po tym bashu nie jest już w stanie sobie z tym poradzić, ale to oznacza, że możemy łatwo próbkować losową wartość z przedziału od 0 do 2 60 -1. Tak więc, aby próbkować n-bitową liczbę całkowitą, powtarzamy procedurę, aż nasz losowy ciąg bitów, którego długość rośnie w 15-bitowych krokach, ma długość większą lub równą n. Wreszcie odcinamy za dużo bitów, odpowiednio przesuwając bitowo w prawo, i otrzymujemy losową liczbę całkowitą n-bit.
Opcja B: Używanie
/dev/urandom
Alternatywnie możemy użyć
od
i/dev/urandom
wypróbować n-bitową liczbę całkowitą.od
odczytuje bajty, tj. ciągi bitów o długości 8. Podobnie jak w poprzedniej metodzie, próbkujemy tyle bajtów, że równoważna liczba próbkowanych bitów jest większa lub równa n, i odcinamy za dużo bitów.Najniższa liczba bajtów potrzebna do uzyskania co najmniej n bitów to najniższa wielokrotność liczby 8, która jest większa lub równa n, tj. Floor ((n + 7) / 8).
Działa to tylko do 56-bitowych liczb całkowitych. Próbkowanie jeszcze jednego bajtu dałoby nam 64-bitową liczbę całkowitą, tj. Wartość do 2 64 -1, której bash nie może obsłużyć.
Łączenie elementów: zdobądź losowe liczby całkowite w dowolnych zakresach
Możemy
n
teraz próbkować -bitowe ciągi bitowe, ale chcemy próbkować liczby całkowite w zakresie od0
domax
, jednolicie losowo , gdziemax
mogą być dowolne, niekoniecznie potęga dwóch. (Nie możemy używać modulo, ponieważ powoduje to stronniczość.)Chodzi o to, że tak bardzo staraliśmy się próbkować tyle bitów, ile potrzeba do przedstawienia wartości
max
, że możemy teraz bezpiecznie (i wydajnie) używać pętli do wielokrotnego próbkowanian
ciągów bitowych, dopóki nie spróbujemy wartości niższej lub równamax
. W najgorszym przypadku (max
potęga dwóch) każda iteracja kończy się z prawdopodobieństwem 50%, aw najlepszym przypadku (max
potęga dwóch minus jeden) pierwsza iteracja kończy się z pewnością.Podsumowując
Na koniec chcemy próbkować liczby całkowite pomiędzy
min
imax
, gdziemin
imax
mogą być dowolne, a nawet ujemne. Jak wcześniej wspomniano, jest to teraz trywialne.Umieśćmy to wszystko w skrypcie bash. Wykonaj kilka analiz składni argumentów ... Chcemy dwa argumenty
min
imax
, lub tylko jeden argumentmax
,min
domyślnie0
.... i wreszcie próbki równomiernie na losowej wartości w między
min
imax
, możemy spróbować losową liczbę całkowitą między0
a wartość bezwzględnąmax-min
i dodaćmin
do wyniku końcowego. :-)Zainspirowany tym , mógłbym spróbować użyć zagorzałego testera do przetestowania i przetestowania tego PRNG i umieszczenia tutaj moich wniosków. :-)
źródło
sizeof(int) == 8
(64-bitowy) z powodu--format=u
random.Random
klasa używa 53bit? generator zwracający dowolne duże liczby losowe (wielokrotne wywołania),random.SystemRandom
robi to samo,os.urandom()
co można zaimplementować za pomocą/dev/urandom
.--format=u8
użyję, założyłem na stałe założeniesizeof(int)==8
. Z drugiej strony, jeśli używasz,--format=uL
nie ma problemu: nie sądzę, że istnieje platforma, która ma 64-bitowe liczby całkowite, ale wciąż definiuje długie int jako coś niższego. Tak więc zasadniczo twierdzę, że--format=uL
pozwala na większą elastyczność. Jakie są Twoje myśli?long long
, że może być 64bit podczas int = długo = 32bit na niektórych platformach. Nie powinieneś ubiegać się o zasięg 0..2 ** 60, jeśli nie możesz tego zagwarantować na wszystkich platformach. Z drugiej strony bash może nie obsługiwać samego zakresu na takich platformach (nie wiem, być może używa maxint_t, a wtedy u8 jest bardziej poprawny, jeśli chcesz potwierdzić stały zakres (od
nie obsługuje określania maksymalnego zakresu, jeśli twój zakres to niezależnie od tego, jaki jest zakres zależny od platformy?). Jeśli zasięg bash zależy od rozmiaru długiego, bardziej odpowiednie może być uL). Czy chcesz mieć pełny zakres obsługiwany przez bash we wszystkich systemach operacyjnych lub stały zakres?Czy to może być Zsh?
Możesz także użyć nasion
rand48(seed)
. Zobaczman zshmodules
iman 3 erand48
szczegółowy opis w razie zainteresowania.źródło
python
jest dostępny na systemach Redhat, opartych na Debianie.źródło
Jeśli chcesz liczbę od 0 do (2 ^ n) -1, gdzie n mod 8 = 0 , możesz po prostu uzyskać n / 8 bajtów
/dev/random
. Na przykład, aby uzyskać dziesiętną reprezentację losowej,int
możesz:Jeśli chcesz wziąć tylko n bitów , możesz najpierw pobrać pułap (n / 8) bajtów i przesunąć w prawo do żądanej ilości. Na przykład, jeśli chcesz 15 bitów:
Jeśli masz absolutną pewność, że nie zależy Ci na jakości losowości i chcesz zagwarantować minimalny czas działania, którego możesz użyć
/dev/urandom
zamiast/dev/random
. Przed użyciem upewnij się, że wiesz, co robisz/dev/urandom
!źródło
n
losowe bajty/dev/urandom
i sformatuj za pomocąod
. Podobny duchem jak ta odpowiedź . Oba są równie dobre :) Chociaż oba mają tę wadę, że mają stały zakres od 0 do 2 ^ (n * 8) -1 bitów, gdzie n jest liczbą bajtów. Wolałbym metodę dla dowolnego zakresu, do 2 ^ 32-1, ale także cokolwiek niższego. To stwarza trudności z uprzedzeniami./dev/urandom
zamiast/dev/random
- Nie widzę powodu, aby używać/dev/random
, i może być naprawdę drogie / wolne lub spowalniać inne części systemu. (Nie krępuj się, edytuj z powrotem i wyjaśnij, czy jest to naprawdę potrzebne.)/dev/urandom
wyniki są o wiele gorsze niż to,/dev/random
że w większości przypadków nie można zastosować urandomu . Po/dev/urandom
zainicjowaniu (na początku systemu); jego wyniki są tak dobre, jak/dev/random
dla prawie wszystkich aplikacji w systemie Linux. W niektórych systemach losowe i losowe są takie same.--format=u
należy zastąpić,--format=u4
ponieważsizeof(int)
może być mniej niż4
teoretycznie./dev/random
i/dev/urandom
są niezadowalające, oraz że „Linux należy dodać bezpieczny RNG, który blokuje aż zebrał wystarczającą entropię nasion, a następnie zachowuje się jakurandom
”.Zakładając, że nie wyrażasz sprzeciwu wobec korzystania z zewnętrznych narzędzi, powinno to spełniać Twoje wymagania:
Używa funkcji perla,
rand
która przyjmuje górną granicę jako parametr. Możesz ustawić to, co chcesz. To, jak blisko jest to do prawdziwej przypadkowości w abstrakcyjnej definicji matematycznej, wykracza poza zakres tej witryny, ale powinno być w porządku, chyba że potrzebujesz jej do bardzo wrażliwego szyfrowania lub podobnego. Być może nawet tam, ale nie zaryzykuję opinii.źródło
1^32-1
ale musisz go dostosować dla większych liczb.Powinieneś uzyskać najbliższą (2 ^ X) -1 równą lub większą niż pożądane maksimum i uzyskać liczbę bitów. Następnie wystarczy kilka razy wywołać / dev / random i dołączyć wszystkie bity razem, aż będzie ich wystarczająco, obcinając wszystkie bity, które są zbyt duże. Jeśli wynikowa liczba jest większa niż maksymalna liczba powtórzeń. W najgorszym przypadku masz większą niż 50% szansę na uzyskanie losowej liczby poniżej swojego maksimum, więc (w tym najgorszym przypadku) odbierzesz średnio dwa połączenia.
źródło
/dev/urandom
, ale w obu odpowiedziach jest to zawsze wielokrotność 8 bitów. Obcinanie bitów, które są zbyt duże dla niższych zakresów przed formatowaniem do dziesiętnego,od
jest dobrym pomysłem, aby poprawić wydajność, ponieważ pętla ma tylko oczekiwaną liczbę 2 iteracji, jak ładnie wyjaśnisz. To, w połączeniu z jedną z wymienionych odpowiedzi, jest prawdopodobnie właściwą drogą.Twoja odpowiedź jest interesująca, ale dość długa.
Jeśli chcesz dowolnie dużych liczb, możesz dołączyć do wielu losowych liczb w pomocniku:
Jeśli problemem jest błąd, po prostu go usuń.
Łączenie tych funkcji razem
źródło