【问题标题】:Why wouldn't my sieve terminate when I rewrote it as a foldl?当我将它重写为折叠时,为什么我的筛子不会终止?
【发布时间】:2013-04-22 16:52:07
【问题描述】:

我的foldl 阻止它终止或产生输出的具体问题是什么?

首先,我实现了一个素数筛。这不是最好的,但它就像(例如)take 20 primesA 一样工作得很好。

primesA :: [Integer]
primesA = sieve 2 []

sieve :: Integral a => a -> [a] -> [a]
sieve i []   = (i:) $ sieve (i + 1) $ map (*i) [i ..]
sieve i composites@(h : t)
  | i == h    =     sieve (i + 1) t
  | otherwise = (i:) $ sieve (i + 1) $ unionIncreasing composites $ map (*i) [i ..]

unionIncreasing :: Ord a => [a] -> [a] -> [a]
unionIncreasing [] b = b
unionIncreasing a [] = a
unionIncreasing a@(ah:at) b@(bh:bt) | ah <  bh  = ah : at `unionIncreasing` b
                                    | ah == bh  = ah : at `unionIncreasing` bt
                                    | otherwise = bh : a  `unionIncreasing` bt

然后我认为使用foldl 消除计数器i 会更Haskell-y,如下所示。但这并不有效。

primesB :: [Integer]
primesB = [2..] `differenceIncreasing` composites

composites :: [Integer]
composites = foldl f [] [2..]
  where f [] i = map (*i) [i ..]
        f knownComposites@(h:t) i | i == h    = knownComposites
                                  | otherwise = (h:) $ unionIncreasing t $ map (*i) [i ..]

differenceIncreasing :: Ord a => [a] -> [a] -> [a]
differenceIncreasing [] b = []
differenceIncreasing a [] = a
differenceIncreasing (x:xs) (y:ys) | x <  y    = x : xs `differenceIncreasing` (y:ys)
                                   | x == y    = xs `differenceIncreasing` ys
                                   | otherwise = (x:xs) `differenceIncreasing` ys

当我运行(例如)head primesB 时,它既不会终止也不会产生任何输出。

据推测,ghci 正在查看无限多的素数倍数列表,试图获得列表头部的值。

但它为什么专门这样做呢?

【问题讨论】:

  • sacundim 的回答回答了您的具体问题,但您可能想看看这个:gist.github.com/nisstyre56/4699275 和论文cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
  • @Wes - 啊哈,谢谢! M. O'Neill 论文的第 11 页显示了我所瞄准的筛子。我花了更多时间尝试自己实现这一目标,但这对我来说并不容易。现在我可以仔细研究了。

标签: haskell primes lazy-evaluation sieve


【解决方案1】:

foldl 永远不能在无限列表上终止。这是函数的定义:

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs

请注意,在 Haskell 中,当您强制执行 thunk 时,评估只会在我们到达最外层操作是数据构造函数的步骤时停止(好吧,除非您使用显式强制或惰性模式匹配)。在foldl 的情况下,只有当它击中[] 时才会出现这种情况。让我们看看这个例子,反转一个无限列表(应该已经放弃了):

foldl (flip (:)) [] [1..]
  = foldl (flip (:)) [1] [2..]
  = foldl (flip (:)) [2, 1] [3..]
  = foldl (flip (:)) [3, 2, 1] [4..]
  = foldl (flip (:)) [4, 3, 2, 1] [5..]
  .
  .
  .

由于foldl 始终是最外层的运算符,而foldl 不是构造函数,因此它永远不会终止。通过一些思考,您可以看到无限列表的任何左折叠都不会终止。

foldr 没有这个问题,因为它的第二个等式顶部有 f

foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)

例子:

foldr (:) [] [1..]
  = 1 : foldr (:) [] [2..]

现在(:) 是最外层的操作符,所以评估在这里停止;某些东西必须强制构造函数的参数进行评估。

【讨论】:

  • 哦!回顾 learnyouahaskell.com 中的“仅折叠和马”部分,我看到它已明确指出,但我没有吸收它:“一个很大的区别是右折叠适用于无限列表,而左折叠则不行!说白了,如果你在某个点取一个无限列表,然后从右边折叠它,你最终会到达列表的开头。但是,如果你在某个点取一个无限列表并尝试折叠从左边往上走,你永远不会走到尽头!”下一次,我可以在 Hoogle 上查找类似 @​​987654333@ 的相关函数并阅读源代码。酷。
【解决方案2】:

对于可能想知道如何更正我的代码primesB 的任何人,以下修订版将起作用。正如 Klaus Draeger 在 cstheory.se 中写的 here 那样,它将“正确地交错各个头部”。它相当于,虽然可能不如我奥尼尔第 11 页上理查德伯德的代码 Eratosthenes 的真正筛子http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf(感谢@Wes)打印为:@ 987654323@

primesB :: [Integer]
-- #1 necessary change
-- primesB = [2..] `differenceIncreasing` composites
primesB = 2 : [3..] `differenceIncreasing` composites

-- #2 necessary change, entire function
composites = foldr f [] primesB -- not foldl, not [2..]
  where f :: Integer -> [Integer] -> [Integer]
        -- no destructuring of knownComposites;
        -- square the first list element and don't put that in unionIncreasing's second argument
        f i knownComposites = ((i*i):) $ unionIncreasing knownComposites $ map (*i) [(i+1)..]

