解释这一块输出素数流的haskell代码

时间:2009-11-19 15:38:08

标签: haskell primes lazy-evaluation

我无法理解这段代码:

let
  sieve (p:xs) = p : sieve (filter (\ x -> x `mod` p /= 0) xs)
in sieve [2 .. ]

有人可以为我分解吗?我知道它有递归,但问题是我无法理解这个例子中的递归是如何工作的。

5 个答案:

答案 0 :(得分:22)

与其他人在此处所说的相反,此功能 does not 实现了真正的sieve of Eratosthenes。它确实以相似的方式返回素数的初始序列,因此可以将其视为Eratosthenes的筛子。

当mipadi posted回答时,我已经完成了对代码的解释;我可以删除它,但由于我花了一些时间,并且因为我们的答案不完全相同,我会把它留在这里。


首先,请注意您发布的代码中存在一些语法错误。正确的代码是,

let sieve (p:xs) = p : sieve (filter (\x -> x `mod` p /= 0) xs) in sieve [2..]
  1. let x in y定义x并允许其定义在y中使用。此表达式的结果是y的结果。因此,在这种情况下,我们定义一个函数sieve并返回将[2..]应用于sieve的结果。

  2. 现在让我们仔细看看这个表达式的let部分:

    sieve (p:xs) = p : sieve (filter (\x -> x `mod` p /= 0) xs)
    
    1. 这定义了一个函数sieve,它以列表作为第一个参数。
    2. (p:xs)是一个模式,它将p与所述列表的头部匹配,xs与尾部匹配(除了头部之外的所有内容)。
    3. 通常,p : xs是第一个元素为p的列表。 xs是包含其余元素的列表。因此,sieve返回它收到的列表的第一个元素。
    4. 不看列表的其余部分:

      sieve (filter (\x -> x `mod` p /= 0) xs)
      
      1. 我们可以看到递归调用sieve。因此,filter表达式将返回一个列表。
      2. filter采用过滤功能和列表。它仅返回列表中过滤器函数返回true的那些元素。
      3. 在这种情况下,xs是要过滤的列表

        (\x -> x `mod` p /= 0)
        

        是过滤功能。

      4. 过滤器函数只接受一个参数x,如果它不是p的倍数,则返回true。
  3. 现在定义了sieve,我们传递[2 .. ],从2开始的所有自然数列表。因此,

    1. 将返回2号。所有其他自然数是2的倍数将被丢弃。
    2. 第二个数字是3.它将被退回。所有其他3的倍数将被丢弃。
    3. 因此下一个号码将是5.等等。

答案 1 :(得分:14)

它实际上非常优雅。

首先,我们定义一个带有元素列表的函数sieve

sieve (p:xs) =

sieve的主体中,我们采用列表的头部(因为我们传递无限列表[2..],并且2被定义为素数)并追加它(懒惰!)将sieve应用于列表的其余部分的结果:

p : sieve (filter (\ x -> x 'mod' p /= 0) xs)

让我们看一下代码列表其余部分的代码:

sieve (filter (\ x -> x 'mod' p /= 0) xs)

我们将sieve应用于已过滤的列表。让我们分解过滤器部分的作用:

filter (\ x -> x 'mod' p /= 0) xs

filter接受一个函数和一个列表,我们应用该函数,并保留符合函数给出的条件的元素。在这种情况下,filter采用匿名函数:

\ x -> x 'mod' p /= 0

