ЗНАЕТЕ ЛИ ВЫ?

Ещё немного функций, работающих со случайностью



 
 

А что если бы мы захотели подкинуть четыре монеты? Или пять? На этот случай есть функция randoms, которая принимает генера- тор и возвращает бесконечную последовательность значений, ос- новываясь на переданном генераторе.

ghci> take 5 $ randoms (mkStdGen 11) :: [Int] [–1807975507,545074951,–1015194702,–1622477312,–502893664]

ghci> take 5 $ randoms (mkStdGen 11) :: [Bool]


 

[True,True,True,True,False]

 
 

ghci> take 5 $ randoms (mkStdGen 11) :: [Float] [7.904789e–2,0.62691015,0.26363158,0.12223756,0.38291094]

Почему функция randoms не возвращает новый генератор вмес- те со списком? Мы легко могли бы реализовать функцию randoms вот так:

randoms' :: (RandomGen g, Random a) => g –> [a]

 
 

randoms' gen = let (value, newGen) = random gen in value:randoms' newGen

Рекурсивное определение. Мы получаем случайное значение и новый генератор из текущего генератора, а затем создаём список, который помещает сгенерированное значение в «голову» списка, а значения, сгенерированные по новому генератору, – в «хвост». Так как теоретически мы можем генерировать бесконечное коли- чество чисел, вернуть новый генератор нельзя.

 
 

Мы могли бы создать функцию, которая генерирует конечный поток чисел и новый генератор таким образом:

finiteRandoms :: (RandomGen g, Random a, Num n) => n –> g –> ([a], g) finiteRandoms 0 gen = ([], gen)

finiteRandoms n gen =

let (value, newGen) = random gen

 
 

(restOfList, finalGen) = finiteRandoms (n–1) newGen in (value:restOfList, finalGen)

Опять рекурсивное определение. Мы полагаем, что если нам нужно 0 чисел, мы возвращаем пустой список и исходный гене- ратор. Для любого другого количества требуемых случайных зна- чений вначале мы получаем одно случайное число и новый гене- ратор. Это будет «голова» списка. Затем мы говорим, что «хвост» будет состоять из (n – 1) чисел, сгенерированных новым генерато- ром. Далее возвращаем объединенные «голову» и остаток списка и финальный генератор, который мы получили после вычисления (n – 1) случайных чисел.

Ну а если мы захотим получить случайное число в некотором диапазоне? Все случайные числа до сих пор были чрезмерно боль- шими или маленькими. Что если нам нужно подбросить игральную кость?.. Для этих целей используем функцию randomR. Она имеет следующий тип:


 

 
 

randomR :: (RandomGen g, Random a) :: (a, a) –> g –> (a, g)

Это значит, что функция похожа на функцию random, но полу- чает в первом параметре пару значений, определяющих верхнюю и нижнюю границы диапазона, и возвращаемое значение будет в границах этого диапазона.

ghci> randomR (1,6) (mkStdGen 359353)

(6,1494289578 40692)

ghci> randomR (1,6) (mkStdGen 35935335)

 
 

(3,1250031057 40692)

Также существует функция randomRs, которая возвращает по- ток случайных значений в заданном нами диапазоне. Смотрим:

 
 

ghci> take 10 $ randomRs ('a','z') (mkStdGen 3) :: [Char] "ndkxbvmomg"

Неплохо, выглядит как сверхсекретный пароль или что-то в этом духе!

 

Случайность и ввод-вывод

Вы, должно быть, спрашиваете себя: а какое отношение имеет эта часть главы к системе ввода-вывода? Пока ещё мы не сделали ничего, что имело бы отношение к вводу-выводу! До сих пор мы создавали генераторы случайных чисел вручную, основывая их на некотором целочисленном значении. Проблема в том, что если де- лать так в реальных программах, они всегда будут возвращать оди- наковые последовательности случайных чисел, а это нас не вполне устраивает. Вот почему модуль System.Random содержит действие ввода-вывода getStdGen, тип которого – IO StdGen. При запуске про- грамма запрашивает у системы хороший генератор случайных чи- сел и сохраняет его в так называемом глобальном генераторе. Фун- кция getStdGen передаёт этот глобальный генератор вам, когда вы связываете её с чем-либо.

 
 

Вот простая программа, генерирующая случайную строку.

import System.Random

 

main = do

gen <– getStdGen

 
 

putStrLn $ take 20 (randomRs ('a','z') gen)


 

 
 

Теперь проверим:

$ ./random_string pybphhzzhuepknbykxhe

 

$ ./random_string eiqgcxykivpudlsvvjpg

 

$ ./random_string nzdceoconysdgcyqjruo

 

 
 

$ ./random_string bakzhnnuzrkgvesqplrx

Но будьте осторожны: если дважды вызвать функцию getStdGen, система два раза вернёт один и тот же генератор. Если сделать так:

import System.Random

 

main = do

gen <– getStdGen

putStrLn $ take 20 (randomRs ('a','z') gen) gen2 <– getStdGen

 
 

putStr $ take 20 (randomRs ('a','z') gen2)

