۲۲ - ۳اعداد تصادفی
مثلِ فصلِ قبل با یه مثالِ مفصّل شروع میکنیم. این مثال کمک میکنه یه کم با مسائلی که میشه با نوعداده ِ State
حل کرد آشنا بشین.
در این مثال از نسخهی ۱٫۱ کتابخونه ِ random
استفاده میکنیم.
اول یه نگاه به توابعی که میخوایم استفاده کنیم بندازیم. قبلاً تو فصلی که داربازی رو درست کردیم از کتابخونه ِ System.Random
استفاده کردیم، اما برای این مثال از توابعِ متفاوتی استفاده میکنیم. تو این مثال نگرش کلی داریم؛ نمیخوایم وارد جزئیاتِ طرز کار این ژنراتورها بشیم.
System.Random
برای ایجاد ِ مقادیرِ نیمهتصادفی طراحی شده. میشه اون مقادیر رو با تأمینِ یه مقدار دانهای بدست آورد، یا با استفاده از یه ژنراتوری که سیستم خودش راه میندازه. از کتابخونه اینها رو استفاده میکنیم:
۱.
StdGen
یکی از تایپهاییه که اینجا میبینیم، و ضرب ِ دو مقدارِ Int32
ِه. پس یه مقدار از تایپِ StdGen
همیشه متشکل از دو مقدارِ Int32
ِه. هردوشون مقادیرِ دانهای برای ایجاد ِ عددِ تصادفی ِ بعدیاند.
۲.
تایپِ تابعِ mkStdGen
از این قراره:
mkStdGen :: Int -> StdGen
اینجا با تعریفِ این تابع کاری نداریم، الان اون جزئیات مهم نیستن. این تابع یه آرگومانِ Int
میگیره و به یه ژنراتور نگاشتش میده و یه مقدارِ StdGen
برمیگردونه، که در واقع یه جفت از مقادیرِ Int32
ِه.
۳.
تایپِ تابعِ next
از این قراره:
next :: g -> (Int, g)
که g
یه مقدار با تایپِ StdGen
ِه. اون Int
در اولِ توپل، عددِ نیمهتصادفی ایه که از مقدارِ StdGen
ایجاد شده؛ عضوِ دومِ توپل یه مقدارِ StdGen
ِ جدیده.
۴.
تایپِ تابعِ random
از این قراره:
random :: (RandomGen g, Random a)
=> g -> (a, g)
این هم شبیهِ next
ِه، ولی با این تابع میشه مقادیرِ تصادفی ِ غیر از اعداد هم ایجاد کرد. این که چه توپِلی درست شه با تایپ تعیین میشه.
خوب حالا یه ذره طرز کارشون رو ببینیم:
Prelude> import System.Random
Prelude> mkStdGen 0
1 1
Prelude> :t mkStdGen 0
mkStdGen 0 :: StdGen
Prelude> let sg = mkStdGen 0
Prelude> :t next sg
next sg :: (Int, StdGen)
Prelude> next sg
(2147482884, 40014 40692)
Prelude> next sg
(2147482884, 40014 40692)
اون تابعِ پشتپرده که مقادیرِ خروجی رو تعیین میکنه خالص ِه، به همین خاطر دو بار یک مقدار گرفتیم؛ تایپ اجازهی هیچ اثری رو نمیده تا اتفاقات عجیبغریب نیوفتن. یه نسخهی دیگه از sg
با یه ورودیِ دیگه به StdGen
تعریف کنین و ببینین چی میشه.
نهایتاً به یه مقدار به اسمِ next sg
رسیدیم. حالا اگه بخوایم با استفاده از اون عددِ تصادفی ِ بعدی رو ایجاد کنیم، باید مقدار StdGen
از اون توپل رو دوباره به next
بدیم. با تابع snd
میشه اون مقدار رو بیرون کشید و به عنوانِ ورودی به next
داد:
Prelude> snd (next sg)
40014 40692
Prelude> let newSg = snd (next sg)
Prelude> newSg
newSg :: StdGen
Prelude> next newSg
(2092764894,1601120196 1655838864)
اگه دوباره newSg
رو به next
بدین باز هم همون جواب رو میگیرین، اما میشه دوباره مقدارِ StdGen
رو استخراج کرد و به next
داد تا یه توپل ِ جدید گرفت:
Prelude> next (snd (next newSg))
(1679949200,1635875901 2103410263)
حالا چندتا مثال از random
ببینیم. چون random
میتونه از تایپهای مختلف مقادیر ایجاد کنه، باید تعیین کنیم از چه تایپی استفاده کنه:
Prelude> :t random newSg
random newSg :: Random a => (a, StdGen)
Prelude> random newSg :: (Int, StdGen)
(138890298504988632,439883729 1872071452)
Prelude> random newSg :: (Double, StdGen)
(0.41992072972993366,439883729 1872071452)
سادهست، اگه یک عدد از یه بازه بخوایم چطور؟
Prelude> :t randomR
randomR :: (RandomGen g, Random a)
=> (a, a) -> g -> (a, g)
Prelude> randomR (0, 3) newSg :: (Int, StdGen)
(1,1601120196 1655838864)
Prelude> randomR (0, 3) newSg :: (Double, StdGen)
(1.259762189189801,439883729 1872071452)
باید حالت ِ جدیدِ ژنراتور ِ عددِ تصادفی رو به تابعِ next
بدیم تا یه مقدارِ جدید بگیریم:
λ> let rx :: (Int, StdGen); rx = random (snd sg3)
λ> rx
(2387576047905147892,1038587761 535353314)
λ> snd rx
1038587761 535353314
اینطور زنجیرکردنِ حالت ممکنه خستهکننده شه. هدفِ ما تو این فصل حلِ این مسئلهست.