在 Haskell 中合并两个列表

Posted

技术标签:

【中文标题】在 Haskell 中合并两个列表【英文标题】:Merging two lists in Haskell 【发布时间】:2011-04-25 16:54:02 【问题描述】:

不知道如何在 Haskell 中按以下方式合并两个列表:

INPUT:  [1,2,3,4,5] [11,12,13,14]

OUTPUT: [1,11,2,12,3,13,4,14,5]

【问题讨论】:

通常,如果你解释你尝试了什么以及为什么它不起作用,你会学到更多,这样人们就可以做一些填补空白,而不是仅仅给你一大块代码。 相关:Interleave list of lists in Haskell 【参考方案1】:

我想提出一个更惰性的合并版本:

merge [] ys = ys
merge (x:xs) ys = x:merge ys xs

对于一个示例用例,您可以查看最近关于 lazy generation of combinations 的 SO 问题。 接受的答案中的版本在第二个参数中是不必要的严格,这就是这里改进的地方。

【讨论】:

嗯,这将 ys 的所有元素放在最后,所以它不起作用。但我认为你的意思是颠倒 andri 解中前两个方程的顺序。 不,它做同样的事情 - 在每个列表之间交替。请注意,xsys 在递归调用中被交换了。 这是一个很棒的解决方案!我希望我自己也能想到类似的事情 为什么这个版本比较懒,类似于`merge (x:xs) (y:ys) = x:y: merge xs ys merge xs [] = xs merge [] ys = ys`不是吗? @Shitikanth 你看过我回答中的链接了吗?这是您需要此版本合并的额外惰性的示例。您的合并也很懒惰,但它通过模式匹配不必要地强制第二个参数。【参考方案2】:
merge :: [a] -> [a] -> [a]
merge xs     []     = xs
merge []     ys     = ys
merge (x:xs) (y:ys) = x : y : merge xs ys

【讨论】:

我是函数式编程的新手,代码让我想知道:尾调用优化是否也适用于这种形式的递归? 不,它没有。尾调用是 (:),不需要优化。 another answer 中有一个更懒的版本。第二个参数是惰性的。 @Ingo 关于 Le Curious 的问题,在达到基本情况之前,它不会保留堆栈中的所有元素吗?这不会溢出堆栈吗? @JustinMeiners 如果 (:) 构造函数在第二个参数中是严格的。但由于懒惰,它不会评估 merge xs ys 部分,直到调用者需要它。但是,创建该列表的调用已经返回。【参考方案3】:

那么为什么你认为简单的(concat . transpose)“不够漂亮”?我假设您已经尝试过类似的方法:

merge :: [[a]] -> [a]
merge = concat . transpose

merge2 :: [a] -> [a] -> [a]
merge2 l r = merge [l,r]

因此,您可以避免显式递归(与第一个答案相比),它仍然比第二个答案更简单。那么有什么缺点呢?

【讨论】:

啊,我忘了转置,错过了评论。非常好,+1(但我不一定会说它比我的第一个解决方案容易得多。) 同意。您的解决方案可能更直接。但它的真正问题是它不是 100% 正确:对于不同长度的列表(如问题的示例输入),它不能按预期工作(拖尾'5' 缺失)。 好收获!我忽略了示例输出中的 5。我将使用指向您的答案和 cmets 的指针来更新我的答案。谢谢! 看起来两者都是 O(n) 尽管显式递归比 Data.List 实现快 2 倍以上并且节省空间(这是预期的 - 后者会生成大量中间列表)与“ghc -氧气”。但是,我怀疑如果使用“transpose”和“concat”的“流融合”实现,差异会不那么明显。 主要缺点是普通人看着它必须盯着它思考一段时间才能理解它为什么起作用,而其他解决方案是显而易见的。不过,您的解决方案非常优雅。【参考方案4】:

编辑:看看 Ed'ka 的答案和 cmets!

另一种可能性:

merge xs ys = concatMap (\(x,y) -> [x,y]) (zip xs ys)

或者,如果你喜欢 Applicative:

merge xs ys = concat $ getZipList $ (\x y -> [x,y]) <$> ZipList xs <*> ZipList ys

【讨论】:

【参考方案5】:

当然是展开的案例:

interleave :: [a] -> [a] -> [a]
interleave = curry $ unfoldr g
  where
    g ([], [])   = Nothing
    g ([], (y:ys)) = Just (y, (ys, []))
    g (x:xs, ys) = Just (x, (ys, xs))

【讨论】:

您的原始代码无效; interleave [] [1,2,3] 会给[]。我认为它现在应该可以工作了。 您的unfoldr' 变形的另一个案例! (那么它就相当于上面的this answer)。 @dfeuer(以上评论)【参考方案6】:
-- ++
pp [] [] = []
pp [] (h:t) = h:pp [] t
pp (h:t) [] = h:pp t []
pp (h:t) (a:b) = h : pp t (a:b)

【讨论】:

此解决方案不正确。最后一行应该是pp (h:t) (a:b) = h : a : pp t b

以上是关于在 Haskell 中合并两个列表的主要内容,如果未能解决你的问题,请参考以下文章

Haskell中两个列表元素的所有组合

Haskell-将两个列表放入一个元组列表中

用 90 行 Haskell 代码实现 2048 游戏

为啥这个 Haskell 代码可以成功地处理无限列表?

Haskell 需要帮助理解流

比较两个列表的某些元素,Haskell