вы получите дважды напечатанную одинаковую строку.

 
 

Лучший способ получить две различные строки – использовать действие ввода-вывода newStdGen, которое разбивает текущий гло- бальный генератор на два генератора. Действие замещает глобаль- ный генератор одним из результирующих генераторов и возвраща- ет второй генератор в качестве результата.

import System.Random

 

main = do

gen <– getStdGen

putStrLn $ take 20 (randomRs ('a','z') gen) gen' <– newStdGen

 
 

putStr $ take 20 (randomRs ('a','z') gen')

Мы не только получаем новый генератор, когда связываем с чем-либо значение, возвращённое функцией newStdGen, но и заме- няем глобальный генератор; так что если мы воспользуемся функ-


 

цией getStdGen ещё раз и свяжем его с чем-нибудь, мы получим гене- ратор, отличный от gen.

 
 

Вот маленькая программка, которая заставляет пользователя угадывать загаданное число.

import System.Random import Control.Monad(when)

 

main = do

gen <- getStdGen askForNumber gen

 

askForNumber :: StdGen -> IO () askForNumber gen = do

let (randNumber, newGen) = randomR (1,10) gen :: (Int, StdGen) putStr "Я задумал число от 1 до 10. Какое? "

numberString <- getLine

when (not $ null numberString) $ do let number = read numberString if randNumber == number

then putStrLn "Правильно!"

else putStrLn $ "Извините, но правильный ответ "

++ show randNumber

 
 

askForNumber newGen

Здесь мы создаём функцию askForNumber, принимающую гене- ратор случайных чисел и возвращающую действие ввода-вывода, которое спросит число у пользователя и сообщит ему, угадал ли он. В этой функции мы сначала генерируем случайное число и новый генератор, основываясь на исходном генераторе; случайное число мы называем randNumber, а новый генератор – newGen. Допустим, что было сгенерировано число 7. Затем мы предлагаем пользователю угадать, какое число мы задумали. Вызываем функцию getLine и свя- зываем её результат с идентификатором numberString. Если пользо- ватель введёт 7, numberString будет равно 7. Далее мы используем функцию when для того, чтобы проверить, не ввёл ли пользователь пустую строку. Если ввёл, выполняется пустое действие ввода-выво- да return(), которое закончит выполнение программы. Если поль- зователь ввёл не пустую строку, выполняется действие, состоящее из блока do. Мы вызываем функцию read со значением numberString в качестве параметра, чтобы преобразовать его в число; образец number становится равным 7.


 

ПРИМЕЧАНИЕ.Наминуточку!.. Еслипользовательвведёт что-ни- будь, чего функция read не сможет прочесть (например, "хa-хa"), наша программа «упадёт» с ужасным сообщением об ошибке. Если вы не хотите, чтобы программа «падала» на некорректном вводе, используйте функцию reads: она возвращает пустой спи- сок, если у функции не получилось считать строку. Если чтение прошло удачно, функция вернёт список из одного элемента, со- держащий пару, один компонент которой содержит желаемый элемент; второй компонент хранит остаток строки после считы- вания первого.

Мы проверяем, равняется ли number случайно сгенерирован- ному числу, и выдаём пользователю соответствующее сообщение. Затем рекурсивно вызываем нашу функцию askForNumber, но на сей

раз с вновь полученным генера- тором; это возвращает нам та- кое же действие ввода-вывода, как мы только что выполнили, но основанное на новом гене- раторе. Затем это действие вы- полняется.

Функция main состоит всего лишь из получения генератора случайных чисел от системы и вызова функции askForNumber с этим генератором для того, чтобы получить первое дейс- твие.

 
 

Посмотрим, как работает наша программа!

$ ./guess_the_number

Я задумал число от 1 до 10. Какое? 4

Извините, но правильный ответ 3

Я задумал число от 1 до 10. Какое? 10

Правильно!

Я задумал число от 1 до 10. Какое? 2

Извините, но правильный ответ 4

Я задумал число от 1 до 10. Какое? 5


 

 
 

Извините, но правильный ответ 10 Я задумал число от 1 до 10. Какое?

Можно написать эту же программу по-другому:

import System.Random import Control.Monad (when)

 

main = do

gen <- getStdGen

let (randNumber, _) = randomR (1,10) gen :: (Int, StdGen) putStr "Я задумал число от 1 до 10. Какое? "

numberString <- getLine

when (not $ null numberString) $ do let number = read numberString if randNumber == number

then putStrLn "Правильно!"

else putStrLn $ "Извините, но правильный ответ "

++ show randNumber

 
 

newStdGen main

Эта версия очень похожа на предыдущую, но вместо создания функции, которая принимает генератор и вызывает сама себя рекурсивно с вновь полученным генератором, мы производим все действия внутри функции main. После того как пользователь полу- чит ответ, угадал ли он число, мы обновим глобальный генератор и снова вызовем функцию main. Оба подхода хороши, но мне больше нравится первый способ, так как он предусматривает меньше дейс- твий в функции main и даёт нам функцию, которую мы можем легко использовать повторно.

 





Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.238.184.78 (0.008 с.)