Опубликован: 19.09.2008 | Доступ: свободный | Студентов: 656 / 70 | Оценка: 4.50 / 5.00 | Длительность: 21:25:00
Лекция 28:

Случайные числа

< Лекция 27 || Лекция 28: 12
Аннотация: В этой лекции мы изучим как правильно работать с генератором псевдослучайных чисел. Рассмотрим библиотеку Random, ее классы и основные возможности этой библиотеки

module Random (
RandomGen(next, split, genRange),
StdGen, mkStdGen,
Random( random,   randomR, 
randoms,  randomRs,
randomIO, randomRIO ),
getStdRandom, getStdGen, setStdGen, newStdGen
  ) where

---------------- Класс RandomGen ------------------------

class RandomGen g where
  genRange :: g -> (Int, Int)
  next     :: g -> (Int, g)
  split    :: g -> (g, g)

---------------- Стандартный экземпляр класса RandomGen -----------
data StdGen = ... -- Абстрактный

instance RandomGen StdGen where ...
instance Read     StdGen where ...
instance Show     StdGen where ...

mkStdGen :: Int -> StdGen

---------------- Класс Random ---------------------------
class Random a where
   randomR :: RandomGen g => (a, a) -> g -> (a, g)
   random  :: RandomGen g => g -> (a, g)

   randomRs :: RandomGen g => (a, a) -> g -> [a]
   randoms  :: RandomGen g => g -> [a]

   randomRIO :: (a,a) -> IO a
   randomIO  :: IO a

instance Random Int     where ...
instance Random Integer where ...
instance Random Float   where ...
instance Random Double  where ...
instance Random Bool    where ...
instance Random Char    where ...

---------------- Глобальный генератор случайных чисел ----------------
newStdGen    :: IO StdGen
setStdGen    :: StdGen -> IO ()
getStdGen    :: IO StdGen 
getStdRandom :: (StdGen -> (a, StdGen)) -> IO a

Библиотека Random имеет дело со стандартной задачей генерации псевдослучайных чисел. Библиотека делает возможным генерацию повторных результатов, посредством старта с указанного начального случайного числа генератора, или получения различных результатов при каждом выполнении, посредством использования инициализируемого системой генератора или предоставления случайного значения из некоторого другого источника.

Библиотека делится на два уровня:

  • Ядро генератора случайных чисел обеспечивает поставку битов. Класс RandomGen обеспечивает общий интерфейс к таким генераторам.
  • Класс Random обеспечивает способ извлечь конкретные значения из генератора случайных чисел. Например, экземпляр Float класса Random позволяет генерировать случайные значения типа Float.

27.1. Класс RandomGen и генератор StdGen

Класс RandomGen обеспечивает общий интерфейс к генераторам случайных чисел.

class RandomGen g where
    genRange :: g -> (Int,Int)
    next     :: g  -> (Int, g)
    split    :: g -> (g, g)
  
    - Метод по умолчанию
    genRange g = (minBound,maxBound)
  • Операция genRange дает диапазон значений, возвращенный генератором.

    Требуется, чтобы:

    • Если (a,b) = genRange g, то a < b.
    • genRange \perp  /= \perp.

    Второе условие гарантирует, что genRange не может проверять свой аргумент, и следовательно значение, которое он возвращает, можно определить только посредством экземпляра класса RandomGen. Это, в свою очередь, позволяет реализации выполнять один вызов genRange, чтобы установить диапазон генератора, не касаясь того, что генератор, возвращенный (скажем) next, мог бы иметь диапазон, отличный от диапазона генератора, переданного next.

  • Операция next возвращает Int, который равномерно распределен в диапазоне, возвращенном genRange (включая обе конечные точки), и новый генератор.
  • Операция split позволяет получить два независимых генератора случайных чисел. Это очень полезно в функциональных программах (например, при передаче генератора случайных чисел в рекурсивных вызовах), но очень мало работы было сделано над статистически надежными реализациями split ([1, 4 ] - единственные примеры, о которых мы знаем).

Библиотека Random предоставляет один экземпляр RandomGen - абстрактный тип данных StdGen:

data StdGen = ... - Абстрактный
  
  instance RandomGen StdGen where ...
  instance Read      StdGen where ...
  instance Show      StdGen where ...
  
  mkStdGen :: Int -> StdGen

Экземпляр StgGen класса RandomGen имеет genRange по крайней мере 30 битов.

Результат повторного использования next должен быть по крайней мере таким же статистически надежным, как "Минимальный стандартный генератор случайных чисел", описанный [2, 3 ]. До тех пор, пока нам больше ничего неизвестно о реализациях split, все, чего мы требуем, - чтобы split производил генераторы, которые являются: (a) не идентичными и (b) независимо надежными в только что данном смысле.

Экземпляры Show/Read класса StdGen обеспечивают примитивный способ сохранить состояние генератора случайных чисел. Требуется, чтобы read (show g) == g.

Кроме того, read можно использовать для отображения произвольной строки (необязательно порожденной show ) на значение типа StdGen. Вообще, экземпляр read класса StdGen обладает следующими свойствами:

  • Он гарантирует, что завершится успешно для любой строки.
  • Он гарантирует, что потребит только конечную часть строки.
  • Различные строки аргумента, вероятно, приведут к различным результатам.

Функция mkStdGen обеспечивает альтернативный способ создания начального генератора, посредством отображения Int в генератор. Опять, различные аргументы, вероятно, должны породить различные генераторы.

Программисты могут, конечно, поставлять свои собственные экземпляры класса RandomGen.

Предупреждение о реализациях. Внешне привлекательная реализация split выглядит так:

instance RandomGen MyGen where
    ...
    split g = (g, variantOf g)

Здесь split возвращает сам g и новый генератор, полученный из g. Но теперь рассмотрим эти два предположительно независимых генератора:

g1 = snd (split g)
  g2 = snd (split (fst (split g)))

Если split искренне поставляет независимые генераторы (как указано), то g1 и g2 должны быть независимы, но на самом деле они оба равны variantOf g. Реализации вышеупомянутого вида не отвечают требованиям спецификации.

< Лекция 27 || Лекция 28: 12
Николай Щербаков
Николай Щербаков
Россия, Москва
Олег Корсак
Олег Корсак
Латвия, Рига