تولید عدد صحیح تصادفی

فصل 9. یک نگاه دیگر به متغیرها

‎9.3‎‏- ‎$RANDOM‎: تولید عدد صحیح تصادفی

 

هر شخصی که تلاش می‌کند اعداد تصادفی را با روشهای قطعی تولید کند، به طور حتم در اشتباه به سر می‌برد.

‎--John von Neumann‎

‎$RANDOM‎ یک تابع درونی Bash است (نه یک ثابت) که یک عدد صحیح تصادفی ساختگی ‎[1]‎ در محدوده ‎0 - 32767‎ تولید می‌کند. نباید برای تولید یک کلید رمزنگاری به کار برود.

مثال ‎9-11‎. تولید اعداد تصادفی

#!/bin/bash

# ‎$RANDOM‎ در هر نوبت یک عدد صحیح تصادفی متفاوتی برگشت می‌دهد.
#  محدوده اسمی آن: ‎0 - 32767‎ (صحیح علامت‌دار شانزده بیتی) است.

MAXCOUNT=10
count=1

echo
echo "$MAXCOUNT random numbers:"
echo "-----------------"
while [ "$count" -le $MAXCOUNT ]      # تولید 10 ‎($MAXCOUNT)‎ عدد صحیح تصادفی.
do
  number=$RANDOM
  echo $number
  let "count += 1"  #
done
echo "-----------------"

#  اگر به عدد صحیح تصادفی در یک محدوده معین نیاز دارید، عملگر modulo را 
#      

RANGE=500

echo

number=$RANDOM
let "number %= $RANGE"
#
echo "Random number less than $RANGE  ---  $number"

echo

#
#+

FLOOR=200

number=0   # 
while [ "$number" -le $FLOOR ]
do
  number=$RANDOM
done
echo "Random number greater than $FLOOR ---  $number"
echo

   #       
   #       کنیم، یعنی ‎let "number = $RANDOM + $FLOOR"‎ را که
   #    
   #

#
number=0   # 
while [ "$number" -le $FLOOR ]
do
  number=$RANDOM
  let "number %= $RANGE"  #  کاهش ‎$number‎ به کمتر از ‎$RANGE‎
done
echo "Random number between $FLOOR and $RANGE ---  $number"
echo

# تولید گزینه دوحالته، یعنی کمیت true یا false.
BINARY=2
T=1
number=$RANDOM

let "number %= $BINARY"
#   توجه کنید که ‎let "number >>= 14"‎ پراکندگی تصادفی بهتری ارایه می‌کند.
#+ 
#مترجم: در اصل number مساوی آخرین بیت سمت چپ عدد تصادفی تولیدی می‌شود.
if [ "$number" -eq $T ]
then
  echo "TRUE"
else
  echo "FALSE"
fi  

echo

          #                             
SPOTS=6   #      باقیمانده تقسیم بر ‎6‎ محدوده ‎0 - 5‎ را بیان می‌کند.
          #      با افزودن 1، محدوده مورد نظر ‎1 - 6‎ تامین می‌گردد.
          # با تشکر از Paulo Marcel Coelho Aragao، برای ساده‌سازی.
die1=0
die2=0
# بهتر نخواهد بود فقط ‎SPOTS=7‎ تنظیم شود و 1 اضافه نشود؟ چرا یا چرا نه؟

#    

    let "die1 = $RANDOM % $SPOTS +1"    # انداختن تاس اول.
    let "die2 = $RANDOM % $SPOTS +1"    # انداختن تاس دوم.
    #          
    #+                 هستند، باقیمانده ‎(%)‎ یا افزایش ‎(+) ‎؟

let "throw = $die1 + $die2"
echo "Throw of the dice = $throw"
echo

exit 0

مثال ‎9-12‎. کشیدن یک کارت اتفاقی از یک دسته ورق

#!/bin/bash
#

#

#

Suites="Clubs
Diamonds
Hearts
Spades"

Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"

#

suite=($Suites)                #
denomination=($Denominations)

