【问题标题】:About strictness in haskell关于 haskell 的严格性
【发布时间】:2014-05-23 17:37:20
【问题描述】:

我创建了以下 Haskell 素数函数(在 ghci 中):

let pi :: Int -> Int -> Int;
pi 1 _ = 2;
pi x y = if all (/=0) (map (rem y) [pi z 2| z <- [1..(x-1)]]) then y else pi x (y+1);

请不要介意第二个/记忆的参数(它应该总是从 2 开始)。 当然,正如预期的那样,thunk 很快就会失控。确认 43 是第 14 个素数需要超过 19 秒...

Prelude> pi 10 2
29
(0.14 secs, 23924220 bytes)
Prelude> pi 11 2
31
(0.48 secs, 71394644 bytes)
Prelude> pi 12 2
37
(1.64 secs, 244218772 bytes)
Prelude> pi 13 2
41
(5.57 secs, 832500204 bytes)
Prelude> pi 14 2
43
(19.11 secs, 2841677968 bytes)

我已经阅读了严格性(主要是seq,$!),但我所有的尝试都花费了更长的时间!

【问题讨论】:

  • 请不要使用 ghci 对任何东西进行基准测试——这毫无意义。使用ghc -O2 并考虑-fllvm
  • 由于您的列表理解,pi n 的每次调用都会调用pi (n-1) 次。这种极端的爆炸可以通过 memoization 来控制 - 只需在 SO 中搜索 memoize 并在 Haskell 中搜索许多合适的答案。
  • 要计算第 14 个素数,您需要计算第 1 到第 13 个素数。要计算第 13 个素数,您需要计算第 1 到第 12 个素数,等等……

标签: haskell thunk strictness


【解决方案1】:

如果您将trace 添加到您的函数以查看对pi 的哪些调用被评估,您会发现您的实现没有使用已经计算的值。

import Prelude hiding (pi)
import Debug.Trace (trace)

pi :: Int -> Int -> Int
pi 1 y = trace ("pi 1 " ++ show y) 2
pi x y =
    trace ("pi " ++ show x ++ " " ++ show y) $
    if all (/=0) (map (rem y) [pi z 2| z <- [1..(x-1)]])
        then y
        else pi x (y+1)

现在评估 pi 3 2 会打印以下内容(我添加了空格,以显示某种结构):

pi 3 2
    pi 1 2
pi 3 3
    pi 1 2
    pi 2 2
        pi 1 2
    pi 2 3
        pi 1 2
pi 3 4
    pi 1 2
pi 3 5
    pi 1 2
    pi 2 2
        pi 1 2
    pi 2 3
        pi 1 2

您会看到很多冗余,并且对于较高的 x 值,这种情况会呈指数级恶化。懒惰在这里不是你的问题,而是你没有传播你到目前为止计算的值。换句话说,问题出在你的方法上,现在很难解决。

【讨论】:

  • 顺便说一句,htrace 包提供了分层跟踪,因此您下次不必手动添加空格
  • 谢谢,@luqui,我之前没听说过那个包 :)
【解决方案2】:
pi = (!!) primes . subtract 1

primes = 2 : filter isPrime [3..]

isPrime n = all ((/=) 0 . rem n)) $ takeWhile ((>=) n . (^2)) primes

> pi 14
43
it :: Integer
(0.00 secs, 0 bytes)

【讨论】:

    猜你喜欢
    • 2014-02-12
    • 2014-10-06
    • 1970-01-01
    • 2011-07-30
    • 2012-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多