我正在学习OCaml(原谅我的风格)并且我正在尝试编写一个函数来生成一个素数列表,直到某个上限。我已经设法以几种不同的方式做到这一点,所有这些都有效,直到你将它们扩展到相对较高的上限。
如何更改这些(其中任何一个)以便递归不会填满堆栈?我以为我的while循环版本会实现这一点,但显然不是!
let primes max =
let isPrime p x =
let hasDivisor a = (x mod a = 0) in
not (List.exists hasDivisor p) in
let rec generate p test =
if test < max then
let nextTest = test + 2 in
if isPrime p test then generate (test :: p) nextTest
else generate p nextTest
else p in
generate [5; 3; 2] 7;;
这是我最成功的解决方案,因为它在运行primes 2000000;;
时不会立即溢出堆栈。然而它只是在那里消耗CPU;我只能假设它最终会完成!以下备选方案都存在堆栈溢出问题:
let primes max =
let rec sieve found toTest =
let h = List.hd toTest
and t = List.tl toTest in
let newPrimes = h :: found
and doesntDivide x = (x mod h <> 0) in
let nonDivisors = List.filter doesntDivide t in
if nonDivisors = [] then newPrimes
else sieve newPrimes nonDivisors in
let rec range a b =
if a > b then []
else a :: range (a + 1) b in
let p = range 2 max in
sieve [] p;;
let primes max =
let rec sieve toTest =
let h = List.hd toTest
and t = List.tl toTest in
let doesntDivide x = (x mod h <> 0) in
let nonDivisors = List.filter doesntDivide t in
if nonDivisors = [] then [h]
else (h :: sieve nonDivisors) in
let rec range a b =
if a > b then []
else a :: range (a + 1) b in
let p = range 2 max in
sieve p;;
let primes max =
let rec range a b =
if a > b then []
else a :: range (a + 1) b in
let tail = ref (range 2 max)
and p = ref [] in
while !tail <> [] do
let h = List.hd !tail
and t = List.tl !tail in
let doesntDivide x = (x mod h <> 0) in
let newTail = ref (List.filter doesntDivide t) in
tail := !newTail;
p := h :: !p
done;
!p;;
答案 0 :(得分:9)
发生堆栈溢出是因为您的range函数不是尾递归的。一个有效的是,例如
let rec range store a b =
if a > b then store
else range (a :: store) (a + 1) b
in
let p = List.rev (range [] 2 max) in
使用该定义和格式行,给出
$ ocamlopt -o primes2 primes2.ml
$ ./primes2
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
...
既然你正在学习,我也会给你一些不请自来的风格评论:)
不要使用hd和tl。喜欢模式匹配。然后编译器可以告诉你错过的案例。 E.g。
让rec筛子找到测试= 让h = List.hd toTest &t = List.tl toTest in将是
let rec sieve found = function
| h :: t -> ...
| [] -> Error handling...
不要使用x = []。使用模式修补。
匹配x | [] - &gt; ... | h :: t - &gt; ...
使用匿名函数而不是短(即&lt; = 1行)命名的单用函数:
让dontDivide x =(x mod h&lt;&gt; 0)in 让nonDivisors = List.filter不在
中让nonDivisors = List.filter(fun x - &gt;(x mod h&lt;&gt; 0))t in
谨慎使用命令功能。
答案 1 :(得分:4)
你声称的算法是Eratosthenes的Sieve实际上并非如此;他们使用试验分割代替筛分,这很容易通过寻找余数(mod运算符)与零的比较来发现。这是一个简单的Eratosthenes Sieve实现,以伪代码而不是Ocaml,因为我写了Ocaml代码已经有很长时间了:
function primes(n)
sieve := makeArray(2..n, True)
for p from 2 to n
if sieve[p]
output p
for i from p*p to n step p
sieve[i] := False
这可以进一步优化,但是对于像n = 2000000这样的小限制,这样做几乎没有意义;在任何情况下,筛子将比您正在使用的试验部门快得多。如果您对使用素数进行编程感兴趣,我会在我的博客上谦虚地推荐这个essay。