【问题标题】:Primes in HaskellHaskell 中的素数
【发布时间】:2019-09-30 19:38:53
【问题描述】:

我正在学习 Haskell,我试图生成一个无限的素数列表,但我无法理解我的函数做错了什么。

功能:

prime = 2:3:filter (\x -> all (\y -> (mod x y) > 0) (init prime)) [5..]

我认为是init prime,但奇怪的是,即使我设置了范围的上限(例如5..10),函数也会永远循环,并且永远不会得到prime !! 2 的任何结果

你能告诉我我做错了什么吗?

【问题讨论】:

  • 您正在尝试过滤列表以仅保留那些不能被init prime 的任何元素整除的元素。为了检查init prime 的每个元素,Haskell 需要找出prime 的所有元素(“最后一个”除外)。现在你能看出问题了吗?当您需要完整的数据结构来计算中间结果时,惰性求值对您没有帮助。
  • 您可以通过将(init prime) 替换为(takeWhile (\z -> z * z <= x) prime) 来挽救您的想法,因为每个非质数的质因数都不大于其平方根。
  • 您对init primes 的调用表明您认为每次迭代只会看到到目前为止构建的列表。如果这是您的信念,那么它是错误的,primes 列表根本没有完全计算。避免使用您正在生成的列表部分,如 ShreevatsaR 的评论中所做的那样。
  • 是的,这当然可行,谢谢,我只是想了解是否可以通过使用列表本身的一部分来递归地扩展无限列表来计算下一个元素。
  • 绝对是;这就是@ShreevatsaR 的评论所建议的。您的不起作用的唯一原因是使用的无限列表部分(init)与正在生成的列表部分重叠。如果您通过使用适当的takeWhile 来防止重叠,则没有问题。

标签: haskell primes lazy-evaluation


【解决方案1】:

好吧,让我们看看init 对有限列表做了什么:

init [1] == []
init [1,2] == [1]
init [1,2,3] == [1,2]

好的,所以它给了我们除了列表的最后一个元素之外的所有元素。

那么init primes 是什么?好吧,prime 没有最后一个元素。希望如果我们正确实现了prime,它不应该最后一个元素(因为有无限多的素数!),但更重要的是我们还不需要关心,因为我们不需要无论如何现在都有完整的列表 - 毕竟我们只关心前几个元素,所以对我们来说它与 prime 本身几乎相同。

现在,看看all:这是做什么的?好吧,它需要一个列表和一个谓词,并告诉我们列表中的所有元素是否满足谓词:

all (<5) [1..4] == True
all even [1..4] == False

它甚至适用于无限列表!

all (<5) [1..] == False

那么这里发生了什么?好吧,事情是这样的:它确实适用于无限列表......但前提是我们可以实际评估列表,直到列表中违反谓词的第一个元素!让我们看看这里是否成立:

all (\y -> (mod 5 y) > 0) (init prime)

所以要确定5 是否是素数,我们必须检查素数中是否有一个数字减去除以它的素数的最后一个元素。让我们看看我们是否可以做到这一点。

现在让我们看看素数的定义,我们得到

all (\y -> (mod 5 y) > 0) (2:3:filter (\x -> all (\y -> (mod x y) > 0) (init prime)) [5..])

所以要判断 5 是否是质数,我们只需要检查它是否是:

  1. 能被 2 整除 - 不是,让我们继续
  2. 能被 3 整除 - 仍然不能
  3. 能被...整除?嗯,我们正在检查第三个素数是什么,所以我们还不知道......

这就是问题的症结所在。按照这个逻辑,要确定第三个质数,您需要知道第三个质数!当然,从逻辑上讲,我们实际上根本 想要检查这个,而我们只需要检查是否有任何 较小的素数是当前候选的除数。

那么我们该如何做呢?好吧,不幸的是,我们将不得不改变我们的逻辑。我们可以做的一件事是尝试记住我们已经有多少质数,并且只取我们需要的数量进行比较:

prime = 2 : 3 : morePrimes 2 [5..]
  morePrimes n (x:xs)
    | all (\y -> mod x y > 0) (take n prime) = x : morePrimes (n+1) xs
    | otherwise                              = morePrimes n xs

那么这是如何工作的呢?嗯,它基本上完成了我们刚才所说的:我们记得我们已经有多少个素数(从2 开始,因为我们知道在n 中至少有[2,3]。然后我们检查我们的下一个素数是否可整除通过使用take n 已经知道的n 素数中的任何一个,如果它是我们知道它是我们的下一个素数,我们需要增加n - 否则我们就继续。

还有一种更广为人知的形式,其灵感来自(虽然不完全相同)埃拉托色尼筛子:

prime = sieve [2..] where
  sieve (p:xs) = p : sieve (filter (\x -> mod x p > 0) xs)

那么这是如何工作的呢?好吧,再次使用类似的想法:我们知道下一个素数必须不能被任何先前的素数整除。那么我们该怎么办?好吧,从2 开始,我们知道列表中的第一个元素是质数。然后,我们使用filter 丢弃所有可被该素数整除的数字。之后,列表中的下一项将再次成为质数(因为我们没有丢弃它),所以我们可以重复这个过程。

这些都不是你所希望的那种衬里。

【讨论】:

  • 两个反对意见:init 从未应用于[5..],因此对init [5..] 所做的解释似乎无关紧要;我们绝对已经确定prime是一个无限列表(事实上,它不是一个无限列表,所以我们当然不能确定它是)。
  • @DanielWagner Argh,在这两个方面都是正确的。我已经设法解决了这个问题,我认为我无法挽救这个答案
  • @Cubic 我相信你!
  • @Cubic 如果不删除init,您的论点基本上仍然有效,但变得有点复杂。 let prime_rest = filter f [5..](带有适当的f);然后你可以应用你的推理来推断prime_rest = case f 5 of { True -&gt; 5 : prime_rest1; False -&gt; prime_rest1 } where prime_rest1 = filter f [6..](对过滤器的定义进行一些重写)。但是要评估f 5,您必须首先确定prime_rest[] 还是_:_(通过finit 的定义); prime_rest 就是您当前正在评估的表达式。
  • @Cubic 换句话说,您可以将prime_rest 重写为case prime_rest of { [] -&gt; ..; [x] -&gt; ..; (x:xs) -&gt; .. },最外面的case 语句是init 的定义。
【解决方案2】:

如果the other answer中的代码在身份下重组

[take n primes | n <- [0..]]  ==  inits primes

最终我们得到了

import Data.List
              -- [ ([], 2), ([2], 3), ([2,3], 5), ... ]
primes = 2 : [ c | (ps, p) <- zip (inits primes) primes,  
                   c <- take 1 [c | c <- [p+1..], 
                                    and [mod c p > 0 | p <- ps]]]

在算法上进一步改进它,它变成了

primes = 2 : [ c | (ps, r:q:_) <- zip (inits primes)                  -- [] [3,4,...]
                                      (tails $ 3 : map (^2) primes),  -- [2] [4,9,...]
                   c <- [r..q-1], and [mod c p > 0 | p <- ps]]        -- [2,3] [9,25,...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-11
    • 2014-11-10
    • 1970-01-01
    • 2011-05-31
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    • 2010-11-11
    相关资源
    最近更新 更多