我目前正在学习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]]
那么,正如标题中所述,我将如何有效地合并两个列表?
答案 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
这里有两个问题:
一种更有效的地图实现方法是:
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-