【问题标题】:Immutable DataStructures In ScalaScala 中的不可变数据结构
【发布时间】:2014-11-19 23:25:22
【问题描述】:

我们知道 Scala 支持不可变数据结构。即每次更新列表时,它都会在堆中创建一个新对象和引用。

例子

val xs:List[Int] = List.apply(22)
val newList = xs ++ (33)

因此,当我将第二个元素附加到列表时,它将创建一个包含 22 和 33 的新列表。这与 Java 中不可变字符串的工作方式完全相同。 所以问题是每次我在列表中添加一个元素时,每次都会创建一个新对象。这对我来说看起来效率不高。 处理这个时是否使用了一些特殊的数据结构,如持久数据结构。有人知道吗?

【问题讨论】:

  • 有充分的理由支持不变性。这些原因的一个很好的总结可以在 Effective Java,第 15 条中找到。作为性能问题的解决方案,它建议您要么依赖可变数据结构的有效内部实现,要么使用一个可变的对应物(例如,ListBuffer)。
  • 列表仅在前置时有效。如果要追加,请使用 IndexedSeq,它使用 Vector 数据结构。这对于追加和前置都是相当有效的,而且是不可变的。

标签: scala functional-programming


【解决方案1】:

附加到列表具有 O(n) 复杂度并且效率低下。一种通用的方法是在构建列表时将其添加到列表的前面,最后将其反转。

现在,您关于创建新对象的问题仍然适用于前置。请注意,由于xs 是不可变的,newList 只是指向 xs 以获取前置后的其余数据。

【讨论】:

  • 完全正确,但问题是它可以指向其余数据,直到数据没有更改。例如,我有一个列表为 val list = List(12,13,14) 和我将一个元素说 15 附加到不可变列表,即 (list1 = list + 15) 它不会创建新列表。它将使用前一个链表,指针将指向起始地址,即 12,并将创建一个新节点对于 15.但是,一旦我使用列表引用更改数据,它将创建一个新列表供 list1 使用(即复制以前的数据)并修改列表引用。这称为持久数据结构。
【解决方案2】:

虽然@manojlds 在他的分析中是正确的,但原始帖子询问了每当您执行操作时复制列表节点的效率。

正如@manojlds 所说,构建列表通常需要向后思考,即构建一个列表然后将其反转。在许多其他情况下,列表构建需要“不必要”的复制。

为此,Scala 中有一个名为ListBuffer 的可变数据结构,您可以使用它来构建列表,然后将结果提取为不可变列表:

val xsa = ListBuffer[Int](22)
xsa += 33
val newList = xsa.toList

但是,列表数据结构通常是不可变的,这意味着您可以使用一些非常有用的工具来分析、分解和重新组合列表。许多内置操作利用了不变性。通过扩展,您自己的程序也可以利用这种不变性。

【讨论】:

  • 是的,但问题是我不想在我的程序中引入任何可变状态。变量 xsa 是可变的,任何人都可以修改它的状态,这是某种危险的指示。所以我需要使用 scala.collection.immutable._ 提供的不可变数据结构
  • xsa 不是变量...它的“状态”将始终是ListBuffer。随着列表的建立,列表缓冲区的内容可能会发生变化。然而,它只是在你建立它的时间段内。将其转换为列表后,将无法再更改。不变性的关键在于您封装了可变性。
猜你喜欢
  • 1970-01-01
  • 2012-04-11
  • 2012-01-20
  • 1970-01-01
  • 2015-03-20
  • 2015-04-27
  • 2011-01-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多