有效地合并两个列表

时间:2018-11-17 15:40:32

标签: list haskell merge compiler-errors

我目前正在学习Haskell,并且正在与List合作。

根据HaskellWiki,如果我想将两个列表合并在一起,我会写:

list1 ++ list2 

但是,根据this答案,在大列表上使用++效率不高。

通过研究,我遇到了this SO page,但是这个问题要求对List的输出有特定的要求。

我尝试过的事情

假设我有两个数字列表(对于此示例,假设两个列表都足够大,以致如SO答案所述,使用++效率不高):

 oldNumbers = [1,5,14,22,37]
 newNumbers = [3,10,17,27,34,69]
 allNumbers = oldNumbers:newNumbers

如您所见,我试图将oldNumbers添加到newNumbers的开头,目的是随后将其反转(忽略allNumbers暂时是无序的,这是一个练习另一天)。

您可能已经猜到了,它产生了一个错误

error:
    * Non type-variable argument in the constraint: Num [a]
      (Use FlexibleContexts to permit this)
    * When checking the inferred type
        allNumbers :: forall a. (Num a, Num [a]) => [[a]]

那么,正如标题中所述,我将如何有效地合并两个列表?

3 个答案:

答案 0 :(得分:6)

  

根据HaskellWiki,如果我想将两个列表合并在一起,   我会写:

list1 ++ list2 
     

但是,根据this answer,在大型列表上使用++   效率低下。

我认为您需要在此答案中考虑上下文。如果我们用a附加两个列表b(++),则它将两个列表附加在 O(m)(带有 m 列表的长度)。因此,就时间复杂度而言,这是有效的。构造这种单链表的效率最高。

@melpomene实际上指出了Haskell的新手经常犯的一个普遍错误:他们在列表的末尾附加了 single 元素。再一次,如果您只想将单个元素添加到列表中,那不是问题。如果您想以这种方式将 n 元素添加到列表中,那么就会出现问题,因此,如果您每次都将单个元素添加到列表中,并且每次执行 n 次,则算法为 O(n 2

因此,简而言之,从时间复杂度的角度来看,将两个列表附加在一起时(++)并不慢,而将单个列表附加到其中也不会很慢。但是,就渐近行为而言,通常有比重复将带有一个元素的列表重复添加到列表更好的方法,因为这样第一个操作数通常会变大,并且每次迭代都花费 O(n)仅将单个元素添加到列表的时间。通常,在这种情况下,可以在列表的tail元素上使用递归,或者例如反向构建列表。

因此,

(++)并非“固有”的低效率,通过以非设计的方式使用它,可以获得无效的行为。

++会非常慢的示例是以下执行“映射”的函数:

badmap :: (a -> b) -> [a] -> [b]
badmap f = go []
    where go temp [] = temp
          go temp (x:xs) = go (temp ++ [f x]) xs

这里有两个问题:

  1. 它不是惰性的,它需要评估整个列表,然后才能发出第一个元素;和
  2. 它将以二次时间运行,因为对于输入列表中的每个元素,添加该元素将花费越来越多的时间。

一种更有效的地图实现方法是:

goodmap :: (a -> b) -> [a] -> [b]
goodmap f = go
    where go (x:xs) = f x : go xs
          go [] = []

答案 1 :(得分:4)

一次性添加两个列表 ++绝对没错,无论它们长多少。

您引用的答案(正确地)是关于一个元素列表的重复附加,其中++ [x]为“坏”。

答案 2 :(得分:0)

如果您想有效地附加任意列表,请使用其他数据结构!例如,对Data.Seq进行了优化,以实现高效的附加。

https://www.stackage.org/haddock/lts-12.19/containers-0.5.11.0/Data-Sequence.html#v:-62--60-