Terima kasih atas semua jawaban Anda. Saya berakhir dengan solusi berikut, yang ingin saya bagikan.
Sebelum saya membahas lebih detail tentang mengapa dan bagaimana, inilah tl; dr : skrip baru saya yang mengkilap :-)
#!/usr/bin/env bash
#
# Generates a random integer in a given range
# computes the ceiling of log2
# i.e., for parameter x returns the lowest integer l such that 2**l >= x
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
# uses $RANDOM to generate an n-bit random bitstring uniformly at random
# (if we assume $RANDOM is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 60 bits
get_n_rand_bits() {
local n=$1 rnd=$RANDOM rnd_bitlen=15
while (( rnd_bitlen < n ))
do
rnd=$(( rnd<<15|$RANDOM ))
let rnd_bitlen+=15
done
echo $(( rnd>>(rnd_bitlen-n) ))
}
# alternative implementation of get_n_rand_bits:
# uses /dev/urandom to generate an n-bit random bitstring uniformly at random
# (if we assume /dev/urandom is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 56 bits
get_n_rand_bits_alt() {
local n=$1
local nb_bytes=$(( (n+7)/8 ))
local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
echo $(( rnd>>(nb_bytes*8-n) ))
}
# for parameter max, generates an integer in the range {0..max} uniformly at random
# max can be an arbitrary integer, needs not be a power of 2
rand() {
local rnd max=$1
# get number of bits needed to represent $max
local bitlen=$(log2 $((max+1)))
while
# could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
rnd=$(get_n_rand_bits $bitlen)
(( rnd > max ))
do :
done
echo $rnd
}
# MAIN SCRIPT
# check number of parameters
if (( $# != 1 && $# != 2 ))
then
cat <<EOF 1>&2
Usage: $(basename $0) [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
EOF
exit 1
fi
# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
min=$max
max=$1
shift
done
# ensure that min <= max
if (( min > max ))
then
echo "$(basename $0): error: min is greater than max" 1>&2
exit 1
fi
# need absolute value of diff since min (and also max) may be negative
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
Simpan itu ~/bin/rand
dan Anda memiliki fungsi acak manis di bash yang dapat mencicipi integer dalam rentang sewenang-wenang yang diberikan. Rentang ini dapat berisi bilangan bulat negatif dan positif dan panjangnya dapat mencapai 2 60 -1:
$ rand
Usage: rand [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
$ rand 1 10
9
$ rand -43543 -124
-15757
$ rand -3 3
1
$ for i in {0..9}; do rand $((2**60-1)); done
777148045699177620
456074454250332606
95080022501817128
993412753202315192
527158971491831964
336543936737015986
1034537273675883580
127413814010621078
758532158881427336
924637728863691573
Semua ide dari penjawab lain sangat bagus. Jawaban oleh terdon , JF Sebastian , dan jimmij menggunakan alat eksternal untuk melakukan tugas dengan cara yang sederhana dan efisien. Namun, saya lebih suka solusi bash sejati untuk portabilitas maksimum, dan mungkin sedikit, hanya karena cinta untuk bash;)
Jawaban Ramesh dan l0b0 digunakan /dev/urandom
atau /dev/random
dikombinasikan dengan od
. Itu bagus, bagaimanapun, pendekatan mereka memiliki kelemahan hanya mampu sampel bilangan bulat acak dalam kisaran 0 hingga 2 8n -1 untuk beberapa n, karena metode ini sampel byte, yaitu, bitstrings dengan panjang 8. Ini adalah lompatan yang cukup besar dengan meningkat n.
Akhirnya, jawaban Falco menggambarkan gagasan umum bagaimana ini bisa dilakukan untuk rentang arbitrer (tidak hanya kekuatan dua). Pada dasarnya, untuk rentang yang diberikan {0..max}
, kita dapat menentukan apa kekuatan dua berikutnya, yaitu, persis berapa banyak bit yang diperlukan untuk mewakili max
sebagai bitstring. Kemudian kita bisa mencicipi banyak bit itu dan melihat apakah bistring ini, sebagai bilangan bulat, lebih besar dari max
. Jika demikian, ulangi. Karena kami sampel bit yang diperlukan untuk mewakili max
, setiap iterasi memiliki probabilitas lebih besar atau sama dengan 50% dari berhasil (50% dalam kasus terburuk, 100% dalam kasus terbaik). Jadi ini sangat efisien.
Skrip saya pada dasarnya adalah implementasi konkret jawaban Falco, ditulis dalam bash murni dan sangat efisien karena menggunakan operasi bitwise bawaan bash untuk mengambil sampel bitstring dengan panjang yang diinginkan. Ini juga menghormati ide oleh Eliah Kagan yang menyarankan untuk menggunakan $RANDOM
variabel bawaan dengan meringkas bitstring yang dihasilkan dari pemanggilan berulang $RANDOM
. Saya benar-benar mengimplementasikan kedua kemungkinan untuk menggunakan /dev/urandom
dan $RANDOM
. Secara default, skrip di atas menggunakan $RANDOM
. (Dan ok, jika menggunakan /dev/urandom
kita perlu od dan tr , tetapi ini didukung oleh POSIX.)
Jadi bagaimana cara kerjanya?
Sebelum saya membahas hal ini, dua pengamatan:
Ternyata bash tidak dapat menangani bilangan bulat yang lebih besar dari 2 63 -1. Lihat diri mu sendiri:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
Tampaknya bash secara internal menggunakan integer 64-bit yang ditandatangani untuk menyimpan integer. Jadi, pada 2 63 itu "membungkus" dan kami mendapatkan bilangan bulat negatif. Jadi kita tidak bisa berharap untuk mendapatkan rentang yang lebih besar dari 2 63 -1 dengan fungsi acak apa pun yang kita gunakan. Bash tidak bisa mengatasinya.
Kapan pun kita ingin mengambil sampel dalam rentang yang sewenang-wenang antara min
dan max
dengan yang mungkin min != 0
, kita bisa dengan mudah mengambil sampel di antara 0
dan max-min
alih-alih kemudian menambahkan min
ke hasil akhir. Ini bekerja bahkan jika min
dan mungkin juga max
yang negatif , tapi kami harus berhati-hati untuk sampel nilai antara 0
dan nilai absolut dari max-min
. Jadi, kita bisa fokus pada bagaimana sampel nilai acak antara 0
dan bilangan bulat positif arbitrer max
. Sisanya mudah.
Langkah 1: Tentukan berapa banyak bit yang diperlukan untuk mewakili integer (logaritma)
Jadi untuk nilai yang diberikan max
, kami ingin tahu berapa banyak bit yang diperlukan untuk menyatakannya sebagai bitstring. Ini agar nantinya kita dapat secara acak sampel hanya sebanyak bit yang diperlukan, yang membuat skrip jadi efisien.
Ayo lihat. Karena dengan n
bit, kita dapat mewakili hingga nilai 2 n -1, maka jumlah n
bit yang diperlukan untuk mewakili nilai arbitrer x
adalah plafon (log 2 (x + 1)). Jadi, kita membutuhkan fungsi untuk menghitung langit-langit logaritma ke basis 2. Ini agak jelas:
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
Kita membutuhkan kondisinya n>0
sehingga jika tumbuh terlalu besar, membungkus dan menjadi negatif, loop dijamin akan berakhir.
Langkah 2: Cicipi bitstring acak yang panjangnya n
Gagasan yang paling portabel adalah menggunakan /dev/urandom
(atau bahkan /dev/random
jika ada alasan kuat) atau $RANDOM
variabel bawaan bash . Mari kita lihat bagaimana melakukannya $RANDOM
terlebih dahulu.
Opsi A: Menggunakan $RANDOM
Ini menggunakan ide yang disebutkan oleh Eliah Kagan. Pada dasarnya, karena $RANDOM
sampel bilangan bulat 15-bit, kita dapat menggunakan $((RANDOM<<15|RANDOM))
sampel bilangan bulat 30-bit. Itu berarti, menggeser doa pertama $RANDOM
sebesar 15 bit ke kiri, dan menerapkan bitwise atau dengan doa kedua $RANDOM
, efektif meringkas dua bitstring sampel secara independen (atau setidaknya sama independennya dengan built-in $RANDOM
berjalan bash ).
Kita dapat mengulanginya untuk mendapatkan integer 45-bit atau 60-bit. Setelah itu bash tidak bisa mengatasinya lagi, tetapi ini berarti kita dapat dengan mudah mencicipi nilai acak antara 0 dan 2 60 -1. Jadi, untuk mengambil sampel bilangan bulat n-bit, kami ulangi prosedur sampai bitstring acak kami, yang panjangnya tumbuh dalam langkah 15-bit, memiliki panjang lebih besar atau sama dengan n. Akhirnya, kita memotong bit yang terlalu banyak dengan menggeser bitwise ke kanan, dan kita berakhir dengan integer acak n-bit.
get_n_rand_bits() {
local n=$1 rnd=$RANDOM rnd_bitlen=15
while (( rnd_bitlen < n ))
do
rnd=$(( rnd<<15|$RANDOM ))
let rnd_bitlen+=15
done
echo $(( rnd>>(rnd_bitlen-n) ))
}
Opsi B: Menggunakan /dev/urandom
Atau, kita bisa menggunakan od
dan /dev/urandom
mengambil sampel integer n-bit. od
akan membaca byte, yaitu, bitstrings of length 8. Demikian pula seperti dalam metode sebelumnya, kami sampel begitu banyak byte sehingga jumlah setara bit sampel lebih besar atau sama dengan n, dan memotong bit yang terlalu banyak.
Jumlah byte terendah yang diperlukan untuk mendapatkan setidaknya n bit adalah kelipatan terendah dari 8 yang lebih besar atau sama dengan n, yaitu lantai ((n + 7) / 8).
Ini hanya bekerja hingga bilangan bulat 56-bit. Mengambil sampel satu byte lagi akan memberi kita integer 64-bit, yaitu nilai hingga 2 64 -1, yang tidak dapat ditangani oleh bash.
get_n_rand_bits_alt() {
local n=$1
local nb_bytes=$(( (n+7)/8 ))
local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
echo $(( rnd>>(nb_bytes*8-n) ))
}
Menyatukan potongan: Dapatkan bilangan bulat acak dalam rentang acak
Kita dapat mencicipi n
bitstring-bit sekarang, tetapi kami ingin mengambil contoh bilangan bulat dalam kisaran dari 0
hingga max
, seragam secara acak , di mana max
mungkin arbitrer, tidak harus kekuatan dua. (Kami tidak dapat menggunakan modulo karena itu menciptakan bias.)
Inti mengapa kami berusaha sangat keras untuk sampel bit sebanyak yang diperlukan untuk mewakili nilai max
, adalah bahwa kita sekarang dapat dengan aman (dan efisien) menggunakan loop untuk berulang kali sampel n
bitstring-bit sampai kita sampel nilai yang lebih rendah atau sama dengan max
. Dalam kasus terburuk ( max
adalah kekuatan dua), setiap iterasi berakhir dengan probabilitas 50%, dan dalam kasus terbaik ( max
adalah kekuatan dua minus satu), iterasi pertama berakhir dengan pasti.
rand() {
local rnd max=$1
# get number of bits needed to represent $max
local bitlen=$(log2 $((max+1)))
while
# could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
rnd=$(get_n_rand_bits $bitlen)
(( rnd > max ))
do :
done
echo $rnd
}
Membungkus semuanya
Akhirnya, kami ingin mengambil sampel bilangan bulat antara min
dan max
, di mana min
dan max
dapat arbitrer, bahkan negatif. Seperti yang disebutkan sebelumnya, ini sekarang sepele.
Mari kita letakkan semuanya dalam skrip bash. Lakukan beberapa penguraian argumen ... Kami ingin dua argumen min
dan max
, atau hanya satu argumen max
, di mana min
defaultnya adalah 0
.
# check number of parameters
if (( $# != 1 && $# != 2 ))
then
cat <<EOF 1>&2
Usage: $(basename $0) [min] max
Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1
EOF
exit 1
fi
# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
min=$max
max=$1
shift
done
# ensure that min <= max
if (( min > max ))
then
echo "$(basename $0): error: min is greater than max" 1>&2
exit 1
fi
... dan, akhirnya, untuk sampel secara acak di nilai antara min
dan max
, kami sampel bilangan bulat acak antara 0
dan nilai absolut dari max-min
, dan menambah min
hasil akhir. :-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
Terinspirasi oleh ini , saya mungkin mencoba menggunakan dieharder untuk menguji dan membandingkan PRNG ini, dan memasukkan temuan saya di sini. :-)