我有一个函数,它接受一系列随机数/浮点数,并使用它们来生成一个值/结构(即,获取一个球的随机速度和位置,并输出它的坐标)会落地)。而且我需要连续产生几千个。
我实现一切的方式是每次计算都采用stdGen,用它来生成几个数字,并传递一个新的stdGen以允许它被链接到另一个。
要为10000个项目执行此操作,我从generate_item n
创建一个列表,它基本上输出一个(value,gen)
元组(值是我正在尝试计算的值),其中值gen
的{{1}}是从generate_item n-1
获取值所涉及的计算中递归输出的stdGen
然而,这个程序似乎在大约一千个结果左右的时候爬行变得不切实际。而且似乎绝对不具备可扩展性。这可能与我将所有generate_item
结果存储在内存中的事实有关吗?
或者是否有一种更加自觉的方式在Haskell中使用Monads或者上面描述的内容来解决这个问题?
请注意,即使在像ruby和python这样的高级脚本语言中,从随机值生成算法的代码也会在几秒内生成10k;这些计算几乎不是密集的。
代码
-- helper functions that take in StdGen and return (Result,new StdGen)
plum_radius :: StdGen -> (Float,StdGen)
unitpoint :: Float -> StdGen -> ((Float,Float,Float),StdGen)
plum_speed :: Float -> StdGen -> (Float,StdGen)
-- The overall calculation of the value
plum_point :: StdGen -> (((Float,Float,Float),(Float,Float,Float)),StdGen)
plum_point gen = (((px,py,pz),(vx,vy,vz)),gen_out)
where
(r, gen2) = plum_radius gen
((px,py,pz),gen3) = unitpoint r gen2
(s, gen4) = plum_speed r gen3
((vx,vy,vz),gen5) = unitpoint s gen4
gen_out = gen5
-- Turning it into some kind of list
plum_data_list :: StdGen -> Int -> (((Float,Float,Float),(Float,Float,Float)),StdGen)
plum_data_list seed_gen 0 = plum_point seed_gen
plum_data_list seed_gen i = plum_point gen2
where
(_,gen2) = plum_data_list seed_gen (i-1)
-- Getting 100 results
main = do
gen <- getStdGen
let data_list = map (plum_data_list gen) [1..100]
putStrLn List.intercalate " " (map show data_list)
答案 0 :(得分:6)
考虑使用mersenne-twister和vector-random软件包,该软件包经过专门优化,可生成大量高质量的随机数据。
列表不适合分配大量数据 - 更好地使用打包表示 - 除非您正在流式传输。
答案 1 :(得分:5)
首先,您正在描述的模式 - 采用StdGen
然后返回一个带有值的元组,另一个StdGen
将被链接到下一个计算中 - State
monad编码的模式。重构代码以使用它可能是开始熟悉monadic模式的好方法。
至于您的性能问题,StdGen
的速度非常慢。我没有做过很多这方面的事情,但我听说mersenne twister更快。
但是,您可能还想发布您的代码,因为在您生成大型列表的情况下,根据您的操作方式,懒惰可能对您有利或不利。但是如果不了解你在做什么就很难给出具体的建议。一个经验法则,以防你来自另一个函数语言,如Lisp - 生成列表(或其他惰性数据结构 - 例如树,但不是Int
),避免< / strong>尾递归。它更快的直觉不会转移到懒惰的语言。例如。使用(没有我在练习中实际使用的monadic风格的书写)
randoms :: Int -> StdGen -> (StdGen, [Int])
randoms 0 g = (g, [])
randoms n g = let (g', x) = next g
(g'', xs) = randoms (n-1) g'
in (g'', x : xs)
这将允许结果列表“流式传输”,因此您可以在生成后续部分之前访问它的早期部分。 (在这种情况下,它有点微妙,因为访问生成的StdGen
将必须生成整个列表,所以你必须小心避免这样做,直到你消耗了列表 - 我希望有一个支持良好split
操作的快速随机生成器,然后你可以完全返回一个生成器。
哦,以防你在使用monad事物时遇到问题,这是上面用状态monad编写的函数:
randomsM :: Int -> State StdGen [Int]
randomsM 0 = return []
randomsM n = do
x <- state next
xs <- randomsM (n-1)
return (x : xs)
看信件?
答案 2 :(得分:4)
其他海报有好点,StdGen
表现不佳,你应该尝试使用State而不是手动传递发生器。但我认为最大的问题是你的plum_data_list
功能。
它似乎是某种查找,但由于它是在没有任何记忆的情况下递归实现的,因此您所做的调用必须递归到基本情况。也就是说,plum_data_list seed_gen 100
需要来自plum_data_list seed_gen 99
的随机生成器,依此类推,直到plum_data_list seed_gen 0
。当您尝试生成这些值的列表时,这将为您提供二次性能。
可能更惯用的方法是让plum_data_list seed_gen
生成无限的点列表,如下所示:
plum_data_list :: StdGen -> [((Float,Float,Float),(Float,Float,Float))]
plum_data_list seed_gen = first_point : plum_data_list seed_gen'
where
(first_point, seed_gen') = plum_point seed_gen
然后,您只需将main
中的代码修改为类似take 100 $ plum_data_list gen
的内容,即可恢复线性性能。