num_suites=${#suite[*]}        #
num_denominations=${#denomination[*]}

echo -n "${denomination[$((RANDOM%num_denominations))]} of "
echo ${suite[$((RANDOM%num_suites))]}


#
#

# با تشکر از jipe برای نشان دادن این مورد استفاده از ‎$RANDOM‎.
exit 0

مثال ‎9-13‎. شبیه‌سازی حرکت Brownian

#!/bin/bash
#
#  مولف: Mendel Cooper
#  ‎تاریخ انتشار: ‎10/26/07
#  مجوز: GPL3

#  ----------------------------------------------------------------
#                  این اسکریپت حرکت Brownian را طرح‌ریزی می‌کند:
#+  
#+   
#+      این اصطلاحاً «‎Drunkard's Walk‎»(حرکت مستانه) گفته می‌شود.

#   
#+
#+ 
#+  
#+                                  
#   .مترجم: در صورت تمایل، می‌توانید یک شبیه‌سازی نمایشی زیبا از صفحه گالتون را در اینجا  مشاهده نمایید
#    
#    
#+    
#+    
#      به عنوان یک شبیه‌ساز Galton Board، اسکریپت پارامترهایی از قبیل زاویه 
#+    
#+     
#           
#  ----------------------------------------------------------------

PASSES=500            #       
ROWS=10               #        
RANGE=3               #                   محدوده ‎0 - 2‎ برای خروجی ‎$RANDOM‎
POS=0                 #        
RANDOM=$$             #

declare -a Slots      #
NUMSLOTS=21           #                     


Initialize_Slots () { #                       
for i in $( seq $NUMSLOTS )
do
  Slots[$i]=0
done

echo                  #
  }


Show_Slots () {
echo; echo
echo -n " "
for i in $( seq $NUMSLOTS )   #
do
  printf "%3d" ${Slots[$i]}   #
done

echo                          #
echo " |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|"
echo "                                ||"
echo                          #
                              #+
                              # اجرا با تنها ‎500‎ گلوله، معمولاً مانع این امر می‌گردد.
  }


Move () {                     #
  Move=$RANDOM                # ‎$RANDOM‎ چطور تصادفی است؟ خوب، بیایید ببینیم
  let "Move %= RANGE"         #
  case "$Move" in
    0 ) ;;                    #
    1 ) ((POS--));;           #
    2 ) ((POS++));;           #
    * ) echo -n "Error ";;    #
  esac
  }


Play () {                     #
i=0
while [ "$i" -lt "$ROWS" ]    #
do
  Move
  ((i++));
done

SHIFT=11                      #                   چرا ‎11‎ و نه  ‎10‎؟
let "POS += $SHIFT"           #
(( Slots[$POS]++ ))           #         برای اشکالزدایی: ‎echo $POS‎

#

  }


Run () {                      #
p=0
while [ "$p" -lt "$PASSES" ]
do
  Play
  (( p++ ))
  POS=0                       #
done
  }

# --------------
#
Initialize_Slots
Run
Show_Slots
# --------------

exit $?

#                                 
#                               
#‎(1‎‎ نتایج را با نمودار میله‌ای عمودی، یا به صورت نمودار ‎scattergram‎‏‎ نمایش دهید.
#  ‎(2‎‎ اسکریپت را برای استفاده از ‎/dev/urandom‎ به جای کاربرد ‎$RANDOM‎ اصلاح کنید.‎‎
#                            آیا این کار، نتایج را بیشتر تصادفی خواهد نمود؟
#          ‎(3‎‎ نوعی «پویانمایی» یا خروجی گرافیکی برای حرکت هر گلوله ارایه کنید.‎

Jipe مجموعه‌ای از تکنیک‌های تولید اعداد تصادفی داخل یک محدوده را نشان می‌دهد.

#                 تولید عدد تصادفی بین ‎6‎ و ‎30‎.
   rnumber=$((RANDOM%25+6))	

#    تولید عدد تصادفی در همان محدوده ‎6 - 30‎ اما
#+                عدد باید بر  ‎3‎ بخش‌پذیر باشد.
   rnumber=$(((RANDOM%30/3+1)*3))

#
# اگر ‎$RANDOM%30‎ صفر برگشت بدهد، ناموفق می‌شود.

#  ‎Frank Wang‎ جایگزین پایین را پیشنهاد می‌دهد:
   rnumber=$(( RANDOM%27/3*3+6 ))

Bill Gradwohl فرمول بهبودیافته‌ای که برای اعداد مثبت کار می‌کند، مطرح نموده است.

rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))

در اینجا Bill تابع فراگیرنده‌ای را عرضه می‌کند که یک عدد تصادفی بین دو کمیت تعیین شده برگشت می‌دهد.

مثال ‎9-14‎. عدد تصادفی بین کمیت‌ها

#!/bin/bash
#
#                               
# اسکریپت توسط Bill Gradwohl، با اندک اصلاحاتی توسط نگارنده این سند.
#            اصلاحات در سطرهای ‎183‎ و ‎185‎ توسط ‎Anthony Le Clezi‎o است.
#                                     