这个匿名函数接受一个参数x。它检查x的{​​em>模数对p(列表的头部,每次调用sieve时):

 x 'mod' p

如果模数不等于0:

 x 'mod' p /= 0

然后元素x保留在列表中。如果它等于0,则将其过滤掉。这是有道理的:如果x可以被p整除,那么x可以被1除以及它自身整除,因此它不是素数。

答案 2 :(得分:9)

它定义了一个生成器 - 一个名为“sieve”的流变换器

Sieve s = 
  while( True ):
      p := s.head
      s := s.tail
      yield p                             -- produce this
      s := Filter (nomultsof p) s         -- go next

primes := Sieve (Nums 2)

使用与

等效的匿名函数的curried形式
nomultsof p x = (mod x p) /= 0

SieveFilter都是数据构造操作,内部状态和按值参数传递语义。


我们可以看到此代码的最明显的问题,重复 它使用trial division过滤掉工作序列中的倍数,而它可以通过counting up in increments of p直接找到它们。如果我们用后者替换前者,那么生成的代码仍然会有非常糟糕的运行时复杂性。

不,它最明显的问题是它会将Filter置于其工作序列过早之上,而它应该在输入中真正执行only after the prime's square is seen。因此,与真正需要的相比,它创建了{em>二次数量的Filter s。它创建的Filter链条太长了,根本不需要它们。

修正后的版本,过滤器创建推迟直到适当的时刻,

Sieve ps s = 
  while( True ):
      x := s.head
      s := s.tail
      yield x                             -- produce this
      p := ps.head
      q := p*p
      while( (s.head) < q ):
          yield (s.head)                  --    and these
          s := s.tail
      ps := ps.tail                       -- go next
      s  := Filter (nomultsof p) s

primes := Sieve primes (Nums 2) 

in Haskell

primes = sieve primes [2..] 
sieve ps (x:xs) = x : h ++ sieve pt [x | x <- t, rem x p /= 0]
     where (p:pt) = ps
           (h,t)  = span (< p*p) xs 
这里使用的是{p> rem而不是mod,因为在某些口译员中它可以快得多,而且无论如何这些数字都是正数。

通过将问题大小为t1,t2的运行时间n1,n2作为logBase (n2/n1) (t2/t1)来衡量算法的local orders of growth,我们得到O(n^2)第一个,第二个就在O(n^1.4)之上(产生n素数)。


为了澄清它,缺少的部分可以用这个(想象的)语言定义为

Nums x =            -- numbers from x
  while( True ):
      yield x
      x := x+1

Filter pred s =     -- filter a stream by a predicate
  while( True ):
      if pred (s.head) then yield (s.head)
      s := s.tail

see also


更新:奇怪的是,根据A.J.T.,David Turner的1976年SASL手册中的第一个代码实例。戴维1992年的哈斯克尔书,

    primes = sieve [2..]

    sieve (p:nos) = p : sieve (remove (multsof p) nos)

实际上承认两个 的实现removemultsof一起进行 - 一对用于试验分区筛,就像在这个问题中一样,另一个用于有序去除每个素数的倍数,这些倍数是通过计数直接产生的,也就是Eratosthenes的真正的筛子(当然,两者都不会被推迟)。在Haskell,

    multsof p n = rem n p==0            remove m xs = filter (not . m) xs

    multsof p = [p*p, p*p+p..]          remove m xs = diff xs m

(如果他推迟选择实际的实施......)

对于推迟的代码,在带有“列表模式”的伪代码中,它可能已经

    primes = [2, ...sieve primes [3..]]

    sieve [p, ...ps] [...h, p*p, ...nos] =
                     [...h, ...sieve ps (remove (multsof p) nos)]

答案 3 :(得分:1)

正在实施Sieve of Eratosthenes

基本上,从一个素数(2)开始,并从其余的整数中过滤出所有的两个倍数。该筛选列表中的下一个数字也必须是素数,因此从剩余的数字中过滤掉所有的倍数,依此类推。

答案 4 :(得分:1)

它说“一些列表的筛子是列表的第一个元素(我们称之为p)和列表其余部分的筛子,过滤使得只有不能被p整除的元素才能通过”。然后通过将所有整数的筛子从2返回到无穷大(这是2然后是不能被2整除的所有整数的筛子等)来启动事物。

在攻击Haskell之前,我建议The Little Schemer