将元素添加到列表末尾而不通过整个列表(haskell)

时间:2017-07-31 17:09:17

标签: haskell

Haskell中有没有办法在列表末尾添加元素而不必遍历整个列表?

示例如果我有列表[1,2,3,4]并希望在结尾添加5,例如:[1,2,3,4,5]

据我所知,++运算符必须解析整个列表,这是非常低效的。

谢谢!

1 个答案:

答案 0 :(得分:4)

正如@Jubobs在评论中已经说过的那样 - 不,你不能,但这并不一定(总是)低效。

在开始做一些解释之前,我想提醒你唐纳德克努特和他的说法,即所有邪恶的根源都是过早的优化。因此,尝试编写正确的程序并在以后担心性能 - 当您遇到瓶颈时(并确保haskell具有良好的工具来检测瓶颈!)。

实施例

假设您有一个无限列表[1..]并且您想要附加5,您仍然可以在haskell中执行此操作。让我们开启ghci并观察一些事情。

> ghci
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Prelude> let a = [1..] ++[5] :: [Int]

首先我们注意到 - let - 绑定工作懒惰而且没有打印(尚未)。我们甚至可以进一步检查a的评估范围。

Prelude> :sprint a
a = _

虽然filter (== 5) a之类的表达式永远不会终止,但我们仍然可以使用列表a执行有用的操作。

Prelude> let b = map (+2) a
Prelude> take 10 b
[3,4,5,6,7,8,9,10,11,12]

再次检查a

Prelude> :sprint a
a = 1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10 : _

我们只评估了前十个元素。

总结这个例子,我们看到 - 即使你“追加”5它也许永远不会被使用 - 因此“附加”将永远不会发生,这比我们有一个便宜的“追加”更有效率那件事发生了。

(注意,这种懒惰会引入一些可能导致麻烦的内存开销,请参阅Lazy Evaluation - Space Leak以获取更多信息。)

替代

正如其他人已经说过的那样 - 可以选择使用好的旧列表。

  1. 如果经常附加但是将列表用作“累加器” - 在返回之前预先挂起并反转列表可能是个好主意。所有O(1)次操作均为(:),反之为O(n) - 与O(n)步骤的(++)相比。
  2. 使用不同的数据结构:

    • 差异列表:,您可以在Learn you a haskell for great good引用其中的内容:
        

      等同于[1,2,3]的列表的差异列表将是函数\xs -> [1,2,3] ++ xs。普通的空列表是[],而空的差异列表是函数\xs -> [] ++ xs

    • 来自Data.Sequence

      Sequence 支持高效的prepend / append - 操作并查找边缘元素。< / p>

      $ stack ghci --package containers
      Prelude> import Data.Sequence
      Prelude Data.Sequence> let a = fromList [1..4] :: Seq Int
      Prelude Data.Sequence> (a |> 5)
      fromList [1,2,3,4,5]
      Prelude Data.Sequence> 0 <| (a |> 5)
      fromList [0,1,2,3,4,5]
      
    • Set - 不像列表那样排序,但仍然支持高效(O(log n))插入,但只允许进行唯一插入。

      $ stack ghci --package containers
      Prelude> import Data.Set
      Prelude Data.Set> let a = fromList [1..4] :: Set Int
      Prelude Data.Set> insert 5 a
      fromList [1,2,3,4,5]
      Prelude Data.Set> insert 4 a
      fromList [1,2,3,4]