randomBetween() {
   #   یک عدد تصادفی مثبت یا منفی بین ‎$min‎ و ‎$max‎ و قابل قسمت
   #+                             به ‎$divisibleBy‎ تولید می‌کند.
   # 
   #
   # 

   syntax() {
   #
      echo
      echo    "Syntax: randomBetween [min] [max] [multiple]"
      echo
      echo -n "Expects up to 3 passed parameters, "
      echo    "but all are completely optional."
      echo    "min is the minimum value"
      echo    "max is the maximum value"
      echo -n "multiple specifies that the answer must be "
      echo     "a multiple of this value."
      echo    "    i.e. answer must be evenly divisible by this number."
      echo    
      echo    "If any value is missing, defaults area supplied as: 0 32767 1"
      echo -n "Successful completion returns 0, "
      echo     "unsuccessful completion returns"
      echo    "function syntax and 1."
      echo -n "The answer is returned in the global variable "
      echo    "randomBetweenAnswer"
      echo -n "Negative values for any passed parameter are "
      echo    "handled correctly."
   }

   local min=${1:-0}
   local max=${2:-32767}
   local divisibleBy=${3:-1}
   #

   local x
   local spread

   #          
   [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))

   # 
   if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o  ${min} -eq ${max} ]; then 
      syntax
      return 1
   fi

   #  بررسی اینکه min و max برعکس ارایه شده‌اند
   if [ ${min} -gt ${max} ]; then
      #
      x=${min}
      min=${max}
      max=${x}
   fi

   #  اگر خود min قابل قسمت به ‎$divisibleBy‎ نباشد،
   #+    آنوقت اصلاح min برای قرار گرفتن در محدوده.
   if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then 
      if [ ${min} -lt 0 ]; then
         min=$((min/divisibleBy*divisibleBy))
      else
         min=$((((min/divisibleBy)+1)*divisibleBy))
      fi
   fi

   #  اگر خود max قابل قسمت به ‎$divisibleBy‎ نباشد،
   #+    آنوقت اصلاح max برای قرار گرفتن در محدوده.
   if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then 
      if [ ${max} -lt 0 ]; then
         max=$((((max/divisibleBy)-1)*divisibleBy))
      else
         max=$((max/divisibleBy*divisibleBy))
      fi
   fi

   # ---------------------------------------------------------------------
   #                    

   #         
   #+         
   #+ بین ‎0‎ و ‎abs(max-min)+divisibleBy‎، نه ‎abs(max-min)+1‎ باشد.

   # 
   
   #    تغییر دادن فرمول در جهت استفاده از ‎abs(max-min)+1‎ بازهم جواب‌های
   #+ 
   #+ تعداد دفعات برگشت داده شده برای نقاط انتهایی‏(‎$min‎ و ‎$max‎)‏ به طور
   #+        
   #  ---------------------------------------------------------------------

   spread=$((max-min))
   #
   #+     
   [ ${spread} -lt 0 ] && spread=$((0-spread))
   let spread+=divisibleBy
   randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))   

   return 0

   #   اما، ‎Paulo Marcel Coelho Aragao‎ اشاره می‌کند
   #+  وقتی ‎$max‎ و ‎$min‎ قابل تقسیم به ‎$divisibleBy‎ 
   #+                  
   #
   #    
   #

}

#  
min=-14
max=20
divisibleBy=3


# 
#+

declare -a answer
minimum=${min}
maximum=${max}
   if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then 
      if [ ${minimum} -lt 0 ]; then
         minimum=$((minimum/divisibleBy*divisibleBy))
      else
         minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
      fi
   fi


   # اگر ‎max‎ خودش قابل قسمت به ‎$divisibleBy‎ نباشد،
   #+ آنوقت اصلاح ‎max‎ برای اینکه داخل محدوده باشد‏.

   if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then 
      if [ ${maximum} -lt 0 ]; then
         maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
      else
         maximum=$((maximum/divisibleBy*divisibleBy))
      fi
   fi


#
#+   

disp=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
   answer[i+disp]=0
done


# 
loopIt=1000   #
              #+   

