今天早些时候向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 ...
答案 0 :(得分:1)
要解决无限列表问题,只需定义:
primesUpTo100 = takeWhile (< 100) primes
并在primesUpTo100
的定义中使用res
代替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
这将打印出一个列表,其中新行上的每个元素都应该使事情更容易阅读。 E.g:
pp (take 10 res)
使用所有这些想法,我打赌你可以搞清楚。它实际上是对最长序列的强力搜索,但使用scanl
可能会避免重新计算总和。
答案 1 :(得分:1)
一般来说,tails
的{{1}}没有问题,因为Haskell lazy (评估是按需)。
这里特别是问题更少,因为每个子列表都被primes
修剪 - 只有一个有限的前缀。
under 100 = takeWhile (< 100)
只需查看(x:xs) <- tails primes
的所有后缀 - 即 primes ,从primes
开始;然后 primes 从x=2
开始,然后是x=3
。该模式只需要头元素5,7,11, ...
和素数列表的尾x
,甚至不会立即请求它们的值,只有所谓的“spine” primes 列表被强制执行,一次1个陷阱(当然确保xs
以至少一个元素primes
开头,将自动计算{{1}的实际值但是那是另一回事)。
因此x
对素数列表的连续后缀进行操作。试试x
,看看那里发生了什么。
键入
时(x:xs) <- tails primes
在GHCi提示符下,您实际上要求所有要打印的tails [1..10]
列表元素作为输出。但是
GHCi> primes
只会请求低于100的人,之后不会超过一个元素。它使用内置的primes
,一个接一个地检查GHCi> under 100 primes
中的元素,直到发现一个谓词失败(在这种情况下,大于100)。终止元素不包含在结果中。
用户定义的takeWhile
与此不同之处仅在于它在结果中还包含终止元素(并且谓词的含义被翻转 - 它指示何时停止)。
primes
是计算序列部分和的序列的常用方法:
takeUntil
代码
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, ...
表达式确定何时可以停止,因为不可能再改进结果。