-- No change needed in `unionIncreasing` and `differenceIncreasing`.

我仍然不确定一个人是如何实现这个解决方案的,除非它只是通过反复试验。

【讨论】:

【解决方案3】:

(澄清:这或多或少重复了my answer to your question on https://cstheory.stackexchange.com/ 的最后一部分,并对其进行了一些详细说明;我不确定它是否适合那里。)

您的第一个版本实际上非常接近 Richard Bird 的解决方案(来自 M. O'Neill 论文的第 11 页)。您可以通过代码转换和逐步改进来实现目标。

经过一些重命名,预先计算第一步给我们这个非常漂亮的代码:

--     map (*i) [i ..] == map (*i) [i, i+1 ..] == [i*i, i*i+i ..]
--     sieve 2 [] where sieve i [] = (i:) $
--               sieve (i + 1) [i*i, i*i+i ..] == 2 : sieve 3 [4, 6 ..]

primesA :: [Integer]
primesA = 2 : sieve 3 [4, 6 ..]

sieve p cs@(c : t)
   | p == c    =     sieve (p + 1) t
   | otherwise = p : sieve (p + 1) (unionI cs [p*p, p*p+p ..])

unionI a@(x:xs) b@(y:ys) | x <  y    = x : xs `unionI` b
                         | x == y    = x : xs `unionI` ys
                         | otherwise = y : a  `unionI` ys

这段代码有两个问题。有一个 (...(((a+b)+c)+d)+...) 计算结构,它将产生频率较高的流放在结构中低于产生频率较低的流的位置。

但更直接的问题是过早处理每个素数的倍数。例如。当您达到 5 时,[25, 30 ..] 将添加到合成中。但只有在达到 25 时才应该这样做。这样做会从根本上减少每个时刻正在处理的多个流的总数(从Θ(n)Θ(sqrt(n/log n)),对于 n-th 素数)。这对效率有非常重要的影响。

我们可以显式同步素数的平方,取自素数序列本身,因为它正在生成(这只是在序列中维护一个单独的反向指针ps,它的推进速度比生产点慢得多):

primesAC = [2,3] ++ sieve 4 (tail primesAC) 9 [4,6..]

sieve k ps@(p:pt) q cs@(c:ct)                 -- candidate "k", prime "p"
  | k == c  =     sieve (k + 1) ps q ct       -- square "q", composite "c"
  | k < q   = k : sieve (k + 1) ps q cs
  | otherwise =   sieve k       pt (head pt^2)       -- k == q == p*p
                        (unionI cs [q, q+p ..])

现在距离 Richard Bird 的解决方案仅半步之遥,后者通过巧妙使用的foldr 解决了两个 问题,创建了 a+(b+(c+(...))) 计算结构,其中无论如何,每个新素数的倍数流都从素数的平方开始,并且同步隐含在数据中:

primesAD = (2 :) . sieve 3 . 
            foldr (\p r-> p*p : unionI [p*p+p, p*p+2*p ..] r) [] $ primesAD

sieve p cs@(c : t)                  -- == diffI [p..] cs, if p<=c
  | p == c    =     sieve (p + 1) t
  | otherwise = p : sieve (p + 1) cs

首先无条件生成每个素数的平方,以防止失控访问过早地强制更多部分数据。

所以foldr,而不是foldl,自然适合这个问题。


你的第二个变种是

primesB :: [Integer]
primesB = [2..] `diffI` composites

composites = foldl f [4,6..] [3..]
  where 
        f cs@(h:t) i | i == h    = cs    
                     | otherwise = h : unionI t [i*i, i*i+i ..]

diffI (x:xs) (y:ys) | x <  y    = x : xs `diffI` (y:ys)
                    | x == y    =     xs `diffI` ys
                    | otherwise = (x:xs) `diffI` ys

这显然是为了计算composites,将每个数字的倍数类似地迭代添加到混合中;麻烦的是,foldl 不会让我们进入它的内部计算过程,这个过程永远不会完全完成(正如其他答案已经指出的那样)。 foldl 就是那样无情。 :) 我们可以将它变成一个生产基于iterate 的迭代,但是过早添加和倒置结构的相同问题仍然存在。此外,它现在添加了所有数字的倍数,而不仅仅是像以前那样的素数:

composites = foldl f [4,6..] [3..]
 -- no part of calculation of (f [4,6..] 3) is forced here, but if it were, ...
 = foldl f (f [4,6..] 3) [4..]                  
 = foldl f (4:unionI [6,8..] [9,12..]) [4..]    
 = foldl f (4:unionI [6,8..] [9,12..]) [5..]
 = foldl f (4:union (unionI [6,8..] [9,12..]) [25,30..]) [6..]
 = foldl f (4:union (union (unionI [6,8..] [9,12..]) [25,30..]) [36,42..]) [7..]
 = ....

([2..] `diffI`) 和上面的sieve 2 一样,所以什么都不加。

【讨论】:

    猜你喜欢
    • 2018-05-17
    • 1970-01-01
    • 2016-01-09
    • 2014-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-14
    • 1970-01-01
    相关资源
    最近更新 更多