for ((i=0; i<${loopIt}; ++i)); do

   #   توجه نمایید که ما در اینجا ‎min‎ و ‎max‎ را به ترتیب برعکس  
   #+         

   randomBetween ${max} ${min} ${divisibleBy}

   #      
   [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] \
   && echo MIN or MAX error - ${randomBetweenAnswer}!
   [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] \
   && echo DIVISIBLE BY error - ${randomBetweenAnswer}!

   # 
   answer[randomBetweenAnswer+disp]=$((answer[randomBetweenAnswer+disp]+1))
done



# 

for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
   [ ${answer[i+disp]} -eq 0 ] \
   && echo "We never got an answer of $i." \
   || echo "${i} occurred ${answer[i+disp]} times."
done


exit 0

‎$RANDOM‎ چقدر تصادفی است؟ بهترین روش برای بررسی این مطلب نوشتن اسکریپتی است که توزیع اعداد «تصادفی» تولید شده توسط ‎$RANDOM‎ را پیگیری کند. بیایید چندین بار یک تاس ‎$RANDOM‎ را بریزیم . . .

مثال ‎9-15‎. غلتاندن یک تاس منفرد با RANDOM

#!/bin/bash
# 

RANDOM=$$       #    برپاسازی مجدد تولیدکننده عدد تصادفی با ‎ID‎ پردازش اسکریپت.

PIPS=6          #                                     
MAXTHROWS=600   # 
throw=0         #                           

ones=0          #                        
twos=0          #+              
threes=0
fours=0
fives=0
sixes=0

print_result ()
{
echo
echo "ones =   $ones"
echo "twos =   $twos"
echo "threes = $threes"
echo "fours =  $fours"
echo "fives =  $fives"
echo "sixes =  $sixes"
echo
}

update_count()
{
case "$1" in
  0) ((ones++));;   # 
  1) ((twos++));;   # 
  2) ((threes++));; # 
  3) ((fours++));;
  4) ((fives++));;
  5) ((sixes++));;
esac
}

echo


while [ "$throw" -lt "$MAXTHROWS" ]
do
  let "die1 = RANDOM % $PIPS"
  update_count $die1
  let "throw += 1"
done  

print_result

exit $?

#   با فرض اینکه RANDOM تصادفی است، امتیازهای توزیع شده باید نسبتاً مساوی باشند.
#        برای ‎$MAXTHROWS=600‎ تمام گروه‌ها باید حدود ‎100‎ بعلاوه یا منهای ‎20‎ باشند.
#
#      به خاطر داشته باشید که ‎RANDOM‎ یک تولید کننده «شِبه تصادفی»
#+           

#                           
# 
#+         

#                      
#                      ---------------
#  
#  طور چرخشی به هوا پرتاب کند. انتخاب‌ها ‎"HEAD"‎ و ‎"TAIL"‎ هستند.

همان طور که در مثال اخیر دیده‌ایم، بهترین کار، دوباره بنیاد کردن تولید کننده RANDOM در هر نوبت که فراخوانی می‌شود، است. به کار بردن همان بنیاد برای RANDOM، همان گروه اعداد را تکرار می‌کند. ‎[2]‎ ( این متغیر، رفتار تابع ‎random()‎ در C را بازتاب می‌دهد.)

مثال ‎9-16‎. دوباره بنیاد کردن RANDOM

#!/bin/bash
#
#

MAXCOUNT=25       #
SEED=

random_numbers ()
{
local count=0
local number

while [ "$count" -lt "$MAXCOUNT" ]
do
  number=$RANDOM
  echo -n "$number "
  let "count++"
done  
}

echo; echo

SEED=1
RANDOM=$SEED      # تنظیم ‎RANDOM‎ تولید کننده عدد تصادفی را بنیاد می‌کند
echo "Random seed = $SEED"
random_numbers


RANDOM=$SEED      # همان بنیاد برای ‎RANDOM‎ . . .
echo; echo "Again, with same random seed ..."
echo "Random seed = $SEED"
random_numbers    # دقیقاً همان گروه اعداد را بازتولید می‌کند.
                  #
                  #

echo; echo

SEED=2
RANDOM=$SEED      #  تلاش دوباره، اما با یک بنیاد متفاوت . . .
echo "Random seed = $SEED"
random_numbers    #  . . . رشته اعداد متفاوتی ارایه می‌کند.

echo; echo

# ‎RANDOM=$$‎  متغیر ‎RANDOM‎ را بواسطه شماره شناسایی پردازش اسکریپت بنیاد می‌کند.
#  همچنین، بنیاد کردن ‎RANDOM‎ بر اساس فرمان‌های ‎'time'‎ یا ‎'date'‎ نیز مقدور است.

