作为一个学习Haskell的Java人,我开始习惯于思考所有事情的新方式,但我花了半天的时间尝试用简单的RNG实现某些东西并且无处可去。在Java中,我可以创建一个静态RNG并使用Classname.random.nextInt(10)调用它,它将满足以下条件:
到目前为止,在Haskell中,我面临着经典的程序员困境 - 我可以拥有2/3。我还在学习并且对Monads完全不了解,除了他们可能能够在这里帮助我。
我最近的尝试是这样的:
getRn :: (RandomGen g) => Int -> Int -> Rand g Int
getRn lo hi= getRandomR (lo,hi)
- 编辑:修剪我的问题,使其不再啰嗦,取而代之的是摘要,然后我最终做了什么:
在创建了一堆随机城市(针对TSP)后,我使用函数createEdges
对其进行了操作,该函数占用了一个城市并将其连接到其他城市:M.mapWithKey (\x y -> (x,(createEdges y [1..3] makeCountry)))
问题:
我想用随机的东西替换[1..3]。即我想在纯代码上映射随机性(IO)。这对我来说并没有造成混乱(看到人们试图在下面回答我,以便很好地理解我的困惑)。事实上,我甚至不确定我是否正确解释了问题。
我遇到了这种类型的错误:Couldn't match expected type [Int] with actual type IO [Int]
解决方案:
因此,在发现我想要做的事情在功能环境中根本上是错误的之后,我决定改变我的方法。我没有生成城市列表然后应用随机性来连接它们,而是创建了一个[[Int]],其中每个内部列表代表随机边。从而在过程开始时创建我的随机性,而不是试图在纯代码上映射随机性。
(我把最后的结果作为我自己的答案发布了,但是我不会让我接受我自己的答案。一旦它完成,我已经达到了这个门槛我会回来并接受)
答案 0 :(得分:2)
如果您愿意,可以使用随机数而不需要任何monad或IO。 所有你必须知道的是,由于存在状态(随机数发生器的内部状态),你必须接受这个状态。
在我看来,最简单的框架是Sytem.Random。
使用此getRn
功能可能如下所示:
getRn :: (RandomGen g) => Int -> Int -> g -> (Int, g)
getRn lo hi g = randomR (lo,hi) g
在这里,您可以将g
视为我上面提到的州 - 你把它放进去,你得到另一个这样的回复(在ghci中):
> let init = mkStdGen 11
> let (myNr, nextGen) = getRn 1 6 init
> myNr
6
> let (myNr, nextGen') = getRn 1 6 nextGen
> myNr
4
我认为你可以从使用这个开始 - 将gen
线程绕过,然后当你得到所有monad的东西回来并使它更容易写/读时。
我不知道您的数据的定义,但这是一个使用此技术的简单示例:
module StackOQuestion where
import System.Random
getRn :: (RandomGen g) => Int -> Int -> g -> (Int, g)
getRn lo hi = randomR (lo,hi)
getRnList :: (RandomGen g) => (g -> (a, g)) -> Int -> g -> ([a], g)
getRnList f n g
| n <= 0 = ([], g)
| otherwise = let (ls, g') = getRnList f (n-1) g
(a, g'') = f g'
in (a:ls, g'')
type City = (Int, Int)
randomCity :: (RandomGen g) => g -> (City, g)
randomCity g =
let (f, g') = getRn 1 6 g
(s, g'') = getRn 1 6 g'
in ((f, s), g'')
randomCities :: (RandomGen g) => (Int, Int) -> g -> ([City], g)
randomCities (minC, maxC) g =
let (count, g') = getRn minC maxC g
in getRnList randomCity count g'
你可以这样测试:
> let init = mkStdGen 23
> randomCities (2,6) init
([(4,3),(1,2)],394128088 652912057)
正如您所看到的,这会创建两个 Cities (此处仅表示为整数对) - 对于init
的其他值,您将获得其他答案。
如果你在这看起来正确,你可以看到 state-monad 已经开始了(g -> ('a, g)
部分);)
PS:mkStdGen
有点像你从Java和co中知道的随机初始化(你通常把系统时钟的滴答计数放入的部分) - 我选择11因为它很快输入;) - 当然,如果你坚持使用11,你将永远得到相同的数字 - 所以你需要用IO中的东西来初始化它 - 但你可以把这个包推到main
并保持纯净,否则如果你只是通过然后g
左右
答案 1 :(得分:2)
编辑:经过更多阅读和朋友的额外帮助后,我终于将其缩减为此解决方案。但是我会在答案中保留原来的解决方案,以防同样的方法帮助像我这样的新手(这也是我学习过程的重要部分)。
-- Use a unique random generator (replace <$> newStdGen with mkStdGen 123 for testing)
generateTemplate = createCitiesWeighted <$> newStdGen
-- create random edges (with weight as pair) by taking a random sized sample of randoms
multiTakePair :: [Int] -> [Int] -> [Int] -> [[(Int,Int)]]
multiTakePair ws (l:ls) is = (zip chunka chunkb) : multiTakePair remaindera ls remainderb
where
(chunkb,remainderb) = splitAt l is
(chunka,remaindera) = splitAt l ws
-- pure version of utilizing multitake by passing around an RNG using "split"
createCitiesWeighted :: StdGen -> [[(Int,Int)]]
createCitiesWeighted gen = take count result
where
(count,g1) = randomR (15,20) gen
(g2,g3) = split g1
cs = randomRs (0, count - 2) g1
es = randomRs (3,7) g2
ws = randomRs (1,10) g3
result = multiTakePair ws es cs
原始解决方案-----
除了@ user2407038的深刻见解外,我的解决方案非常依赖于我从这两个问题中读到的内容:
(注意:我遇到了一个问题,我无法弄清楚如何随机化每个城市会有多少边缘,@ AnrewC提供了一个非常好的响应,不仅回答了这个问题,而且还大量减少了多余的代码)
module TspRandom (
generateCityTemplate
) where
import Control.Monad (liftM, liftM2) -- promote a pure function to a monad
-- @AndrewC's suggestion
multiTake :: [Int] -> [Int] -> [[Int]]
multiTake (l:ls) is = chunk : multiTake ls remainder
where (chunk,remainder) = splitAt l is
-- Create a list [[Int]] where each inner int is of a random size (3-7)
-- The values inside each inner list max out at 19 (total - 1)
createCities = liftM (take 20) $ liftM2 multiTake (getRandomRs (3,7)) (getRandomRs (0, 19))
-- Run the generator
generateCityTemplate = do
putStrLn "Calculating # Cities"
x <- createCities
print x
return ()
答案 2 :(得分:2)
我想说如果你想使用随机数,最简单的方法就是使用像Control.Monad.Random
这样的实用程序库。
更具教育性,工作密集度的途径是学会编写自己的monad。首先,您想了解State
monad,并对此感到满意。我认为学习this older question(免责声明:我在那里有答案)可能是研究这个问题的一个很好的起点。我要采取的下一步是能够自己编写State
monad。
在那之后,我将尝试的下一个练习是为随机数生成写一个“实用程序”monad。通过“实用程序”monad我的意思是一个monad,它基本上使用API重新打包标准State
monad,这使得该特定任务更容易。这就是Control.Monad.Random
包的实现方式:
-- | A monad transformer which adds a random number generator to an
-- existing monad.
newtype RandT g m a = RandT (StateT g m a)
他们的RandT
monad实际上只是一个newtype
定义,重用了StateT
并添加了一些实用函数,以便您可以专注于使用随机数而不是而不是国家monad本身。因此,对于本练习,您基本上使用您想要的API设计随机数生成monad,然后使用State
和Random
库来实现它。
答案 3 :(得分:0)
状态monad实际上非常简单。它只是从状态到值和新状态的函数,或者:
data State s a = State {getState :: s -> (s, a)}
事实上,这正是Rand
monad所在。没有必要了解State
使用Rand
的机制。您不应该评估Rand
内的IO
,只需使用您用于do
的相同IO
表示法直接使用它。 do
符号适用于任何 monad。
createCities :: Rand StdGen Int
createCities = getRn minCities maxCities
x :: Cities -> X
x = ...
func :: Rand StdGen X
func = do
cities <- createCities
return (x cities)
-- also valid
func = cities <$> createCities
func = createCities >>= return . x
你不能像写下来一样写getConnections
。您必须执行以下操作:
getConnections :: City -> Country -> Rand StdGen [Int]
getConnections c country = do
edgeCount <- createEdgeCount
fromIndecies [] edgeCount (citiesExcludeSelf c country)
任何调用getConnections
的函数都必须返回Rand StdGen x
类型的值。只有在编写完整个算法并希望运行它之后,才能摆脱它。
然后,您可以使用evalRandIO func
运行结果,或者,如果您想测试某些算法,并且希望在每次测试时为其提供相同的输入,则可以使用evalRand func (mkStdGen 12345)
,其中12345或任何其他数字,是您的种子价值。