【问题标题】:Implementing take using foldr使用 foldr 实现 take
【发布时间】:2013-03-30 14:01:42
【问题描述】:

这是我使用foldrtake 版本:

myTake n list = foldr step [] list
                where step x y | (length y) < n = x : y
                               | otherwise = y

main = do print $ myTake 2 [1,2,3,4]

输出不是我所期望的:

[3,4]

然后我尝试通过将y 的长度插入自身来进行调试,结果是:

[3,2,1,0]

我不明白为什么要按降序插入长度。也许我错过了一些明显的东西?

【问题讨论】:

    标签: haskell fold take


    【解决方案1】:

    到目前为止,其他答案过于复杂,因为它们似乎过于拘泥于 foldr“从右到左”工作的概念。从某种意义上说确实如此,但 Haskell 是一种惰性语言,因此使用惰性折叠步骤的“从右到左”计算实际上会从左到右执行,因为结果会被消耗。

    研究这段代码:

    take :: Int -> [a] -> [a]
    take n xs = foldr step [] (tagFrom 1 xs)
        where step (a, i) rest
                   | i > n     = []
                   | otherwise = a:rest
    
    tagFrom :: Enum i => i -> [a] -> [(a, i)]
    tagFrom i xs = zip xs [i..]
    

    【讨论】:

      【解决方案2】:

      如果要使用foldr 实现take,则需要模拟从左到右遍历列表。关键是使折叠函数依赖于一个额外的参数,该参数编码你想要的逻辑,而不仅仅是依赖于列表的折叠尾部。

      take :: Int -> [a] -> [a]
      take n xs = foldr step (const []) xs n
        where
          step x g 0 = []
          step x g n = x:g (n-1)
      

      这里,foldr 返回一个函数,该函数接受一个数字参数并从左到右遍历列表,从中获取所需的数量。由于懒惰,这也适用于无限列表。一旦额外参数达到零,foldr 将短路并返回一个空列表。

      【讨论】:

        【解决方案3】:

        foldr 将从*最后一个元素**开始应用函数step。也就是说,

        foldr step [] [1,2,3,4] == 1 `step` (2 `step` (3 `step` (4 `step` [])))
                                == 1 `step` (2 `step` (3 `step` (4:[])))
                                == 1 `step` (2 `step (3:4:[]))   -- length y == 2 here
                                == 1 `step` (3:4:[])
                                == 3:4:[]
                                == [3, 4]
        

        长度按降序“插入”,因为: 是一个前置 操作。较长的长度被添加到列表的开头。

        (图片取自http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29

        *:为简单起见,我们假设每个操作都是严格的,这在 OP 的 step 实现中是正确的。

        【讨论】:

        • "foldr 将从最后一个元素开始应用函数step。"面对懒惰的评估,这种说法充其量是非常具有误导性的。 Haskell 实际上是从左到右计算你的第二棵树,如果 step 函数对它的第二个参数不严格,它可能会提前中止计算。最简单的例子是safeHead = foldr (\x _ -&gt; Just x) Nothing
        • 我应该补充一点,您的评估步骤顺序是从内部函数应用程序到外部函数应用程序,这与 Haskell 所做的相反顺序。首先评估最外层的step,即以1 作为第一个参数的那个。如果step 不需要它的第二个参数,则计算在此结束,然后再查看列表的其余元素。如果step x _ = Just x,那么foldr step Nothing [1,2,3,4] == step 1 (foldr step Nothing [2,3,4]) == Just 1
        • @sacundim 但是问题中的step 在它的第二个参数中是严格的,所以在这种情况下foldr 没有选择,只能从列表的末尾到前面,然后评估最外层的step,首先必须评估内部的steps。明确说明这是一种特殊情况会有助于答案,但对于给定代码的描述是正确的。
        猜你喜欢
        • 2010-09-19
        • 1970-01-01
        • 2016-09-04
        • 1970-01-01
        • 2021-04-15
        • 2014-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-08-26
        相关资源
        最近更新 更多