【问题标题】:Add element to the end of a list without going through the whole list (haskell)将元素添加到列表末尾而不遍历整个列表(haskell)
【发布时间】:2017-08-01 22:13:55
【问题描述】:

Haskell 中有没有一种方法可以在列表末尾添加一个元素而不必遍历整个列表?

如果我有列表 [1,2,3,4] 并想在末尾添加 5,例如:[1,2,3,4,5]

据我了解,++ 运算符将不得不遍历整个列表,这是非常低效的。

谢谢!

【问题讨论】:

  • 那么..也许你会告诉我们真正的问题?也许不用在列表末尾追加一个元素就可以解决?
  • “不附加元素”通常意味着使用不同的数据结构或算法。列表很容易,但在其他结构更合适时经常被滥用。
  • 有时反向构建一个列表(带有前置)会很有用,然后返回reverse

标签: haskell


【解决方案1】:

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

在开始解释之前,我想提醒您 Donald Knuth 和他所说的万恶之源是过早优化。因此,请尝试编写一个正确的程序并在以后担心性能问题 - 当您遇到瓶颈时(请确保 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-bindings 工作很懒惰并且没有打印任何东西(还)。我们甚至可以进一步检查 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

    • Sequence:来自Data.Sequence,它支持高效的prepend/append-操作和在边缘查找元素。

      $ 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]
      

【讨论】:

  • 不错的答案,但 Set Int 仅包含独特的元素,因此不是一个好的列表替代方案。
  • 请谨慎使用 Knuth 的名言。由于附加到列表的末尾不仅开销慢而且复杂性-慢,避免这种情况我永远不会称之为过早优化。我可能会称它为 YAGNI,实际上我自己也经常使用 ++,但这可能会被视为总是一个 hack
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 2019-04-06
  • 1970-01-01
相关资源
最近更新 更多