# یک مورد تفننی...
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }'| sed s/^0*//)
#  خروجی شبه-تصادفی از ‎/dev/urandom‎  ‏(فایل-دستگاه شبه-تصادفی سیستم)‏ واکشی می‌شود
#+ سپس توسط ‎"od"‎ به سطر اعداد (اکتال) قابل چاپ تبدیل می‌شود آنوقت ‎"awk"‎ فقط یک
#+  عدد برای SEED بازیابی می‌کند، و سرانجام ‏"sed"‎ همه صفرهای مقدم را حذف می‌کند.
RANDOM=$SEED
echo "Random seed = $SEED"
random_numbers

echo; echo

exit 0

فایل شبه-دستگاه ‎/dev/urandom‎ روشی برای تولید اعداد تصادفی ساختگی بسیار «تصادفی»تر از متغیر ‎$RANDOM‎ فراهم می‌کند. سطر فرمان ‎dd if=/dev/urandom of=targetfile bs=1 count=XX‎ یک فایلِ بخوبی پخش و پلا از اعداد تصادفی ساختگی تولید می‌کند. به هرحال، در یک اسکریپت تخصیص این اعداد به یک متغیر، به راهکاری موقتی از قبیل فیلتر کردن بواسطه od (همچون در مثال فوق، مثال ‎16-14‎، و مثال ‎A-36‎)، یا حتی لوله‌کشی به md5sum (مثال ‎36-16‎ را ببینید) نیاز دارد.

روشهای دیگری نیز برای تولید اعداد تصادفی ساختگی در یک اسکریپت‌ وجود دارد. Awk یک وسیله راحت برای انجام این کار فراهم می‌کند.

مثال ‎9-17‎. اعداد تصادفی ساختگی، با استفاده از awk

#!/bin/bash
#    ‎random2.sh‎ یک عدد تصادفی ساختگی در محدوده ‎0 - 1‎ 
#+  با شش رقم اعشار برگشت می‌دهد. برای مثال: ‎0.822725‎
#     این اسکریپت تابع ‎rand()‎ از awk را به کار می‌برد.

AWKSCRIPT=' { srand(); print rand() } '
#                         فرمان(ها)-پارامترهای عبور داده شده به ‎awk‎
# توجه:  ‎srand()‎ تولید کننده عدد تصادفی awk را دوباره برپا می‌کند.


echo -n "Random number between 0 and 1 = "

echo | awk "$AWKSCRIPT"
# اگر شما ‎'echo'‎ را صرفنظر کنید چه اتفاقی رخ می‌دهد؟

exit 0


#                                  
#                                 ------------

#  ‎(1‎ با استفاده از یک حلقه، ‎10‎ عدد تصادفی مختلف در خروجی چاپ کنید.
#  (تذکر: شما باید تابع ‎srand()‎ را در هر نوبت با یک بنیاد متفاوت به
#+ حلقه عبور بدهید. اگر این مورد را از قلم بیاندازید چه خواهد شد؟)

#       ‎(2 با استفاده از یک ضریب صحیح به عنوان عامل درجه‌بندی، اعداد‎ 
#+                         تصادفی در محدوده ‎10‎ تا ‎100‎ تولید کنید.

#     ‎(3 مانند تمرین شماره ‎2‎، اما این دفعه تولید اعداد صحیح تصادفی.‎

فرمان date نیز برای تولید رشته های عدد صحیح شبه-تصادفی مناسب است.

یادداشت‌ها

[1]

«تصادف» حقیقی در چنان درجه‌ای اگر ابدا موجود باشد، تنها می‌تواند در پدیده‌های طبیعی ناقص فهمیده شده از قبیل انهدام رادیواکتیو، یافت بشود. کامپیوترها فقط تصادف را شبیه‌سازی می‌کنند، و از این جهت رشته های اعداد «تصادفی» تولید کامپیوتر به عنوان شبه-تصادف منسوب می‌شوند.

[2]

بنیاد گروه‌های عددی شبه تصادفیِ تولیدِ کامپیوتر می‌تواند به عنوان یک برچسب هویت در نظر گرفته شود. به عنوان مثال، گروه‌های شبه-تصادفی با بنیاد ‎23‎ را به عنوان گروه‌های ‎#23‎ تصور کنید.

یک خصوصیت گروه‌های عدد شبه-تصادفی، طول چرخه قبل از شروع تکرار گروه است. یک تولید کننده شبه-تصادفی خوب، گروه‌هایی با چرخه‌های بسیار طولانی ارایه می‌کند.