Еще вопрос по случайным числам в Haskell - PullRequest
0 голосов
/ 22 февраля 2020

Я пытаюсь сделать версию игры Voltorb из Pokemon Gold и Silver в Haskell. Теперь для генерации доски я хочу иметь список (l, r, v) триплетов, где l - строка, r - строка, а v - значение поля.

Значения l и r реализованы с учетом списка, так как они должны быть одинаковыми каждый раз. Что касается v, хотя я не могу найти вариант, чтобы реализовать его так, чтобы он был 0,1,2 или 3 «случайным образом» (я знаю, что Haskell является чисто функциональным и нет никакой реальной случайности, это одна из причин почему я борюсь с этим).

Если бы кто-нибудь мог помочь с этим, я был бы очень благодарен. Если бы вы также могли дать краткое объяснение того, почему это решение работает, это мне очень помогло бы.

Моя текущая реализация l и r:

field n = [(l,r,v) | l <- [0..n], r <- [0..n]]

Ответы [ 2 ]

1 голос
/ 23 февраля 2020

Вот краткий способ отделить (1) псевдослучайный чистый код от (2) случайного заполнения псевдослучайного генератора:

--Get the generator in IO monad
main :: IO ()
main = do
  g <- getStdGen
  print $ buildGrid g 5

--Keep as much code as possible pure
buildGrid :: StdGen -> Int -> [(Int, Int, Int)]
buildGrid g n = zipWith ($) ((,,) <$> [0..n] <*> [0..n])
                            (take ((n+1) * (n+1)) $ randomRs (0,3) g)

Или вы можете сохранить исходное понимание списка, но в В этом случае вы должны использовать расширение языка:

{-# LANGUAGE TupleSections #-}
...
buildGrid g n = zipWith ($) [(y,x,) | y <- [0..n], x <- [0..n]] 
                            (take ((n + 1) * (n + 1)) $ randomRs (0,3) g) 
1 голос
/ 22 февраля 2020

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

field n = [(l,r,v) | l <- [0..n], r <- [0..n], v <- someRandomStuff]

, поскольку длина выражения field будет тогда (n + 1) x (n + 1) ) x (длина случайного материала), и вы просто хотите (n + 1) x (n + 1).

Возможность состоит в том, чтобы работать в два этапа:

  1. генерация требуемых (n + 1) * (n + 1) случайных значений от 0 до 3
  2. в сочетании с (l, r) значениями

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

Для заданного начального числа вы можете использовать генератор случайных чисел, возвращаемый функцией mkStdGen, для генерации случайных значений, используя функцию randomRs . Давайте используем сеанс ghci в качестве тестового стенда.

Относительно шага 1:

 λ> import System.Random
 λ> :t randomRs
randomRs :: (Random a, RandomGen g) => (a, a) -> g -> [a]
 λ> 
 λ> seed1=42
 λ> 
 λ> getVSeq n seed = let rng0 = mkStdGen seed  in take  ((n+1)^2) (randomRs (0,3) rng0)
 λ> 
 λ> getVSeq 5 seed1
[1,1,3,0,2,1,0,1,0,1,3,1,2,0,2,3,1,1,3,2,0,2,2,0,2,0,0,0,1,0,2,1,0,2,0,1]
 λ> 
 λ> length $ getVSeq 5 seed1
36
 λ> field0 n = [(l,r) | l <- [0..n], r <- [0..n]]
 λ> field0 5
