【问题标题】:Intercalate using fold使用折叠插入
【发布时间】:2019-03-24 08:13:06
【问题描述】:

谁能解释我如何使用 fold 编写 intercalate 函数?另外,我听说过foldrfoldl;在这种情况下,哪一个最适合使用?

这是我的递归尝试:

intercalate :: [a] -> [[a]] -> [a]
intercalate s [] = []
intercalate s [x] = x
intercalate s (x:xs) = x ++ s ++ (intercalate s xs)

【问题讨论】:

  • 如果它是惰性的(在它的列表参数中),它只能是正确的折叠。 take 10 . intercalate [0] $ repeat [1..3] 必须工作,就像原来一样。

标签: function haskell fold


【解决方案1】:

使用foldr1

intercalate' :: [a] -> [[a]] -> [a]
intercalate' _ [] = []
intercalate' x xs = foldr1 (\a acc -> a ++ x ++ acc) xs

这将在每个元素之间插入来自 right 的值与 x。 (尽管它这样做了lazily。– 直到需要时才评估整个折叠。)

foldr1foldr 的相似之处在于它们从右侧折叠。不同之处在于前者不需要我们提供最终期限,而后者确实需要提供最终期限。这需要我们对空列表进行模式匹配,否则会抛出异常。

确实,foldl1 也可以完成插层。但是,强烈建议不要这样做,因为左折叠会急切地消耗输入。

intercalate' :: [a] -> [[a]] -> [a]
intercalate' _ [] = []
intercalate' x xs = foldl1 (\acc a -> acc ++ x ++ a) xs

为了进一步强调对正确折叠的偏好,请考虑以下组合:

take 10 . intercalate' [0] $ repeat [1..3]

看起来无害。但是使用foldl1 会急切地消耗无限列表,而不是停止进程。

但是,使用foldr1 将延迟评估并给出[1,2,3,0,1,2,3,0,1,2] 的正确结果。 (intercalate' 将在 take 10 询问前导词并评估折叠之前临时评估 [1..3] ++ [0] ++ (foldr (...) [0] [elem1 ... elemN])。)

Will致敬。

实用阅读:
foldl versus foldr behaviour with infinite lists

【讨论】:

  • foldr1 "从右侧累加,并将值添加到累加器" 仅当 reducer 函数是严格的。在这里,它从左到右处理列表,实时拼接递归结果(不是“累加器”,所以r 或类似的是正确的助记符,而不是acc)。而左折叠这里绝对是错误的工具,它破坏了懒惰:take 10 . intercalate [0] $ repeat [1..3] 必须像原来的那样工作。
  • @WillNess “只有在 reducer 函数严格的情况下”——啊,所以由于懒惰,结果最终会变成一整条 elem0 ++ x ++ elem1 ++ x + elem2 ++ x ++ ... ++ x ++ elemN 链,然后在需要时对其进行评估? (顺便说一句,很好。)
  • 用正确的折叠,是的。不,甚至不是:实际上它将是elem0 ++ x ++ (foldr (...) x [elem1....])。像你一样写它是一个很好的助记符,但它甚至比这更懒惰。 (关于foldr“累积”顺便说一句,你仍然有错误的解释......,并且必须更强烈地劝阻使用左折叠,IMO)
  • foldr g n xs 中的n 不是第一个词,而是最后一个词,如果g 在其第二个参数中不严格的话。 r 中的 foldr 并不意味着它折叠 from 权利——这仅由 g 的语义决定。我们最多可以说 foldr 折叠 on 右侧或 to 右侧。当g 懒惰时(就像在这个答案中一样)“从最后一个元素开始折叠”是错误的。相反,它“折叠在最后一个元素处结束”。 foldr g n (x:xs) == g x (foldr g n xs),就是这样。
  • 很抱歉打扰您,但是:foldl “执行热切评估”。事实上,它的主要缺点不是(foldl' 这样做,应该“几乎总是”使用)。 foldl 确实会急切地使用输入列表(并且在遍历整个列表之前,它不会评估它构建的 reducer 函数链)。 ---- 是的,你会花时间和精力到达那里。 :)
【解决方案2】:

让我们提醒自己foldl的签名:

foldl :: (b -> a -> b) -> b -> t a -> b

对你所写内容的简单重写:

intercalate :: [a] -> [[a]] -> [a]
intercalate s [] = []
intercalate s [x] = x
intercalate s (x:xs) = foldl (\acc y -> acc ++ s ++ y) x xs

【讨论】:

  • 这个定义不能编译:'不同数量的参数'。
  • @bradrn 这是我在不测试的情况下编写代码所得到的......:P
  • 听起来很耳熟!
  • foldl 破坏懒惰,左嵌套 (++) 破坏效率。双重不利。
猜你喜欢
  • 1970-01-01
  • 2017-03-23
  • 2011-01-16
  • 1970-01-01
  • 2015-11-11
  • 1970-01-01
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多