आपके सभी शानदार जवाब के लिए आप सभी का धन्यवाद। मैं निम्नलिखित समाधान के साथ समाप्त हुआ, जिसे मैं साझा करना चाहूंगा।
इससे पहले कि मैं whys और hows के बारे में किसी भी अधिक विस्तार में जाऊं, यहां tl; dr : मेरी चमकदार नई स्क्रिप्ट :-)
#!/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 ))
उस पर सहेजें ~/bin/rand
और आपके पास अपनी उपलब्धता है जो किसी अनियंत्रित श्रेणी में पूर्णांक का नमूना कर सकती है। सीमा में नकारात्मक और सकारात्मक पूर्णांक हो सकते हैं और लंबाई में 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
अन्य उत्तरदाताओं द्वारा सभी विचार महान थे। द्वारा जवाब terdon , जेएफ सेबस्टियन , और jimmij इस्तेमाल किया बाहरी उपकरण के लिए एक सरल और कुशल तरीके से काम करने के लिए। हालांकि, मैंने अधिकतम पोर्टेबिलिटी के लिए एक सच्चे बैश समाधान को प्राथमिकता दी, और शायद थोड़ा सा, बस बैश के लिए प्यार से बाहर;)
रमेश के और l0b0 के उत्तर का उपयोग किया है /dev/urandom
या के /dev/random
साथ संयोजन में od
। हालांकि, यह अच्छा है, उनके दृष्टिकोणों को केवल 0 से 2 8n की सीमा में यादृच्छिक पूर्णांकों को नमूना करने में सक्षम होने का नुकसान था -1 के लिए कुछ एन के लिए , क्योंकि इस विधि के नमूने बाइट्स, अर्थात, लंबाई के बिटस्ट्रिंग 8. ये काफी बड़े कूदते हैं बढ़ती हुई n।
अंत में, फाल्को का जवाब सामान्य विचार का वर्णन करता है कि यह कैसे मनमाने ढंग से पर्वतमाला के लिए किया जा सकता है (न केवल दो की शक्तियां)। मूल रूप से, एक दी गई सीमा के लिए {0..max}
, हम यह निर्धारित कर सकते हैं कि दो की अगली शक्ति क्या है, यानी, बिटस्ट्रिंग के रूप में प्रतिनिधित्व करने के लिए कितने बिट्स की आवश्यकता होती है max
। तब हम बस कई बिट्स का नमूना ले सकते हैं और देख सकते हैं कि यह बिस्टरिंग, एक पूर्णांक के रूप में, से अधिक है max
। यदि हां, तो दोहराएं। चूंकि हम प्रतिनिधित्व करने के लिए आवश्यक जितने भी बिट्स का नमूना लेते हैं max
, प्रत्येक पुनरावृत्ति में 50% सफल होने की संभावना या 50% (सबसे खराब स्थिति में 50%, सर्वश्रेष्ठ मामले में 100%) के बराबर संभावना होती है। तो यह बहुत कुशल है।
मेरी स्क्रिप्ट मूल रूप से फाल्को के उत्तर का एक ठोस कार्यान्वयन है, जो शुद्ध बैश में लिखा गया है और अत्यधिक कुशल है क्योंकि यह वांछित लंबाई के बिटस्ट्रिंग्स का नमूना करने के लिए बैश के अंतर्निहित बिटवाइज़ संचालन का उपयोग करता है। इसके अलावा, यह एलिया कागन द्वारा एक विचार का सम्मान करता है जो $RANDOM
बार-बार होने वाले चालान से उत्पन्न होने वाले बिटस्ट्रिंग को अंतर्निहित चर का उपयोग करने का सुझाव देता है $RANDOM
। मैंने वास्तव में उपयोग करने की संभावनाओं को लागू किया /dev/urandom
और $RANDOM
। डिफ़ॉल्ट रूप से उपरोक्त स्क्रिप्ट का उपयोग करता है $RANDOM
। (और ठीक है, अगर /dev/urandom
हम का उपयोग करने की जरूरत है od और tr , लेकिन ये POSIX द्वारा समर्थित हैं।)
तो यह कैसे काम करता है?
इससे पहले कि मैं इसमें शामिल होऊं, दो अवलोकन:
यह बताता है कि बैश 2 63 -1 से बड़े पूर्णांकों को नहीं संभाल सकता । अपने आप को देखो:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
ऐसा प्रतीत होता है कि बेज़ आंतरिक रूप से पूर्णांक को संग्रहीत करने के लिए हस्ताक्षरित 64-बिट पूर्णांक का उपयोग करता है। तो, 2 63 पर यह "चारों ओर घूमता है" और हमें एक नकारात्मक पूर्णांक मिलता है। इसलिए हम जो भी रैंडम फ़ंक्शन का उपयोग करते हैं उसके साथ किसी भी रेंज को 2 63 -1 से बड़ा पाने की उम्मीद नहीं कर सकते हैं। बैश बस इसे संभाल नहीं सकते।
जब भी हम एक मनमाने ढंग से सीमा के बीच min
और max
संभवतः के साथ एक मूल्य का नमूना करना चाहते हैं min != 0
, तो हम बस के बीच 0
और max-min
इसके बजाय एक मूल्य का नमूना कर सकते हैं और फिर min
अंतिम परिणाम में जोड़ सकते हैं । यह काम करता है भले ही min
और संभवतः नकारात्मक भी max
हो , लेकिन हमें इसके और पूर्ण मूल्य के बीच एक मूल्य के नमूने के लिए सावधान रहना होगा । तो, हम इस पर ध्यान केंद्रित कर सकते हैं कि कैसे और एक मनमाने ढंग से धनात्मक पूर्णांक के बीच यादृच्छिक मूल्य का नमूना लिया जाए । बाकी काम आसान है।0
max-min
0
max
चरण 1: एक पूर्णांक (लघुगणक) का प्रतिनिधित्व करने के लिए कितने बिट्स की आवश्यकता है यह निर्धारित करें
इसलिए किसी दिए गए मूल्य के लिए max
, हम यह जानना चाहते हैं कि बिटस्ट्रिंग के रूप में इसका प्रतिनिधित्व करने के लिए कितने बिट्स की आवश्यकता है। ऐसा इसलिए है कि बाद में हम बेतरतीब ढंग से केवल नमूने के रूप में कई बिट्स की आवश्यकता के रूप में कर सकते हैं, जो स्क्रिप्ट को इतना कुशल बनाता है।
चलो देखते हैं। चूंकि n
बिट्स के साथ , हम मान 2 एन -1 तक का प्रतिनिधित्व कर सकते हैं , फिर n
एक मनमाना मूल्य x
का प्रतिनिधित्व करने के लिए आवश्यक बिट्स की संख्या छत है (लॉग 2 (x + 1))। तो, हमें आधार के लिए एक लघुगणक की छत की गणना करने के लिए एक फ़ंक्शन की आवश्यकता है। यह बल्कि आत्म-व्याख्यात्मक है:
log2() {
local x=$1 n=1 l=0
while (( x>n && n>0 ))
do
let n*=2 l++
done
echo $l
}
हमें स्थिति की आवश्यकता है n>0
ताकि अगर यह बहुत बड़ा हो जाए, चारों ओर लपेटता है और नकारात्मक हो जाता है, तो लूप को समाप्त करने की गारंटी दी जाती है।
चरण 2: एक यादृच्छिक बिटस्ट्रीम का नमूना लें n
सबसे पोर्टेबल विचार या तो उपयोग करने के लिए हैं /dev/urandom
(या यहां तक कि /dev/random
अगर कोई मजबूत कारण है) या बैश के अंतर्निर्मित $RANDOM
चर। आइए देखें कि इसे $RANDOM
पहले कैसे करना है ।
विकल्प A: उपयोग करना $RANDOM
यह एलिया कगन द्वारा वर्णित विचार का उपयोग करता है । मूल रूप से, $RANDOM
15-बिट पूर्णांक के नमूने के बाद से , हम $((RANDOM<<15|RANDOM))
30-बिट पूर्णांक के नमूने का उपयोग कर सकते हैं । इसका मतलब है, $RANDOM
15 बिट्स के पहले इन्वोकेशन को बाईं ओर शिफ्ट करें , और एक बिटवाइज़ या दूसरे $RANDOM
इंवोकेशन के साथ प्रभावी रूप से दो स्वतंत्र रूप से सैंपल किए गए बिटस्ट्रिंग्स (या कम से कम बैश के अंतर्निहित के रूप में स्वतंत्र रूप से स्वतंत्र $RANDOM
) को लागू करें।
45-बिट या 60-बिट पूर्णांक प्राप्त करने के लिए हम इसे दोहरा सकते हैं। इसके बाद बैश इसे संभाल नहीं सकते, लेकिन इसका मतलब है कि हम आसानी से 0 और 2 60 -1 के बीच एक यादृच्छिक मूल्य का नमूना ले सकते हैं । इसलिए, एक एन-बिट पूर्णांक का नमूना करने के लिए, हम प्रक्रिया को तब तक दोहराते हैं जब तक कि हमारी यादृच्छिक बिटस्ट्रिंग, जिसकी लंबाई 15-बिट चरणों में बढ़ती है, की लंबाई एन की तुलना में अधिक या बराबर होती है। अंत में, हम उन बिट्स को काट देते हैं जो सही तरीके से शिफ्ट करने के लिए उचित रूप से बिटवाइज़ द्वारा बहुत अधिक हैं, और हम एन-बिट यादृच्छिक पूर्णांक के साथ समाप्त होते हैं।
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) ))
}
विकल्प बी: का उपयोग करना /dev/urandom
वैकल्पिक रूप से, हम n-बिट पूर्णांक का उपयोग od
और उपयोग कर सकते हैं /dev/urandom
। od
बाइट्स पढ़ेंगे, अर्थात, लंबाई के बिटस्ट्रिंग्स 8. इसी तरह पिछले पद्धति में, हम सिर्फ इतने बाइट्स का नमूना लेते हैं कि समतुल्य बिट्स के बराबर संख्या n से अधिक या बराबर होती है, और उन बिट्स को काट दें जो बहुत अधिक हैं।
कम से कम n बिट प्राप्त करने के लिए आवश्यक बाइट्स की सबसे कम संख्या 8 की सबसे कम संख्या है जो n, अर्थात, (n + 7) / 8) से अधिक या बराबर है।
यह केवल 56-बिट पूर्णांक तक काम करता है। एक और बाइट का नमूना लेने से हमें 64-बिट पूर्णांक मिलेगा, अर्थात, 2 64 -1 तक का मूल्य , जो बैश संभाल नहीं सकता है।
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) ))
}
टुकड़ों को एक साथ रखना: मनमाने ढंग से श्रेणियों में यादृच्छिक पूर्णांक प्राप्त करें
हम- n
बिट बिटस्ट्रिंग्स को अब नमूना कर सकते हैं, लेकिन हम पूर्ण रूप से यादृच्छिक रूप से , जहां 0
से max
, एक सीमा में पूर्णांकों को नमूना करना चाहते हैंmax
जरूरी नहीं कि दोनों में से एक शक्ति मनमाने ढंग से हो सकता है। (हम पूर्वाग्रह का उपयोग नहीं कर सकते क्योंकि यह एक पूर्वाग्रह पैदा करता है।)
पूरे बिंदु क्यों हम मूल्य का प्रतिनिधित्व करने के लिए आवश्यक के रूप में बस के रूप में कई बिट्स नमूना करने की कोशिश की है max
, यह है कि हम अब सुरक्षित रूप से (और कुशलता से) एक पाश का उपयोग बार-बार n
बिटस्ट्रीम नमूना करने के लिए कर सकते हैं जब तक कि हम एक मूल्य कम है कि नमूना या के बराबर है max
। सबसे खराब स्थिति में ( max
दो की शक्ति है), प्रत्येक पुनरावृत्ति 50% की संभावना के साथ समाप्त होती है, और सबसे अच्छा मामले में ( max
दो शून्य से एक की शक्ति है), पहला पुनरावृत्ति निश्चितता के साथ समाप्त होता है।
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
}
चीजों को लपेटना
अंत में, हम पूर्णांक के बीच min
और max
, जहां min
औरmax
यहां तक कि मनमाने ढंग से नकारात्मक भी हो सकते हैं । जैसा कि पहले उल्लेख किया गया है, यह अब तुच्छ है।
चलो यह सब एक बैश स्क्रिप्ट में डाल दिया। सामान को पार्स करने के लिए कुछ तर्क करें ... हम दो तर्क चाहते हैं min
और max
, केवल एक तर्क max
, जहां min
चूक है 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
... और, अंत में, समान रूप से यादृच्छिक मान के बीच min
का max
नमूना लेने के लिए, हम 0
और के पूर्ण मान के बीच एक यादृच्छिक पूर्णांक का नमूना लेते हैं max-min
, और min
अंतिम परिणाम में जोड़ते हैं । :-)
diff=$((max-min)) && diff=${diff#-}
echo $(( $(rand $diff) + min ))
से प्रेरित होकर इस , मैं उपयोग करने के लिए कोशिश कर सकते हैं dieharder परीक्षण और बेंचमार्क इस PRNG के लिए, और यहाँ में मेरी निष्कर्ष डाल दिया। :-)