【问题标题】:Haskell Merging multiple listsHaskell合并多个列表
【发布时间】:2018-11-12 00:00:14
【问题描述】:

我想编写一个合并函数,它采用多个 x 排序列表并将它们按增量值(从最小到最大)合并到一个排序列表中。我想我可以做两个列表并组合成一个,但无法弄清楚多个列表的基本情况并组合成一个排序。

merge :: [[a]] -> [a]

【问题讨论】:

    标签: haskell merge functional-programming


    【解决方案1】:

    也许更快的实现:

    mergeTwo :: Ord a => [a] -> [a] -> [a]
    mergeTwo x [] = x
    mergeTwo [] x = x
    mergeTwo (x:xs) (y:ys) = if x < y
                              then x:(mergeTwo xs (y:ys))
                              else y:(mergeTwo (x:xs) ys)
    
    mergePairs :: Ord a => [[a]] -> [[a]]
    mergePairs [] = []
    mergePairs (x:[]) = [x]
    mergePairs (x:y:tail) = mergePairs ((mergeTwo x y):(mergePairs tail))
    
    mergeAll :: Ord a => [[a]] -> [a]
    mergeAll [] = []
    mergeAll x = head $ mergePairs x
    

    mergeTwo 只是合并两个列表。 mergeAll 只是运行 mergePairs 并返回 head 如果有的话。魔术发生在 mergePairs 中,它获取列表并合并对,而不是再次这样做等等,而至少有两个列表。

    假设你正在跑步,它可能会更快

    merge = foldl merge2 []
    

    它需要一个长列表并合并和合并。如果你在 [[1,2,3],[4,5,6],[7,8,9],[10,11,12]] 运行它,它会合并:

    [] 与 [1,2,3]

    [1,2,3] 和 [4,5,6]

    [1,2,3,4,5,6] 和 [7,8,9]

    [1,2,3,4,5,6,7,8,9] 与 [10,11,12]

    但您希望保留大致相同长度的列表。所以你想合并:

    [1,2,3] 和 [4,5,6]

    [7,8,9] 与 [10,11,12]

    [1,2,3,4,5,6] 与 [7,8,9,10,11,12]

    您还可以考虑并行实现 mergePairs,它在多核处理器上可能很有用。但我没有这方面的经验:/

    【讨论】:

    • 在一个线程中是的,但恕我直言,它可以很容易地完成多线程。
    • 我认为这并不像您认为的那样有效。这也是O(n^2) 解决方案。我试图在我的回答下解释它。
    【解决方案2】:

    如果您可以为两个列表定义函数,那么您可以通过简单地遍历每个子列表并将其与当前结果合并,直到您合并所有列表,将其推广到任意多个列表。这可以表示为这样的折叠:

    merge = foldr merge2 []
    

    【讨论】:

    • 想象每个子列表都是一个单例,并且它们以相反的顺序出现,那么这需要 O(n^2)。您想使用自下而上的合并 O(n log n)。由于 merge2 定义了一个幺半群,我们可以使用 foldM 的树形实现来实现这一点。同样的问题也适用于 luqui 的回答。
    • ^^ "*foldMap 的树形实现。"例如Data.List.Ordered.foldt' Data.List.Ordered.merge [].
    【解决方案3】:

    @sepp2k 的答案很好,但它只适用于有限多个输入列表。如果你给它无限多的列表,它将永远试图找到最小的起始元素。

    我们可以通过要求输入列表已经通过增加第一个元素来排序来解决这个问题。然后我们知道我们可以产生“左上角”元素(第一个列表的第一个元素),因为它将是所有内容的下限,这为我们提供了足够的信息来递归使用并产生完整的合并。

    merge :: (Ord a) => [[a]] -> [a]
    merge [] = []
    merge ([]:xss) = merge xss
    merge ((x:xs):xss) = x : merge2 xs (merge xss)
    

    merge2 仍然留给读者作为练习:-)

    【讨论】:

      【解决方案4】:

      如果您先将列表与concat 合并,然后再合并sort,会容易得多。

      import Data.List(sort)
      
      mergeSort = sort . concat
      

      【讨论】:

        【解决方案5】:

        这是 mergeSort 或 timSort 算法的基本部分,但遗憾的是在这个主题下没有正确答案。我的意思是一个高效的。

        让我们假设一个带有k = 9 块的chunks 列表,例如[as,bs,cs,ds,es,fs,gs,hs,is],其中as..is 是块。

        foldl merger [] chunks
        

        会产生以下操作

        merge as bs -> abs
        merge abs cs -> abcs
        merge abcs ds -> abcds
        merge abcds es -> abcdes
        merge abcdes fs -> abcdefs
        merge abcdefs gs -> abcdefgs
        merge abchefgs hs -> abcdefghs
        merge abcdefghs is -> abcdefghis
        

        asbs 运行 8 次,cs 运行 7 .. is 运行 1 次。总共是 k(k+1)/2 = 44 次操作。如果您有 100,000 个块,将进行 5,000,050,000 次操作。复杂度为 O(n^2)。

        @kyticka 似乎已经注意到了这一事实,并在他的回答中提到了它的重要性,但未能正确实现它(由于双重递归,这有时是一种欺骗)。他的方法仅在第一阶段消除了多余的操作,并将块放在排序的对中,然后像幼稚的方法一样继续进行,仅从右折叠如下;

        let mt = mergeTwo
            mp = mergePairs
        
        mp (mt as bs : mp [cs,ds,es,fs,gs,hs,is]))
        mp (mt as bs : mp (mt cs ds : mp [es,fs,gs,hs,is]))
        mp (mt as bs : mp (mt cs ds : mp (mt es fs : mp [gs,hs,is])))
        mp (mt as bs : mp (mt cs ds : mp (mt es fs : mp (mt gs hs : mp [is])))
        mp (mt as bs : mp (mt cs ds : mp (mt es fs : mp [ghs,is])))
        mp (mt as bs : mp (mt cs ds : mp (mt es fs : mp (mt ghs is : mp []))))
        mp (mt as bs : mp (mt cs ds : mp (mt es fs : mp [ghis])))
        mp (mt as bs : mp (mt cs ds : mp [efs, ghis]))
        mp (mt as bs : mp (mt cs ds : mp (mt efs ghis : mp [])))
        mp (mt as bs : mp (mt cs ds : mp [efghis]))
        mp (mt as bs : mp [cds, efghis])
        mp (mt as bs : mp (mt cds efghis : mp []))
        mp (mt as bs : mp [cdefghis])
        mp [abs, cdefghis]
        mp (mt abs cdefghis : mp [])
        mp [abcdefghis]
        [abcdefghis]
        

        所以 chunks 的合并操作计数如下

        • as = 2 (mt as bs - mt abs cdefghis)
        • bs = 2 (mt as bs - mt abs cdefghis)
        • cs = 3 (mt cs ds - mt cds efghis - mt abs cdefghis)
        • ds = 3 (mt cs ds - mt cds efghis - mt abs cdefghis)
        • es = 4 (mt es fs - mt efs ghis - mt cds efghis - mt cds efghis)
        • fs = 4 与 es 相同
        • gs = 5 ...
        • hs = 5 与 gs 相同
        • 是 = 5

        操作总数为 33。我猜这可能是 k(k+1)/2 - k..?与幼稚的foldl 解决方案相比,它不会产生任何显着差异,因为它仍然是 O(n^2)。

        这是一个 O(nLog(n)) 的尝试。

        mergeChunks :: Ord a => [a] -> [a] -> [a]
        mergeChunks xs [] = xs
        mergeChunks [] ys = ys
        mergeChunks p@(x:xs) q@(y:ys) | x <= y    = x : mergeChunks xs q
                                      | otherwise = y : mergeChunks p ys
        
        mergePairs :: Ord a => [[a]] -> [[a]]
        mergePairs []         = []
        mergePairs (ps:[])    = [ps]
        mergePairs (ps:qs:rs) = mergeChunks ps qs : mergePairs rs
        
        mergeAll :: Ord a => [[a]] -> [a]
        mergeAll []     = []
        mergeAll [c]    = c
        mergeAll cs     = mergeAll (mergePairs cs)
        

        【讨论】:

          【解决方案6】:
          import Data.List ( sort )
          
          sortLists :: (Ord a) => [[a]] -> [a]
          sortLists cs = concatMap sort cs
          
          test = [ [3,1,2],
                   [5,4],
                   [6,8,9,7] ]
          
          main = print $ sortLists test
          

          要真正享受 haskell,请仔细查看并记住 PreludeData.List。它们是每个 Haskell 程序员的生计。

          【讨论】:

          • 我认为这根本不是问题所要求的 - 特别注意 sortLists [[2],[1]] 是 [2,1]。
          猜你喜欢
          • 2021-02-19
          • 2011-04-25
          • 2012-01-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-02-17
          • 2021-11-01
          相关资源
          最近更新 更多