我试图模仿Sieve使用Haskell找到所有素数少于一些数。我发现其他Haskell程序以极快的速度使用Sieve方法。但是我写的下面的递归函数非常慢。代码如下
sieve' :: Integer -> Integer -> [Integer]
sieve' n 1 = [2 .. n]
sieve' n (k + 1) | [x | x <- sieve' n k, x == k + 1] == [] = sieve' n k
|otherwise = [x | x <- sieve' n k, x == k + 1 || not (mod x (k + 1) == 0)]
sieve :: Integer -> [Integer]
sieve n = sieve' n n
Sieve 20大约需要2分钟。在我写这个问题的时候,Sieve 30还没有完成。
任何人都可以解释为什么这个递归函数太慢了。感谢您的任何帮助,您可以提供。
答案 0 :(得分:15)
sieve'
函数的第二个子句正在进行两次递归调用(sieve' n k
),从而使您的算法在指数时间内执行。
要解决此问题,您可以将该术语绑定到某个名称,从而确保对其进行一次评估:
sieve' n (k + 1) | [x | x <- rec, x == k + 1] == [] = rec
|otherwise = [x | x <- rec, x == k + 1 || not (mod x (k + 1) == 0)]
where
rec = sieve' n k
更新以回应评论,询问编译器为何不自动执行此操作:
这种称为CSE(公共子表达式消除)的转换一般不是优化,而是时间和空间使用之间的权衡,因此最好留给程序员做出决定。
谷歌搜索“CSE”揭示了一些有趣的讨论,其中一个引用了1987年Simon Peyton Jones所着的this very good example,称为“函数式编程语言的实现”(哦,我的,这本书几乎和我一样久是)