Why is this recursive function in Haskell so slow?
我试图模仿筛子,用haskell寻找所有素数小于某个数的素数。我发现了其他的haskell程序,它使用的是快速筛选方法。但是,我编写的以下递归函数非常慢。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 |
筛子20大约需要2分钟。我写这个问题的时候,30筛还没有完成。
有人能解释一下为什么这个递归函数这么慢吗?感谢您提供的帮助。
- 作为Haskell中的Eratostennes筛的最终权威,您应该看看Melissa O'Neill在《函数编程杂志》(2009)上的文章(lambda the Ultimate.org/node/3127)。里面应该还有一些技巧。
sieve'函数的第二个子句是进行两次递归调用(sieve' n k),从而使算法在指数时间内执行。
要解决此问题,可以将术语绑定到某个名称,从而确保对其进行一次评估:
1 2 3 4
| 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"揭示了一些有趣的讨论,其中一个引用了Simon Peyton Jones 1987年出版的一本书中的一个很好的例子,书名为"函数式编程语言的实现"(哦,天哪,这本书几乎和我一样古老)。
- 谢谢,这正是问题所在。现在跑得快多了。
- 很奇怪,编译器错过了这种优化。
- @乔恩,我更新了答案来解决这个问题。