[(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,0),(1,1),(1,2),(1,3),(1,4),(1,5),(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(3,0),(3,1),(3,2),(3,3),(3,4),(3,5),(4,0),(4,1),(4,2),(4,3),(4,4),(4,5),(5,0),(5,1),(5,2),(5,3),(5,4),(5,5)]
 λ> 

 λ> 
 λ> 
 λ> length  $ field0 5
36
 λ> 

Теперь, что касается шага 2, функция zip почти решает нашу проблему, за исключением того, что мы не получаем точно триплеты:

 λ> 
 λ> sol0 n seed = zip (field0 n) (getVSeq n seed)
 λ> sol0 5 seed1
[((0,0),1),((0,1),1),((0,2),3),((0,3),0),((0,4),2),((0,5),1),((1,0),0),((1,1),1),((1,2),0),((1,3),1),((1,4),3),((1,5),1),((2,0),2),((2,1),0),((2,2),2),((2,3),3),((2,4),1),((2,5),1),((3,0),3),((3,1),2),((3,2),0),((3,3),2),((3,4),2),((3,5),0),((4,0),2),((4,1),0),((4,2),0),((4,3),0),((4,4),1),((4,5),0),((5,0),2),((5,1),1),((5,2),0),((5,3),2),((5,4),0),((5,5),1)]
 λ> 

Так что нам нужно немного помассировать результат sol0:

 λ> 
 λ> sol1 n seed = let flatten = (\((a,b),c) -> (a,b,c))  in  map flatten (sol0 n seed)
 λ> sol1 5 seed1
[(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]
 λ> 

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

Также, как упоминалось Томасом М. Дюбюссоном, этот вопрос был рассмотрен в нескольких SO-вопросах. Вы можете использовать местную поисковую систему. Вот один из последних , например.

Что если вам нужно вернуть генератор для повторного использования?

В этом случае вам нужна функция который берет предварительно созданный генератор и возвращает ОБА список «триплетов» и (конечное состояние) генератора в виде пары (list, finalRng).

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


import  System.Random
import  Control.Monad.Random

-- returns the random v values AND the final state of the generator
seqAndGen :: RandomGen tg => (Int,Int) -> Int-> tg -> ([Int], tg)
seqAndGen range count rng0 =
    if (count <= 0)
        then ([],rng0)
        else
            let (v,rng1) = randomR range rng0
                nextSeq  = seqAndGen range (count-1) rng1  -- recursive call
            in
                (v:(fst nextSeq), snd nextSeq)

-- returns the "field" values AND the final state of the generator
fieldAndGen :: RandomGen tg => Int -> tg -> ([(Int,Int,Int)], tg)
fieldAndGen n rng0 =
    let  field0  = [(l,r) | l <- [0..n], r <- [0..n]]
         range   = (0,3)       -- at that level, range gets hardwired
         count   = (n+1)*(n+1) -- number of field/board positions
         pair    = seqAndGen range count rng0  -- the hard work
         vSeq    = fst pair
         endRng  = snd pair
         flatten = \((a,b),c) -> (a,b,c)
         field   = map flatten  (zip field0 vSeq)
    in
         (field, endRng)

Основная программа:

main = do
    let mySeed = 42
        n      = 5
    putStrLn $ "seed=" ++ (show mySeed) ++ "  " ++ "n=" ++ (show n)
    -- get a random number generator:
    let rng0    = mkStdGen mySeed  

    let (field, endRng) = fieldAndGen n rng0
        fieldv = map  (\(a,b,c) -> c)  field
    putStrLn $ "endRng = " ++ (show endRng)
    putStrLn $ "field  = " ++ (show field)

Вывод программы:


seed=42  n=5
endRng = 1388741923 1700779863
field  = [(0,0,1),(0,1,1),(0,2,3),(0,3,0),(0,4,2),(0,5,1),(1,0,0),(1,1,1),(1,2,0),(1,3,1),(1,4,3),(1,5,1),(2,0,2),(2,1,0),(2,2,2),(2,3,3),(2,4,1),(2,5,1),(3,0,3),(3,1,2),(3,2,0),(3,3,2),(3,4,2),(3,5,0),(4,0,2),(4,1,0),(4,2,0),(4,3,0),(4,4,1),(4,5,0),(5,0,2),(5,1,1),(5,2,0),(5,3,2),(5,4,0),(5,5,1)]

Обратите внимание, что есть возможный вариант, когда вместо передачи генератора вы передаете бесконечный список значений v, сгенерированных функцией randomRs . Для этой цели удобно использовать функцию splitAt . Но это предполагает, что вы используете случайность только для значений v и ничего больше, поэтому он немного менее общий и менее гибкий.

...