“inits”的高效版本

时间:2014-12-27 23:59:08

标签: haskell

inits "abc" == ["","a","ab","abc"]

Data.List中有inits的标准版本,但在下面我自己编写了一个版本:

myInits = f id
 where
  f start (l:ls) = (start []):(f (start . (l:)) ls)
  f start [] = [(start [])]

虽然我的版本比标准版本简单得多,但我怀疑它因效率原因而不太好。我怀疑在完全评估myInits l时,它需要O(n^2)个空格。例如,myTailstails的实现:

myTails a@(_:ls) = a:(myTails ls)
myTails [] = [[]]

这几乎与标准版本完全相同,我怀疑通过重用列表的尾部来实现O(n)空间。

有人可以解释一下:

  1. 为什么我的inits版本不好。
  2. 为什么另一种方法更好(Data.List中的标准方法或您自己的方法)。

2 个答案:

答案 0 :(得分:8)

您的myInits使用称为difference list的技术来制作以线性时间构建列表的函数。我相信,但尚未检查,完全评估myInits的运行时间是O(n^2)需要O(n^2)空间。全面评估inits还需要O(n^2)运行时间和空间。任何版本的inits都需要O(n^2)个空格;使用:[]构建的列表只能共享其尾部,inits的结果中没有共同的尾部。 initsData.List的版本使用摊销时间O(1)队列,与simpler queue described in the second half of a related answer非常相似。 Snoc referenced in the source code in Data.ListCons上的文字游戏(:的另一个名称)向后 - 可以将项目附加到列表的末尾。

简要地试验这些函数表明myInits在大型列表中稀疏使用时表现令人满意。在我的计算机上,在ghci中,myInits [1..] !! 8000000会在几秒钟内产生结果。不幸的是,我有ghc 7.8.3附带的horrifyingly inefficient implementation,因此我无法将myInitsinits进行比较。

myInitsinits以及myTailstails之间存在一个很大的区别。当应用于undefined_|_时,它们具有不同的含义(发音为“bottom”,我们用于undefined的另一个符号)。

inits具有严格性属性inits (xs ++ _|_) = inits xs ++ _|_,当xs为空列表时[]表示inits仍然会产生至少一个结果已应用于undefined

inits (xs ++ _|_) = inits xs ++ _|_
inits ([] ++ _|_) = inits [] ++ _|_
inits        _|_  = [[]]     ++ _|_
inits        _|_  =  []      :  _|_

我们可以通过实验看到

> head . inits $ undefined
[]

myInits对于空列表或更长的列表没有此属性。

> head $ myInits undefined
*** Exception: Prelude.undefined

> take 3 $ myInits ([1,2] ++ undefined)
[[],[1]*** Exception: Prelude.undefined

如果我们发现f中的myInits会在任一分支中产生start [],我们就可以解决此问题。因此,我们可以延迟模式匹配,直到需要决定下一步该做什么。

myInits' = f id
 where
  f start list = (start []):
                 case list of
                     (l:ls) -> f (start . (l:)) ls
                     []     -> []

懒惰的增加使myInits'的工作与inits一样。

> head $ myInits' undefined
[]

> take 3 $ myInits' ([1,2] ++ undefined)
[[],[1],[1,2]]

同样,myTailstails in Data.List之间的区别在于tails会在决定是否有剩余的列表之前将整个列表作为第一个结果。文档说它服从tails _|_ = _|_ : _|_,但它实际上遵循一个更难以描述的更强大的规则。

答案 1 :(得分:5)

前缀函数构建可以作为实际列表与其实现分开:

diffInits = map ($ []) . scanl (\a x -> a . (x:)) id

这明显更快(在GHCi内部测试),并且比您的版本更懒惰(请参阅Cirdec's answer进行讨论):

diffInits        _|_  == []           :  _|_
diffInits (xs ++ _|_) == diffInits xs ++ _|_