【发布时间】:2011-07-05 01:05:03
【问题描述】:
我一直认为链表的好处是您可以添加或删除项目(尤其是从末尾),而无需复制大量元素,这要归功于指针的美感。
然而,Scala 的List 是不可变的(至少在默认情况下)。拥有一个不可变链表有什么好处(因为有明确的缺点,例如不是 O(1) 元素访问。)
谢谢!
【问题讨论】:
标签: list scala linked-list immutability
我一直认为链表的好处是您可以添加或删除项目(尤其是从末尾),而无需复制大量元素,这要归功于指针的美感。
然而,Scala 的List 是不可变的(至少在默认情况下)。拥有一个不可变链表有什么好处(因为有明确的缺点,例如不是 O(1) 元素访问。)
谢谢!
【问题讨论】:
标签: list scala linked-list immutability
然而,Scala 的 List 是不可变的(至少默认情况下是这样)。拥有不可变链表有什么好处
我在这里聚会有点晚了,但我觉得有一个重要的观点没有人提出:
除了 Rex Kerr 所说的之外,值得指出的是,通过添加到不可变单链表的前面来构建新列表是一个常数时间操作。您只需创建一个指向现有列表的新节点。由于列表是不可变的,因此您知道新的尾部永远不会改变,并且您不需要复制任何数据。
我怀疑,您可以构建新的、更大但仍然不可变的列表,并使用恒定的时间操作,并且内存占用很小(仅一个节点的成本),这是数据的很大一部分原因选择了结构。
【讨论】:
虽然我几乎不知道如何实现 scala。不过,应该避免使用 LinkedLists。
它们在当前硬件上效率低下,因为遍历它们每个节点实际上会导致缓存未命中。 缓存未命中是当今控制性能的因素。
【讨论】:
我认为主要原因是链表最强大的用途之一是头/尾拆分。有很多递归算法看起来像这样:
def listlen[A](xs: List[A], already: Int = 0): Int = xs match {
case Nil => already
case x :: rest => listlen(rest, already+1)
}
当然,列表已经知道如何获取它们的长度;这只是一个例子。关键是你把头拿掉,然后用尾巴做其他事情——很多有用的事情都是这样工作的。
由于列表是不可变的,我们可以做其他事情——我们可以花尽可能多的时间来评估列表的其余部分!我们不必一口气完成;我们确信它永远不会改变。如果列表是可变的,则情况并非如此——要么我们需要锁定列表,防止其他人看到它,要么我们需要制作整个事物的防御性副本,以防有人交换它。
现在,如果您真的想要一个可变列表,mutable.LinkedList 具有您所说的良好插入属性。但这并不能让您获得不可变列表所提供的优雅递归。
(请注意,您可以使用不可变的数组支持结构来完成其中的一些操作,但是包含每个元素的集合的可能好处是您不需要保存或复制整个数组,因为您需要一个最后的几个元素;如果列表中的早期元素不再被指向,它们可以被垃圾回收。)
【讨论】:
List 是一个不错的选择。对于其他时候(例如,如果您必须在两端添加),您最好选择其他东西。
cons 和 tail(与添加到堆栈和从堆栈中删除一样)很难有效地使用不可变数组支持的结构。
scala.List 是scala.collection.immutable.List 的别名。它只是众多具体的集合实现之一。
如果您有 Java 背景,请将java.util.List 等同于scala.collection.mutable.Buffer,而不是scala.List。
您可以在Scala 2.8 Collections Overview. 中了解馆藏库的结构概览。在这里您会发现许多其他具体实现(可变和不可变)。
【讨论】:
结构共享。如果您对列表执行高阶函数(map、fold 等),您将返回一个共享前一个列表指针的列表的新实例。
Daniel Spiewak 上周在 NE Scala 上做了一个关于函数式数据结构的精彩演讲。见这里:http://www.nescala.org/2011/
【讨论】:
java.util.ArrayList 之类的东西来做到这一点?
java.util.ArrayList 从来都不是一成不变的。您可以获得它的不可修改的视图,但原始参考仍然能够更改它,因此库不能依赖它永远不会改变。