【问题标题】:Project Euler prob 10 using haskell使用haskell的项目欧拉prob 10
【发布时间】:2016-05-05 08:07:19
【问题描述】:

好的,所以我做了一个小修改,似乎对 haskell 产生了很大的影响。事情是这样的:

我为来自项目 euler 的 Prob 10 实施了埃拉托色尼筛法。事情是这样的:

primesBelowN :: Integer -> [Integer]
primesBelowN n = 2:3:filter f [6*k+i | k <- [1..(n-1)`div`6], i <- [-1, 1]]
                 where f x = foldr g True [2..truncate(sqrt(fromInteger x))]
                             where g t ac = (x `rem` t /= 0) && ac

main = print $ sum $ primesBelowN 2000000

使用 ghc 编译,运行时间为 8.95 秒:

$ ghc -O3 010SummationOfPrimes.hs
$ time 010SummationOfPrimes
142913828922
8.739u 0.122s 0:08.95 98.8% 0+0k 2384+0io 1pf+0w

我认为我可以利用 g 函数中的 haskell 惰性求值来优化代码。可以通过简单地将ac 作为&amp;&amp; 的第一个参数来完成(或者我认为),这样它就不会计算ac == False 的不等式:

primesBelowN :: Integer -> [Integer]
primesBelowN n = 2:3:filter f [6*k+i | k <- [1..(n-1)`div`6], i <- [-1, 1]]
                 where f x = foldr g True [2..truncate(sqrt(fromInteger x))]
                             where g t ac = ac && (x `rem` t /= 0)

main = print $ sum $ primesBelowN 2000000

令人惊讶的是,它使程序 慢了 4 倍!。现在的运行时间明显更大,为 30.94 秒:

$ ghc -O3 010SummationOfPrimes.hs
$ time 010SummationOfPrimes
142913828922
30.765u 0.157s 0:30.94 99.9%    0+0k 2384+0io 1pf+0w

我不知道出了什么问题...任何提示/建议/答案?提前致谢!

编辑

所以,当我翻倒另一个东西时,我正在玩这个。如果我像这样调整我的函数,看起来很容易陷入无限循环:

primesBelowN :: Integer -> [Integer]
primesBelowN n = 2:3:filter f [6*k+i | k <- [1..(n-1)`div`6], i <- [-1, 1]]
                 where f x = foldr g True [m | m <- [2..], 
                                               m <= truncate(sqrt(fromInteger x))]
                             where g t ac = (x `rem` t /= 0) && ac

main = print $ sum $ primesBelowN 2000000

在这种情况下,进程内存一直爆炸到巨大的数字(80Gig,在我杀死它之前),没有任何输出:

$ ghc -O3 010SummationOfPrimes.hs
$ time 010SummationOfPrimes
^C20.401u 7.994s 0:28.42 99.8%  0+0k 2384+0io 1pf+0w

任何想法现在出了什么问题?

【问题讨论】:

  • 这是因为你在右折叠,但 &amp;&amp; 的左参数很严格
  • @BenjaminHodgson 愿意解释一下吗?
  • @BenjaminHodgson 感谢您的回复,但我非常感谢您的详细说明。 Haskell新手在这里。 :)
  • 你的候选人不包含任何2和3的倍数;你为什么要将它们除以 2、3、4、6、8、9、10、12、14、15 等? --- 您可以将primesBelowN 切换为:: Int -&gt; [Int],并在sum 之前使用map fromIntegral。 --- 另一件事是,primesBelowN 101 给出了不完整的结果。为此,最好使用takeWhile (&lt;= n) ...
  • @WillNess 感谢您的评论。我的最终程序解决了这个问题:我没有除以任何偶数或复合数。我在 xs 中递归地调用了我自己的 primesBelowN 列表并完成了它。工作速度提高了 8 倍!另外,关于 primesBelowN 101 部分,它不显示 101,因为 truncate . sqrt 只返回 10。只要它解决了问题!

标签: haskell optimization primes lazy-evaluation ghc


【解决方案1】:

对于问题的第一部分,请注意foldr 的运作方式:

foldr g x0 [x1, x2, x3, .., xn] = g x1 (g x2 (g x3 (..(..(g xn x0)))..)

在我们的例子中,

foldr g True [2..truncate(sqrt(fromInteger x))]
      where g t ac = (x `rem` t /= 0) && ac

相当于:

foldr g True (map h [2..truncate(sqrt(fromInteger x))])
      where g t ac = t && ac
            h t = x `rem` t /= 0

相当于:

foldr (&&) True (map h [2..truncate(sqrt(fromInteger x))])
      where h t = x `rem` t /= 0

这又相当于:

(h x1) && ((h x2) && ((h x3) &&(....((h xn) && True)))..)

请记住,我们正在处理一种惰性语言,其中评估由模式匹配引导。 &amp;&amp; 仅在其第一个参数中是严格的,这意味着除非必要,否则不必生成第二个参数表达式,即使那样它也只需要继续直到 (h x2) 处于最外层。 因此,更/合适/的表示是这样的(部分伪代码):

(h x1) && {instructions to generate (h x2) && ((h x3) && (....((h xn) && True)))..)}

因此,计算完整 && 表达式的内存需求大部分是恒定的。我们只需要保留(h x1) 和生成下一个值的指令。如果(h x1)False 我们停止,否则我们丢弃它并生成另一对值和指令。这显然不是 haskell 的实际实现方式,但作为一个模型就足够了。

现在,如果您切换参数顺序,&amp;&amp; 将必须首先计算第二个参数的表达式,其中&amp;&amp; 又必须完全计算下一个子表达式,依此类推,需要所有中间值留在堆栈中,直到整个表达式完全展开并根据需要进行评估。另请注意,余数检查将以相反的顺序进行,这使得该过程更加低效,因为与较小的素数相比,合数不太可能是较大素数的倍数。因此,总运行时间和内存要求更差。

关于问题的第二部分(已编辑),问题在于您不再使用有限列表。对列表理解的限制用作过滤而不是限制。为了使foldr 使用&amp;&amp; 完成,您将需要提供一个空列表(这在无限列表的定义中是不可能的)或对谓词返回False 的列表的单个元素进行模式匹配。不幸的是,在某些情况下(x 是素数),谓词不会返回 False,并且 foldr 将继续尝试与另一个元素进行模式匹配。所有这一切都是徒劳的,因为由于后卫的缘故,在一个点之后不会产生更多的元素。它失败的原因与失败的原因几乎相同:

head $ filter (const False) [1..]

【讨论】:

  • 这不仅仅是关于“需要大量中间值才能保留在堆栈中”。这也意味着以相反的降序执行试验除法,这在算法上更糟糕,因为给定的数字更有可能具有较小的素因数,因此按升序尝试除法平均需要更少的测试,因为非- 正在测试的素数(即大多数)数字。 --- “有些情况(例如 x=5)” 你的意思是,素数。 :)
  • @WillNess 你说的很对。似乎我也只见树木不见森林。在谈论孤立的部分时发生。
  • 你所说的保持所有价值观也发挥了作用。
  • @Veritas 谢谢 - 这是一个非常简洁的解释!此外,对于后一部分,我让它与 takeWhile (&lt;= truncate(sqrt(fromInteger x))) [2..] 一起工作。我想这绕过了我面临的陷阱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-20
  • 1970-01-01
  • 2014-03-08
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多