【问题标题】:Understanding Haskell code which applies the `tails` function to an infinite list within a list comprehension了解将“tails”函数应用于列表理解中的无限列表的 Haskell 代码
【发布时间】:2014-11-28 03:22:18
【问题描述】:

在今天早些时候将我的解决方案提交给 Project Euler 的problem 50 后,我正在浏览问题的论坛,查看其他人的解决方案/执行时间。

一段时间后,我开始为自己的代码感到非常自豪,该代码在大约 3 秒内解决了问题(我的代码使用了 Primes 库并使用 O2 编译)...

...然后我遇到了下面的代码,它在大约 0.05 秒内解决了它...在解释模式下(即ghci)。

有人能解释一下下面的代码如何/为什么解决这个特定问题吗?

令人费解的部分是将tails 函数应用于列表理解中的无限素数列表(primes)。我很难理解我们如何保证查看所有可能的连续素数子列表,而不仅仅是tails 生成的子列表。

(我通常在ghci 中尝试代码片段的策略在这种情况下不起作用,因为primes 是无限的......)

问题:我们被要求找到 1,000,000 以下的最大素数,这是连续素数相加的结果。例如,连续素数之和 100 以下的最大素数是 41(2 + 3 + 5 + 7 + 11 + 13)。

import Data.List (tails)
import Data.Numbers.Primes

under n xs = takeWhile (< n) xs

takeUntil p xs = foldr (\x r-> if p x then [x] else x:r) [] xs

res :: [((Int, Int), (Int, Int))] 
-- ((top_length, sums_to), (total_length, starting_prime))

res = [(r,(length s,x)) | (x:xs) <- tails primes
                        , let s = zip [1..]
                                $ under 100
                                $ scanl (+) x xs
                        , let r = ...] 

main = mapM_ print $ takeUntil ...

【问题讨论】:

  • “用O2编译的obv”是什么意思? OBV 是某种特殊的编译工具吗?
  • obv = 显然 :P(把它拿出来),只是想强调我自己的代码相对于上面发布的代码执行得有多糟糕。
  • 也许你应该发布你的代码,我们可以告诉你为什么它运行这么慢。
  • 该条目确实指出“通过试除法检测素数”和“通过 Eratosthenes 的筛子找到素数”是“都从编译模块加载的”。
  • btw the problem 要求 “最连续素数的总和?” 即最长的,而不是最大的。

标签: haskell list-comprehension primes infinite


【解决方案1】:

一般来说,取tailsprimes 是没有问题的,因为Haskell 懒惰(评估是按需)。

这里的问题更小,因为每个子列表都用under 100 = takeWhile (&lt; 100) 修剪——只采用有限前缀。

(x:xs) &lt;- tails primes 只是遍历 primes 的所有后缀 - 即 primesx=2 开始;然后 素数x=3 开始,然后是 5,7,11, ...。该模式只需要素数列表的头部元素x,尾部元素xs,甚至它们的值也不会立即请求,只有的所谓“spine” >primes 列表是强制的,一次 1 个等级(当然,确保 primes 以至少一个元素开头 x 将自动计算 x 的实际值,但这是另一回事)。

所以(x:xs) &lt;- tails primes 对素数列表的连续后缀进行操作。试试tails [1..10] 看看那里发生了什么。


当你输入时

GHCi> primes

在 GHCi 提示符下,您实际上是在请求 allprimes 列表的元素作为输出打印。但是

GHCi> under 100 primes

只会请求低于 100 的元素,之后不会超过一个元素。它使用内置的takeWhile 一个接一个地检查primes 中的元素,直到找到一个不符合谓词的元素(在这种情况下,大于100)。结果中不包含终止元素。

用户定义的takeUntiltakeUntil 的不同之处仅在于它在其结果中还包括终止元素(并且谓词的含义被翻转 - 它表示何时停止)。

scanl (+) 是计算序列部分和的序列的常用方法:

Prelude> scanl (+) 1 [2..10]
[1,3,6,10,15,21,28,36,45,55]

代码

 [ (r,(length s,x)) | (x:xs) <- tails primes
                    , let s = zip [1..]
                            $ under 100
                            $ scanl (+) x xs
                    , let r = last $ filter (isPrime.snd) s] 

意思是:

for each suffix of primes        -- [[2,3,5,7...],[3,5,7,11...],...]
  let 
    x  = head suffix             -- for all primes from x
    xs = tail suffix             --   i.e. xs
    t1 = scanl (+) x xs          -- calculate partial sums
    t2 = under 100 t1            -- stopping when the sum reaches 100
    s  = zip [1..] t2            -- index each sum by the length of subsequence,
    t3 = filter (isPrime.snd) s  -- keep only such that summed to a prime,
    r  = last t3                 -- and take the one created from the
  in                             --    longest subsequence, starting from x
    emit (r,(length s,x))        -- and collect the data

所以我们得到每个连续素数的条目列表((top_length, sums_to), (total_length, starting_prime))starting_prime = 2,3,5,7,11, ...

main 中的 takeUntil 表达式决定了何时可以停止,因为不再有可能改善结果。

【讨论】:

  • 我很惭愧地说,我花了很长时间才真正理解这段代码是如何工作的。具有讽刺意味的是,当我今天早些时候处理另一个欧拉问题时,你对 inits/tails/scanl 的解释为我“点击”了(我最终在无限列表上使用了类似的函数模式)。然后当我回到这段代码时,它突然变得一清二楚!我不是第一次有这样的 Haskell 体验 :) 再次感谢您的解释。 (我当然希望在问题论坛中阅读更多您的解决方案!)
【解决方案2】:

要解决无限列表问题,只需定义:

primesUpTo100 = takeWhile (< 100) primes

并在res 的定义中使用primesUpTo100 代替primes

接下来,更改res 的定义以返回不同的值,这样您就可以看到发生了什么,例如:

res = do
  (x:xs) <- tails primesUpTo100
  let s = zip [1..] $ under 100 $ scanl (+) x xs
  let r = last  $ filter (isPrime.snd) s
  return (x, take 10 xs, s)

现在您可以看到s 是什么以及它与x 以及xs 的前10 个元素的关系。

如果列表res 太长,只需评估take 10 res。另一个技巧是定义一个辅助函数:

import Control.Monad
pp xs = forM_ xs print

这将打印出一个列表,其中每个元素都在一个新行上,这应该使事情更容易阅读。例如:

pp (take 10 res)

使用所有这些想法,我敢打赌,您可以弄清楚。这实际上是对最长序列的蛮力搜索,但使用scanl 可能会避免重新计算总和。

【讨论】:

    猜你喜欢
    • 2015-01-10
    • 1970-01-01
    • 1970-01-01
    • 2017-02-10
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多