Выборка последовательностей случайных чисел в 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
и тогда вы получите другое семя (практически) каждый раз.