使用严格的功能语言时,您必然会写程序。我遇到了用OCaml生成大量伪随机数的问题,我不确定我是否正在使用最好的方法在这种语言上生成该数。
我所做的是创建一个带有函数( gen )的模块,该函数将整数作为 size 和一个空列表,并返回一个伪随机数列表大小大小。问题是当大小过大时,它会断言 StackOverflow ,这是预期的结果。
我应该使用尾递归吗?我应该使用我不知道的更好的方法吗?
module RNG =
struct
(* Append a number n in the end of the list l *)
let rec append l n =
match l with
| [] -> [n]
| h :: t -> h :: (append t n)
(* Generate a list l with size random numbers *)
let rec gen size l =
if size = 0 then
l
else
let n = Random.int 1000000 in
let list = append l n in
gen (size - 1) list
end
测试代码以生成十亿个伪随机数将返回:
# let l = RNG.gen 1000000000 [];;
Stack overflow during evaluation (looping recursion?).
答案 0 :(得分:2)
以相反的顺序生成列表,然后最后将其反向一次将是一个很大的改进。将连续的值添加到列表的末尾非常慢。可以在固定时间内添加到列表的最前面。
甚至更好,只是以相反的顺序生成列表并以这种方式返回它。您是否关心列表与生成值的顺序相同?
答案 1 :(得分:2)
为什么需要显式计算完整列表?另一个选择可能是使用新的序列模块延迟(确定性地)生成元素:
let rec random_seq state () =
let state' = Random.State.copy state in
Seq.Cons(Random.State.int state' 10, random_seq state')
然后,随机序列random_seq state
完全由初始状态state
决定:既可以无问题地重用,又可以根据需要生成新元素。
答案 2 :(得分:1)
标准的List
模块具有init
函数,可用于将所有这些都写在一行中:
let upperbound = 10
let rec gen size =
List.init size (fun _ -> Random.int upperbound)
答案 3 :(得分:1)
问题在于append函数不是尾部递归的。每次递归都会用一点堆栈空间来存储状态,并且随着列表的增加,append函数会占用越来越多的堆栈空间。从某种程度上讲,堆栈根本不够大,并且代码失败。
正如您在问题中建议的那样,避免这种情况的方法是使用尾递归。使用列表时,通常意味着以相反的顺序构造列表。然后,附加函数将变成简单的::
。
如果结果列表的顺序很重要,则需要在末尾反转列表。因此,看到代码返回List.rev acc
并不少见。这需要O(n)时间,但是空间恒定,并且是尾递归的。因此堆栈在那里没有限制。
因此您的代码将变为:
let rec gen size l =
if size = 0 then
List.rev l
else
let n = Random.int 1000000 in
let list = n :: l in
gen (size - 1) list
还有一些要优化的地方:
通过递归逐点构建结果时,结果通常是名称acc
,是累加器的缩写,并且首先传递:
let rec gen acc size =
if size = 0 then
List.rev acc
else
let n = Random.int 1000000 in
let list = n :: acc in
gen list (size - 1)
这将允许使用函数和模式匹配,而不是使用size参数和if构造:
let rec gen acc = function
| 0 -> List.rev acc
| size ->
let n = Random.int 1000000 in
let list = n :: acc in
gen list (size - 1)
一个随机数列表通常是一样好的。除非您希望使用不同大小的列表,但使用相同的种子以相同的数字序列开头,否则可以跳过List.rev。 n :: acc
是这样一种简单的构造,通常不会将其绑定到变量。
let rec gen acc = function
| 0 -> acc
| size ->
let n = Random.int 1000000 in
gen (n :: acc) (size - 1)
最后,您可以利用可选参数。虽然这样会使代码的读取更加复杂,但大大简化了使用:
let rec gen ?(acc=[]) = function
| 0 -> acc
| size ->
let n = Random.int 1000000 in
gen ~acc:(n :: acc) (size - 1)
# gen 5;;
- : int list = [180439; 831641; 180182; 326685; 809344]
您不再需要指定空列表来生成随机数列表。
注意:另一种方法是使用包装函数:
let gen size =
let rec loop acc = function
| 0 -> acc
| size ->
let n = Random.int 1000000 in
loop (n :: acc) (size - 1)
in loop [] size