ขอบคุณทุกคำตอบที่ดี ฉันลงเอยด้วยวิธีแก้ปัญหาต่อไปนี้ซึ่งฉันต้องการแบ่งปัน
ก่อนที่ฉันจะพูดถึงรายละเอียดเพิ่มเติมเกี่ยวกับ whys and 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
และคุณมีฟังก์ชั่นการสุ่มหวานใน bash ที่สามารถสุ่มตัวอย่างจำนวนเต็มในช่วงที่กำหนด ช่วงนี้อาจมีจำนวนเต็มบวกและลบและสามารถมีความยาวได้สูงสุด 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 , JF Sebastianและjimmijใช้เครื่องมือภายนอกเพื่อทำงานในลักษณะที่เรียบง่ายและมีประสิทธิภาพ อย่างไรก็ตามฉันต้องการโซลูชันทุบตีที่แท้จริงสำหรับการพกพาสูงสุดและอาจจะเล็กน้อยออกจากความรักสำหรับทุบตี;)
คำตอบของRameshและl0b0ที่ใช้/dev/urandom
หรือใช้/dev/random
ร่วมกับod
ร่วมกับนั่นเป็นสิ่งที่ดี แต่วิธีการของพวกเขามีข้อเสียเพียงแค่สามารถสุ่มจำนวนเต็มในช่วง 0 ถึง 2 8n -1 สำหรับบาง n เนื่องจากวิธีนี้เป็นตัวอย่างไบต์ตัวอย่างเช่นบิตของความยาว 8 พวกนี้ค่อนข้างกระโดดด้วย เพิ่ม n
ในที่สุดคำตอบของฟัลโกจะอธิบายแนวคิดทั่วไปว่าจะทำอย่างไรกับช่วงที่กำหนดเอง (ไม่ใช่แค่พลังสองอันเท่านั้น) โดยทั่วไปสำหรับช่วงที่กำหนด{0..max}
เราสามารถกำหนดได้ว่ากำลังสองถัดไปของอะไรคือจำนวนบิตที่ต้องการแสดงmax
เป็นบิตสตริง จากนั้นเราสามารถสุ่มตัวอย่างได้หลายบิตและดูว่าการแบ่งนี้เป็นจำนวนเต็มมากกว่าmax
หรือไม่ ถ้าเป็นเช่นนั้นทำซ้ำ เนื่องจากเราสุ่มตัวอย่างได้มากเท่าที่จำเป็นในการเป็นตัวแทนmax
แต่ละการวนซ้ำมีความน่าจะเป็นมากกว่าหรือเท่ากับ 50% ของการประสบความสำเร็จ (50% ในกรณีที่เลวร้ายที่สุด 100% ในกรณีที่ดีที่สุด) ดังนั้นนี่จึงมีประสิทธิภาพมาก
สคริปท์ของฉันเป็นการใช้งานคำตอบของ Falco อย่างเป็นรูปธรรมเขียนด้วย bash บริสุทธิ์และมีประสิทธิภาพสูงเพราะใช้การทำงาน bitcoin ในตัวของ bash เพื่อทดสอบ bitstrings ตามความยาวที่ต้องการ นอกจากนี้ยังได้รับเกียรตินิยมความคิดโดยEliah Kaganที่แสดงให้เห็นการใช้งานในตัว$RANDOM
ตัวแปรโดย bitstrings concatening $RANDOM
ที่เกิดจากการสวดซ้ำ ที่จริงผมนำมาใช้ทั้งความเป็นไปได้ที่จะใช้และ/dev/urandom
โดยค่าเริ่มต้นการใช้สคริปต์ข้าง$RANDOM
$RANDOM
(และตกลงถ้าใช้/dev/urandom
เราต้องการodและ trแต่สิ่งเหล่านี้ได้รับการสนับสนุนโดย POSIX)
แล้วมันทำงานอย่างไร
ก่อนที่ฉันจะได้รับสิ่งนี้ข้อสังเกตสองประการ:
ปรากฎว่า bash ไม่สามารถจัดการจำนวนเต็มที่มากกว่า 2 63 -1 ดูตัวเอง:
$ echo $((2**63-1))
9223372036854775807
$ echo $((2**63))
-9223372036854775808
มันจะปรากฏว่าทุบตีภายในใช้จำนวนเต็ม 64 บิตลงนามในการจัดเก็บจำนวนเต็ม ดังนั้นที่ 2 63มัน "ล้อมรอบ" และเราได้จำนวนเต็มลบ ดังนั้นเราจึงไม่สามารถหวังได้ว่าจะมีช่วงที่ใหญ่กว่า 2 63 -1 ด้วยฟังก์ชันสุ่มใด ๆ ที่เราใช้ Bash ไม่สามารถจัดการได้
เมื่อใดก็ตามที่เราต้องการตัวอย่างค่าในช่วงใดก็ได้ระหว่างmin
และmax
อาจเป็นไปmin != 0
ได้เราสามารถสุ่มค่าระหว่าง0
และmax-min
แทนและเพิ่มmin
ในผลลัพธ์สุดท้าย นี้ทำงานแม้ว่าmin
และอาจจะยังmax
เป็นเชิงลบแต่เราจะต้องระมัดระวังที่จะลิ้มลองค่าระหว่าง0
และค่าสัมบูรณ์ของ max-min
ดังนั้นแล้วเราสามารถมุ่งเน้นเกี่ยวกับวิธีการที่จะลิ้มลองค่าสุ่มระหว่างและจำนวนเต็มบวกโดยพลการ0
max
ที่เหลือก็ง่าย
ขั้นตอนที่ 1: กำหนดจำนวนบิตที่ต้องการเพื่อแสดงจำนวนเต็ม (ลอการิทึม)
ดังนั้นสำหรับค่าที่กำหนด max
เราต้องการทราบจำนวนบิตที่ต้องการเพื่อแสดงว่าเป็นบิตสตริง นี่คือเพื่อให้ในภายหลังเราสามารถสุ่มตัวอย่างเพียงแค่บิตจำนวนมากเท่าที่ต้องการซึ่งทำให้สคริปต์มีประสิทธิภาพมาก
มาดูกัน. เนื่องจากด้วยn
บิตเราสามารถแทนค่าได้สูงสุด 2 n -1 จากนั้นจำนวนn
ของบิตที่จำเป็นในการแทนค่าโดยพลการx
คือ Ceiling (log 2 (x + 1)) ดังนั้นเราต้องการฟังก์ชั่นในการคำนวณเพดานของลอการิทึมถึงฐาน 2 มันค่อนข้างอธิบายได้ด้วยตนเอง:
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
ตัวแปรในตัวของ bash ลองดูวิธีการทำ$RANDOM
ก่อน
ตัวเลือก A: การใช้ $RANDOM
สิ่งนี้ใช้ความคิดที่กล่าวถึงโดย Eliah Kagan โดยทั่วไปเนื่องจาก$RANDOM
ตัวอย่างจำนวนเต็ม 15 บิตเราสามารถใช้$((RANDOM<<15|RANDOM))
เพื่อสุ่มตัวอย่างจำนวนเต็ม 30 บิต นั่นหมายความว่าให้เลื่อนการเรียกใช้ครั้งแรกที่$RANDOM
15 บิตไปทางซ้ายและใช้ค่าบิตหรือการเรียกครั้งที่สองของการ$RANDOM
เชื่อมบิตสองตัวอย่างที่เป็นอิสระได้อย่างมีประสิทธิภาพ$RANDOM
ไป)
เราสามารถทำสิ่งนี้ซ้ำเพื่อรับจำนวนเต็ม 45 บิตหรือ 60 บิต หลังจาก bash นั้นไม่สามารถจัดการได้อีกต่อไป แต่นี่หมายความว่าเราสามารถสุ่มค่าสุ่มระหว่าง 0 ถึง 2 60 -1 ได้ ดังนั้นเพื่อสุ่มตัวอย่างจำนวนเต็ม n-bit เราจะทำซ้ำขั้นตอนนี้จนกระทั่ง bitstring แบบสุ่มของเราซึ่งความยาวเพิ่มขึ้นในขั้นตอน 15 บิตมีความยาวมากกว่าหรือเท่ากับ n สุดท้ายเราตัดบิตที่มากเกินไปโดยการเลื่อนบิตไปทางขวาอย่างเหมาะสมและท้ายที่สุดเราจะมีจำนวนเต็มแบบสุ่ม n บิต
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) ))
}
ตัวเลือก B: การใช้ /dev/urandom
อีกวิธีหนึ่งเราสามารถใช้od
และ/dev/urandom
ตัวอย่างจำนวนเต็ม n-bit od
จะอ่านจำนวนไบต์เช่น bitstrings ที่มีความยาว 8 เช่นเดียวกับวิธีก่อนหน้านี้เราสุ่มตัวอย่างจำนวนไบต์จำนวนมากที่จำนวนบิตตัวอย่างที่เท่ากันมากกว่าหรือเท่ากับ n และตัดบิตที่มากเกินไป
จำนวนไบต์ต่ำสุดที่จำเป็นในการรับอย่างน้อย n บิตคือตัวคูณที่ต่ำที่สุดของ 8 ที่มากกว่าหรือเท่ากับ n คือเช่น floor ((n + 7) / 8)
วิธีนี้ใช้ได้กับจำนวนเต็มสูงสุด 56 บิตเท่านั้น การสุ่มตัวอย่างอีกหนึ่งไบต์จะทำให้เราได้จำนวนเต็ม 64 บิตเช่นค่าสูงสุด 2 64 -1 ซึ่ง 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) ))
}
วางชิ้นส่วนเข้าด้วยกัน: รับจำนวนเต็มแบบสุ่มในช่วงใดก็ได้
เราจะได้ลิ้มลองn
บิต bitstrings ตอนนี้ แต่เราต้องการที่จะจำนวนเต็มตัวอย่างอยู่ในช่วงจาก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
สามารถโดยพลการแม้ลบ ดังที่ได้กล่าวไปแล้วตอนนี้เป็นเรื่องเล็กน้อย
ลองใส่ทั้งหมดลงใน bash script ทำบางสิ่งอาร์กิวเมนต์แยก ... เราต้องการทั้งสองมีปากเสียง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 ))
โดยได้รับแรงบันดาลใจจากสิ่งนี้ฉันอาจลองใช้เครื่องมิจฉาทิฐิเพื่อทดสอบและกำหนดมาตรฐาน PRNG นี้และวางสิ่งที่ฉันค้นพบที่นี่ :-)