Попытка выяснить "случайную" функцию в Haskell

Я только что узнал о функции random.

Насколько я понимаю, функция random принимает значение типа, которое является экземпляром RandomGen и возвращает случайное значение, значение которого мы можем указать. С другой стороны, mksdGen принимает Int и генерирует случайный генератор, которому нужна функция random.

Я пытался генерировать случайные значения Bool. Для этого я сделал функцию randomBool.

randomBool :: Int -> Bool
randomBool = fst . random . mkStdGen

Тогда я нашел намного больше True, чем False с малыми числами. И мне было любопытно, и я проверил его как

> length $ takeWhile randomBool [1..]
53667

Я думаю, это означает, что для первых 53667 положительных целых чисел random . mkStdGen возвращает True, что для меня не кажется очень случайным. Это нормально? Или я делаю что-то не так, что облегчить True?

Ответ 1

Неформально, когда вы вызываете mkStdGen с семенами, которые находятся близко друг к другу, вы получите два "похожих" генератора. В вашем примере вы фактически создаете новые генераторы для каждого поставляемого семени, и поскольку эти семена 1, 2, 3 и т.д., Они будут давать похожие потоки.

Когда вы вызываете random с генератором, вы фактически возвращаете новый генератор во втором элементе пары:

Prelude System.Random> random (mkStdGen 100) :: (Bool, StdGen)
(True,4041414 40692)

Так что хорошая идея - использовать этот предоставленный генератор для следующего вызова random. То есть.

Prelude System.Random> let (i, gen0) = random (mkStdGen 100) :: (Bool, StdGen)
Prelude System.Random> let (j, gen1) = random gen0           :: (Bool, StdGen)
Prelude System.Random> let (k, gen2) = random gen1           :: (Bool, StdGen)
Prelude System.Random> (i, j, k)
(True, False, False)

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

Prelude System.Random> take 10 $ randoms (mkStdGen 100) :: [Bool]
[True,False,False,False,False,True,True,False,False,True]

Если вы не особенно заботитесь о том, чтобы быть в IO (это происходит), вы можете использовать randomIO:

Prelude System.Random> import Control.Monad
Prelude System.Random Control.Monad> replicateM 10 randomIO :: IO [Bool]
[True,True,False,True,True,False,False,False,True,True]

Этот раздел LYAH может быть полезным.

Ответ 2

Компьютеры детерминированы и не могут генерировать случайные числа. Скорее, они полагаются на математические формулы, которые возвращают распределение чисел, которые выглядят случайными. Они называются генераторами псевдослучайных чисел. Однако из-за детерминизма мы сталкиваемся с проблемой, что, если мы будем использовать эти формулы одинаково во время каждого вызова нашей программы, мы получим одинаковые генераторы случайных чисел. Очевидно, это нехорошо, так как мы хотим, чтобы наши числа были случайными! Таким образом, мы должны предоставить случайному генератору начальное начальное значение, которое изменяется от run-to-run. Для большинства людей (т.е. Тех, кто не делает криптографические материалы) генератор случайных чисел высевается по текущему времени. В Haskell этот псевдослучайный генератор представлен типом StdGen. Функция mkStdGen используется для создания генератора случайных чисел с семенем. В отличие от C, где есть один генератор глобальных случайных чисел, в Haskell вы можете иметь столько, сколько хотите, и вы можете создавать их с разными семенами.

Однако существует оговорка: поскольку числа являются псевдослучайными, нет никакой гарантии, что генераторы случайных чисел, созданные с разными семенами, возвращают числа, которые выглядят случайными по сравнению с другими. Это означает, что когда вы вызываете randomBool и даете ему последовательные значения семени, нет никакой гарантии, что номер, который вы получаете из созданного StdGen, является случайным по сравнению с StdGen, засеянным его преемником. Вот почему вы получаете почти 50000 True.

Чтобы получить данные, которые на самом деле выглядят случайными, вам нужно продолжать использовать один и тот же генератор случайных чисел. Если вы заметили, функция random Haskell имеет тип StdGen -> (a, StdGen). Поскольку Haskell является чистым, функция random принимает генератор случайных чисел, генерирует псевдослучайное значение (первый элемент возвращаемого значения), а затем возвращает новый StdGen, который представляет генератор, засеянный исходным семенем, но готовый дать новое случайное число. Вам нужно сохранить этот другой StdGen и передать его следующей функции random, чтобы получить случайные данные.

Вот пример, генерирующий три случайных типа bools, a, b и c.

randomBools :: StdGen -> (Bool, Bool, Bool)
randomBools gen = let (a, gen') = random gen
                      (b, gen'') = random gen''
                      (c, gen''') = random gen'''
                   in (a, b, c)

Обратите внимание, как переменная gen "пронумерована" через вызовы случайным образом.

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

import Control.Monad.State
import System.Random

type MyRandomMonad a = State StdGen a

myRandom :: Random a => MyRandomMonad a
myRandom = do
  gen <- get -- Get the StdGen state from the monad
  let (nextValue, gen') = random gen -- Generate the number, and keep the new StdGen
  put gen' -- Update the StdGen in the monad so subsequent calls generate new random numbers
  return nextValue

Теперь вы можете написать функцию randomBools как:

randomBools' :: StdGen -> (Bool, Bool, Bool)
randomBools' gen = fst $ runState doGenerate gen
  where doGenerate = do
          a <- myRandom
          b <- myRandom
          c <- myRandom
          return (a, b, c)

Если вы хотите создать (конечный) список Bool s, вы можете сделать

randomBoolList :: StdGen -> Int -> ([Bool], StdGen)
randomBoolList gen length = runState (replicateM length myRandom) gen

Обратите внимание на то, как мы возвращаем StdGen в качестве второго элемента возвращенной пары, чтобы он мог быть присвоен новым функциям.

Более просто, если вы просто хотите создать бесконечный список случайных значений одного и того же типа из StdGen, вы можете использовать функцию randoms. Это имеет подпись (RandomGen g, Random a) => g -> [a]. Чтобы создать бесконечный список Bool с использованием начального семестра x, вы просто запускаете randoms (mkStdGen x). Вы можете реализовать свой пример, используя length $ takeWhile id (randoms (mkStdGen x)). Вы должны убедиться, что вы получаете разные значения для разных начальных значений x, но всегда одно и то же значение, если вы поставляете те же x.

Наконец, если вы не заботитесь о привязке к монаде IO, Haskell также предоставляет генератор глобальных случайных чисел, подобно императивным языкам. Вызов функции randomIO в монаде IO даст вам случайное значение любого типа, который вам нравится (пока он является экземпляром класса random, по крайней мере). Вы можете использовать это аналогично myRandom выше, за исключением монады IO. У этого есть дополнительное удобство, что он предварительно посеян во время выполнения Haskell, то есть вам не нужно даже беспокоиться о создании StdGen. Итак, чтобы создать случайный список из 10 Bool в монаде IO, все, что вам нужно сделать, это replicateM 10 randomIO :: IO [Bool].

Надеюсь, что это поможет:)

Ответ 3

Случайный генератор, созданный mkStdGen, не обязательно генерирует случайное значение в качестве его первого результата. Чтобы создать следующее случайное число, используйте случайный генератор, возвращенный предыдущим вызовом random.

Например, этот код генерирует 10 Bool s.

take 10 $ unfoldr (Just . random) (mkStdGen 1) :: [Bool]