在 Haskell 中交错列表列表

Posted

技术标签:

【中文标题】在 Haskell 中交错列表列表【英文标题】:Interleave List of Lists in Haskell 【发布时间】:2012-12-20 14:53:36 【问题描述】:

我想知道如何在 Haskell 中编写一个将列表列表交错成单个列表的函数,例如,如果我有一个名为

的函数

interleavelists :: [[a]] -> [a]

它应该能够交错元素。

例如:[[1,2,3] [4,5,6] [7,8]] --> [1,4,7,2,5,8,3,6].

列表可以是有限的也可以是无限的...我可以使用foldr吗?

【问题讨论】:

相关:Merging two lists in Haskell 【参考方案1】:

最快的编写方法是使用Data.List中的transpose

import Data.List

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

transpose 选择其参数的每个非空列表的第一个元素,将它们放入一个列表中,然后transposes 是参数元素的tails 列表。 concatenating transpose 的结果列表会根据需要交错列表。如果某些元素列表是无限的,它会起作用,但如果列表列表本身有无限多的元素,它当然永远不会超过heads 的列表。但无论如何处理这种情况是有问题的。

使用foldr 交错列表并非易事。假设你有

interleavelists xss = foldr something zero xss

interleavelists [] 应该可能产生[],所以这就是zero 的值。和

interleavelists [xs] = xs

看起来很自然,所以

something xs [] = xs

但是如果第二个参数不是[] 怎么办?然后你想将something 的第一个参数的元素以不同的距离插入到第二个参数中。但在哪些距离?如果所有列表的长度相同,每个列表的距离都是恒定的,那么您可以将距离作为进一步的参数传递,

interleavelists = snd . foldr insertAtDistance (0, [])
  where
    insertAtDistance xs (d, ys) = (d+1, helper d xs ys)
    helper _ [] ws = ws
    helper k (b:bs) cs = b : us ++ helper k bs vs
      where
        (us,vs) = splitAt k cs

这不是很漂亮,如果列表的长度不同,将产生可能不是所需的输出。但是,如果列表都具有相同的长度,它就可以完成这项工作。

【讨论】:

【参考方案2】:

简单的递归版本:

inter :: [[a]] -> [a]
inter [] = []
inter xs = inter2 (filter (\x -> not (null x)) xs)
   where inter2 xs = map head xs ++ inter (map tail xs)

现在,关于折叠...

【讨论】:

【参考方案3】:

交错列表的“标准”(或者也许是著名的)方式,早在 SICP(以及后来的 Reasoned Scheme)的欢乐日子里,是

infixr 5 ++/

[]     ++/ ys = ys
(x:xs) ++/ ys = x:(ys ++/ xs)

可以和foldr一起使用,

*Main> foldr (++/) [] [[1,2,3],[4,5,6],[7,8]]
[1,4,2,7,3,5,8,6]

这显然不会按您想要的顺序产生结果,但是当列表的输入列表是无限的时,它会正常工作。我认为没有办法同时满足这两个要求,因为我们无法知道输入列表是否是无限的。

如果修改为始终在1 的距离处插入而不是d+1,则上述结果是来自Daniel's answer 的函数insertAtDistance 将产生的结果:

insertAtDistance xs (d, ys) = (1, helper d xs ys)

当使用d+1 定义时,它会产生“扁平”交织,而foldr (++/) [] 会产生倾斜交织:

*Main> take 20 $ foldr (++/) [] [cycle[i] | i<-[1..]]
[1,2,1,3,1,2,1,4,1,2,1,3,1,2,1,5,1,2,1,3]

【讨论】:

TWIMC:一定要搜索“diagonalize Haskell list interleaving”、“dovetailing”、“Control.Monad.Omega”、“Data.Universe.Helpers”,以获得更全面的信息。 this 似乎也是一篇关于此事的有趣的博文。另请参阅"Cartesian product of infinite lists" 甚至 this answer of mine 上的 CS 等。【参考方案4】:

我们可以做你想做的

testList = [[1,2,3],[4,5,6],[7,8]]

interleave l = foldr' (concat [map (\x -> f x idx) l | idx <- [0..]])  
    where
        f x n = if (length(x)<=n) then Nothing else Just (x !! n)
        foldr' (x:xs) = case x of 
                         Nothing -> []
                         Just a  -> (:) a (foldr' xs)   

根据需要 交错 [[1,2,3] [4,5,6] [7,8]] => [1, 4, 7, 2, 5, 8, 3, 6]

【讨论】:

[[1,2,3],[4,5],[6,7,8]] 怎么样...(查看Data.Maybe.catMaybes :: [Maybe a] -&gt; [a])? [[1..],[4,5,6],[7,8]]呢? 你说得对,我明白你在说什么,我会试试 catMaybes(我不知道这很有趣,谢谢)。事实上,我已经意识到我的答案不完整(或错误),但 D. Fisher 的答案是如此完整和聪明,以至于我认为修改我的答案没有用。 catMaybes 做了它唯一可以做的事,而这正是这里所需要的。如果您决定修复它,您可以对foldr' 进行一些本地化更改,或者完全重写,例如[x | Just x&lt;- input]。 -- Daniel Fischer 的答案中的两个函数都不适用于列表的无限输入列表 - 第一个会卡在头上,但第二个将完全没有生产力,顺便说一句。 更正-实际上有几种可能性-catMaybes 可以从列表中的所有Just as 中返回as,或者只是第一个/最后一个/第二个/中间/每个第三个/等等......我想归还所有是它可以做的最普遍的事情。

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

如何在 haskell 中打印列表?

列表中最短的列表(Haskell)

Haskell 不会懒惰地评估交错

在 Haskell 中添加列表/覆盖现有列表的功能

在 Haskell 中合并两个列表

在 Haskell 中整理列表理解