OCaml中的随机数生成

时间:2019-01-14 20:16:55

标签: random ocaml

使用严格的功能语言时,您必然会写程序。我遇到了用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?).

4 个答案:

答案 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