Tüm güzel cevaplarınız için hepinize teşekkür ederim. Paylaşmak istediğim şu çözümü buldum.
Neden ve nedenlerle ilgili daha fazla ayrıntıya girmeden önce, işte tl; dr : benim parlak yeni senaryomuz :-)
#!/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 ))
Bunu ~/bin/randsaklayın ve uygun olduğunuzda, belirli bir aralıktaki bir tamsayıyı örnekleyebilen bash'ta rastgele bir işlev görürsünüz. Aralık, negatif ve pozitif tamsayılar içerebilir ve uzunlukları 2 60 -1'e kadar olabilir :
$ 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
Diğer cevaplayıcıların fikirleri harikaydı. Terdon , JF Sebastian ve jimmij tarafından verilen cevaplar , görevi basit ve verimli bir şekilde yapmak için dış araçları kullandı. Ancak, maksimum taşınabilirlik için gerçek bir bash çözümü tercih ettim ve belki birazcık bash aşkına;
Ramesh 've l0b0 ' ın cevaplarını kullanılmış /dev/urandomveya /dev/randombirlikte od. Ancak bu onların yaklaşımları, bazı n'ler için yalnızca 0 - 2 8n -1 aralığında rasgele tamsayıları örnekleyememenin dezavantajı vardı; çünkü bu yöntem, baytları, yani 8 uzunluk bit bitlerini örneklemektedir. artan
Son olarak, Falco'nun cevabı, keyfi aralıklar için bunun nasıl yapılabileceği ile ilgili genel fikri açıklar (sadece ikisinin güçleri değil). Temel olarak, belirli bir aralık için {0..max}, ikisinin bir sonraki gücünün ne olduğunu, yani bir bitmax kuşağı olarak temsil etmek için tam olarak kaç bitin gerekli olduğunu belirleyebiliriz . Sonra sadece bu kadar çok bit örnekleyebilir ve bu ısırmanın bir tamsayı olarak büyük olup olmadığını görebiliriz max. Eğer öyleyse, tekrarla. Temsil etmek için gereken kadar bit örneklediğimiz için max, her bir yinelemenin başarılı olma% 50'sine eşit veya daha büyük bir olasılığı vardır (en kötü durumda% 50, en iyi durumda% 100). Yani bu çok verimli.
Senaryom temelde, Falco'nun cevabının somut bir uygulamasıdır, istenen baslıktaki bit hatlarını örneklemek için bash yerleşik bitli işlemleri kullanır. Ayrıca Eliah Kağan tarafından , yerleşik $RANDOMdeğişkenin tekrarlanan çağrılardan kaynaklanan bit kızaklarını birleştirerek kullanılmasını öneren bir fikri onurlandırır $RANDOM. Aslında her ikisini de kullanma /dev/urandomve uygulama olanaklarını uyguladım $RANDOM. Varsayılan olarak yukarıdaki komut dosyası kullanır $RANDOM. (Tamam, eğer kullanıyorsanız od ve tr'ye/dev/urandom ihtiyacımız var , ancak bunlar POSIX tarafından destekleniyor.)
Peki nasıl çalışıyor?
Buna girmeden önce, iki gözlem:
Görünüşe göre bash, 2 63 -1'den büyük tam sayıları kaldıramadı . Kendin için gör:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
Bash'ın dahili olarak, tamsayıları depolamak için işaretli 64-bit tam sayıları kullandığı anlaşılıyor. Bu yüzden, 2 63'te "tamamlandı" ve negatif bir tamsayı elde ediyoruz. Bu nedenle, kullandığımız rastgele işlevle, 2 63 -1'den daha büyük bir aralık elde etmeyi umamayız . Bash basitçe halledemez.
Ne zaman minve maxolasılıkla isteğe bağlı bir aralıkta min != 0bir değeri örneklemek istersek, aralarında 0ve max-minyerine bir değeri örnekleyebiliriz ve sonra minnihai sonucu ekleyebiliriz . Bu da çalışır minve muhtemelen de maxvardır negatif ama biz arasında bir değer örneklemek için dikkatli olmak gerekir 0ve mutlak değerini max-min . Öyleyse, rasgele bir değer ile 0isteğe bağlı bir pozitif tamsayı arasında nasıl örnekleme yapılacağına odaklanabiliriz max. Gerisi kolaydır.
Adım 1: Bir tamsayıyı (logaritma) temsil etmek için kaç bitin gerekli olduğunu belirleyin.
Dolayısıyla, belirli bir değer için max, onu bir bit çarpması olarak temsil etmek için kaç bitin gerekli olduğunu bilmek istiyoruz. Bu, daha sonra, sadece gerektiği kadar çok bit rastgele örnekleme yapabilir, bu da betiği çok verimli kılar.
Bakalım. nBitlerle olduğu için, 2 n -1 değerine kadar temsil edebiliriz , daha sonra nkeyfi bir değeri temsil etmek için gereken bit sayısı xtavandır (log 2 (x + 1)). Bu yüzden, logaritmanın tavanını tabana 2 hesaplamak için bir işleve ihtiyacımız var.
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
Bu duruma ihtiyacımız var, n>0eğer çok büyürse, etrafına sarılır ve negatif hale gelirse, döngünün sona ermesi garanti edilir.
Adım 2: Rastgele bir uzunluk n
En taşınabilir fikirler ya kullanmak /dev/urandom(ya da /dev/randomgüçlü bir sebep olsa bile ) ya da bash'ın yerleşik $RANDOMdeğişkenini kullanmaktır. $RANDOMİlk önce nasıl yapılacağına bakalım .
Seçenek A: Kullanma $RANDOM
Bu, Eliah Kagan tarafından belirtilen fikri kullanıyor . Temel olarak, $RANDOM15 bitlik bir tamsayı $((RANDOM<<15|RANDOM))örneklendiğinden, 30 bitlik bir tamsayıyı örneklemek için kullanabiliriz . Bunun anlamı, bir ilk başlatmayı $RANDOM15 bit sola kaydırmak ve bitsel olarak veya ikinci bir $RANDOMbağımsız başlatmayı uygulayarak , iki bağımsız olarak örneklenen bit bağlantı dizisini etkin bir şekilde birleştirmek (ya da en azından bash'ın yerleşik olduğu kadar bağımsız $RANDOM) demektir.
Bunu 45 bit veya 60 bit tam sayı elde etmek için tekrarlayabiliriz. Bundan sonra bash artık üstesinden gelemez, ancak bu kolayca 0 ile 2 60 -1 arasındaki rastgele bir değeri kolayca örnekleyebileceğimiz anlamına gelir . Bu nedenle n-bit bir tamsayıyı örneklemek için, uzunluğunu 15-bit adımda büyüyen rastgele bit-sarımımız n'ye eşit veya daha uzun olana kadar işlemi tekrar ederiz. Sonunda, uygun şekilde bitsel sağa kaydırarak çok fazla olan bitleri keseriz ve sonunda bir n-bit rasgele tamsayı ile sonuçlanır.
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) ))
}
Seçenek B: Kullanma /dev/urandom
Alternatif olarak, n-bit bir tamsayıyı kullanabilir odve /dev/urandomörnekleyebiliriz. odbaytları, yani uzunluk bit bitlerini okuyacaktır. Önceki yöntemde olduğu gibi, eşdeğer örneklenmiş bitlerin sayısının n'den büyük veya ona eşit olduğu kadar çok bayt örnekleriz ve çok fazla olan bitleri keseriz.
En az n bit elde etmek için gereken en düşük bayt sayısı n'den büyük veya eşit olan 8 katın en küçük katıdır, yani kat ((n + 7) / 8).
Bu yalnızca 56 bit tam sayıya çalışır. Bir bayt daha fazla örnekleme bize 64-bit bir tamsayı, yani bash'ın kaldıramayacağı 2 64 -1 değerine ulaşır.
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) ))
}
Parçaları bir araya getirmek: Rasgele tamsayıları rastgele aralıklarla alın
Biz tadabilirsiniz nşimdi bit katarı -bit, ama biz bir aralıkta örnek tamsayılar istediğiniz 0için max, tekdüze rasgele , nerede max, keyfi olabilir ikisinin ille bir güç. (Bu bir önyargı yaratan modulo kullanamayız.)
Değeri temsil etmek için gerektiği kadar çok bit örneklemek için çok uğraştığımız maxnokta, şimdi daha güvenli bir (ve verimli bir şekilde) bir nbit bitstring'i daha düşük bir değer örnekleyene kadar tekrar tekrar örneklemek için bir döngü kullanabiliriz. veya eşittir max. En kötü durumda ( maxikinin gücü), her yineleme% 50 olasılıkla sona erer ve en iyi durumda ( maxiki eksi olanın gücüdür) ilk yineleme kesin olarak sona erer.
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
}
Eşyaları sarmak
Son olarak, minve arasında, ve maxnerede minve maxkeyfi, hatta negatif olabilir tamsayıları örneklemek istiyoruz . Daha önce belirtildiği gibi, bu artık önemsiz.
Hepsini bash betiğine koyalım. Bazı argüman ayrıştırma şeyleri yapın ... İki argüman minve varsayılanların olduğu yerde maxyalnızca bir argüman istiyoruz .maxmin0
# 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
... ve son olarak, arasında rasgele bir değerde eşit örnek minve maxbiz arasında rasgele bir sayı örnek 0ve mutlak değerini max-minve ek minnihai sonuca. :-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
Bundan ilham alarak, bu PRNG'yi test etmek ve kıyaslamak için kalıp sökücü kullanmaya çalışabilir ve bulgularımı buraya koyabilirim. :-)