Друг въпрос относно произволните числа в Haskell

Опитвам се да направя версия на играта 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]]


person Shini    schedule 22.02.2020    source източник
comment
Като се има предвид, че има много въпроси за произволни числа, какви са ви проблемите с адаптирането на отговора? Какво ви попречи да генерирате списък с произволни позиции, vs, и да дефинирате field n vs = [ ... , v <- vs]?   -  person Thomas M. DuBuisson    schedule 22.02.2020
comment
@ThomasM.DuBuisson Мисля, че вместо да компресирам списък с произволни позиции в координати на мрежата, това ще генерира декартово произведение на координатите и произволностите, което ще доведе до rows * cols * vs стойности, вместо желаните rows * cols стойности.   -  person MikaelF    schedule 23.02.2020


Отговори (2)


Ако разбирам правилно въпроса, трябва да има една произволна стойност на (Int, Int) позиция на дъската. Така че проблемът не може да бъде решен чрез добавяне на трета клауза в списъка за разбиране, като например:

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 стойности и нищо друго, така че е малко по-малко общо и по-малко гъвкаво.

person jpmarinier    schedule 22.02.2020
comment
Благодаря ви много, точно от това имах нужда и ми помогна много. Би ли било възможно да получите списък с безкрайна дължина вместо зададена дължина? Тогава бих могъл просто да изтрия първите записи за всяко ново поле, така че играчът да не трябва ръчно да въвежда начална стойност с всяка нова игра, а по-скоро да въвежда такава при стартиране на програмата. - person Shini; 23.02.2020
comment
@Shini Но всъщност randomRs връща безкраен списък. Мисля, че всъщност е по-добре да върнете и крайното състояние на генератора от функцията, така че да можете да го използвате повторно за следващия си играч. Добавих код за това в края на моя отговор. Дано помогне. - person jpmarinier; 24.02.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) 
person MikaelF    schedule 23.02.2020
comment
(,,) <$> [0..n] <*> [0..n] = liftA2 (,,) [0..n] [0..n] може да е по-ясно. :) - person Will Ness; 24.02.2020