我无法理解这段代码:
let
sieve (p:xs) = p : sieve (filter (\ x -> x `mod` p /= 0) xs)
in sieve [2 .. ]
有人可以为我分解吗?我知道它有递归,但问题是我无法理解这个例子中的递归是如何工作的。
答案 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..]
let x in y
定义x
并允许其定义在y
中使用。此表达式的结果是y
的结果。因此,在这种情况下,我们定义一个函数sieve
并返回将[2..]
应用于sieve
的结果。
现在让我们仔细看看这个表达式的let
部分:
sieve (p:xs) = p : sieve (filter (\x -> x `mod` p /= 0) xs)
sieve
,它以列表作为第一个参数。(p:xs)
是一个模式,它将p
与所述列表的头部匹配,xs
与尾部匹配(除了头部之外的所有内容)。p : xs
是第一个元素为p
的列表。 xs
是包含其余元素的列表。因此,sieve
返回它收到的列表的第一个元素。不看列表的其余部分:
sieve (filter (\x -> x `mod` p /= 0) xs)
sieve
。因此,filter
表达式将返回一个列表。filter
采用过滤功能和列表。它仅返回列表中过滤器函数返回true的那些元素。在这种情况下,xs
是要过滤的列表
(\x -> x `mod` p /= 0)
是过滤功能。
x
,如果它不是p
的倍数,则返回true。现在定义了sieve
,我们传递[2 .. ]
,从2开始的所有自然数列表。因此,
答案 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
Sieve
和Filter
都是数据构造操作,内部状态和按值参数传递语义。
我们可以看到此代码的最明显的问题是不,重复 不 它使用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)
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
更新:奇怪的是,根据A.J.T.,David Turner的1976年SASL手册中的第一个代码实例。戴维1992年的哈斯克尔书,
primes = sieve [2..]
sieve (p:nos) = p : sieve (remove (multsof p) nos)
实际上承认两个 对的实现remove
和multsof
一起进行 - 一对用于试验分区筛,就像在这个问题中一样,另一个用于有序去除每个素数的倍数,这些倍数是通过计数直接产生的,也就是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)
基本上,从一个素数(2)开始,并从其余的整数中过滤出所有的两个倍数。该筛选列表中的下一个数字也必须是素数,因此从剩余的数字中过滤掉所有的倍数,依此类推。
答案 4 :(得分:1)
它说“一些列表的筛子是列表的第一个元素(我们称之为p)和列表其余部分的筛子,过滤使得只有不能被p整除的元素才能通过”。然后通过将所有整数的筛子从2返回到无穷大(这是2然后是不能被2整除的所有整数的筛子等)来启动事物。
在攻击Haskell之前,我建议The Little Schemer。