Выборка последовательностей случайных чисел в Haskell

мне нужны небольшие списки гауссовых случайных чисел для моделирования, и поэтому я попробовал следующий:

import System.Random

seed = 10101
gen = mkStdGen seed

boxMuller mu sigma (r1,r2) =  mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2) 

это просто алгоритм бокса-Мюллера-учитывая R1, r2 равномерные случайные числа в интервале [0,1], он возвращает гауссово случайное число.

normals 0 g = [] 
normals n g = take n $ map (boxMuller 0 1) $ pairs $ randoms g
    where pairs (x:y:zs) = (x,y):(pairs zs)

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

проблема с этим должна быть очевидной: она генерирует всегда одну и ту же последовательность, потому что я всегда использую то же семя! Я не получаю новые последовательности, я получаю только первые N значений последовательности все время.

то, что я притворялся ясно, было то, когда я печатаю:

x = normal 10 
y = normal 50

Я бы х для первых 10 значений map (boxMuller 0 1) $ pairs $ randoms g и y будут следующими 50 значениями в этом списке и так далее.

конечно, это невозможно, потому что функция всегда должна возвращать одни и те же значения при одном и том же входе. Как мне избежать этой ловушки?

3 ответов


я думаю, что делать ваши вычисления, которые требуют случайных чисел внутри монады, которая абстрагирует генератор, - самая чистая вещь. Вот как будет выглядеть этот код:

мы собираемся поместить экземпляр StdGen в монаду состояния, а затем предоставить некоторый сахар над методом get и set монады состояния, чтобы дать нам случайные числа.

сначала загрузите модули:

import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))

(обычно я бы, вероятно, не указывал импорт, но это делает легко понять, откуда берется каждая функция.)

затем мы определим наши "вычисления, требующие случайных чисел" монады; в этом случае, псевдоним State StdGen под названием R. (Потому что " Random "и" Rand " уже означают что-то другое.)

type R a = State StdGen a

способ работы R: вы определяете вычисление, которое требует потока случайных чисел (монадическое "действие"), а затем вы "запускаете его" с runRandom:

runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed

это требует действия и семя, и возвращает результаты действия. Как обычно evalState, runReader, etc.

теперь нам просто нужен сахар вокруг государственной монады. Мы используем get чтобы получить StdGen, и мы используем put для установки нового состояния. Это означает, что для получения одного случайного числа мы напишем:

rand :: R Double
rand = do
  gen <- get
  let (r, gen') = random gen
  put gen'
  return r

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

это "действие", которое можно запустить с помощью runRandom, поэтому давайте попробуем:

ghci> runRandom rand 42
0.11040701265689151                           
it :: Double     

это чистая функция, поэтому, если вы запустите ее снова с теми же аргументами, вы получите тот же результат. Нечистота остается внутри "действия", которое вы передаете runRandom.

в любом случае, ваша функция хочет пары случайных чисел, поэтому давайте напишем действие для получения пара случайных чисел:

randPair :: R (Double, Double)
randPair = do
  x <- rand
  y <- rand
  return (x,y)

Run это с runRandom, и вы увидите два разных числа в паре, как и следовало ожидать. Но обратите внимание, что вам не нужно было снабжать "rand" аргументом; это потому, что функции чисты, но "rand" - это действие, которое не обязательно должно быть чистым.

теперь мы можем реализовать ваш


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

аналогичная проблема возникает для компиляторов и других кодов, которым требуется набор уникальных символов. Это просто настоящая боль в Haskell, потому что вы состояние потока (генератора случайных чисел или генератора символов) по всему коду.

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


вы также можете обойти проблему, используя newStdGen и тогда вы получите другое семя (практически) каждый раз.