【发布时间】:2011-07-30 17:39:00
【问题描述】:
作为一个宠物项目,我试图在 Java 中实现一个不可变的列表数据结构,同时尽可能减少副本;我知道 Google 收藏,但这不是我所追求的,因为列表操作只会返回旧列表的新副本。
我想出了两种不同的方法来解决这个问题;两者都基于双向链表,如下所示:
[head: element1] <--> [element2] <--> [tail: element3]
所以每个列表都由元组{head, tail}组成。
首先,让我们研究将元素附加或前置到列表A 的简单情况,从而生成列表B:
A: [head: element1] <--> [element2] <--> [tail: element3]
B: [head: element0] <--> [element1] <--> [element2] <--> [tail: element3]
这是 O(1)。由于对列表的迭代只发生在之间,A 不会知道任何关于添加到B 的新元素。
当我们尝试在列表中插入或删除任意元素时会变得很有趣。
索引元素方法
每个列表都有一个从 0 开始的唯一顺序 id。每个元素都有一个 {prev, next} 指针数组,对应于列表 id:
[element1] <--> [element2] <--> [element3] <--> [element4]
A: [0] <---------> [0] <---------> [0] <---------> [0]
B: [0] <---------> [1] <-------------------------> [1]
C: ...
因此,当从 id = 0 的列表 A 中删除 element3 时,element2 和 element4 的 id = 1(列表 B)的 prev 或 next 指针分别是更改以反映请求操作的结果; element1 保持不变。当迭代索引为x 的列表时,为了获得正确的prev 或next 指针,max(elementIdCount, x) 用于计算正确的索引(对于element1 为0,对于@987654343 为1 @如果我们迭代 B id = 1,例如)。
添加或替换元素以相同的方式完成。这也是 O(1),除非需要调整元素 id 数组的大小,这应该很少发生。
最大的问题当然是垃圾回收——一旦一个元素被添加到一个列表中,它永远不会被释放,直到所有对原始列表的修改版本的引用都被释放。这可以通过每 10 次修改复制整个列表来解决。
这种列表特别适合这样的代码结构:
while (...)
list = list.addElement(...);
因为在任何给定时间只保留一个对列表的引用。
迭代器方法
另一种方法是滥用迭代器以使结果列表看起来像预期的修改版本;因此每个修改后的不可变列表都包含对其“源”列表的引用和一个附加元组{operation, element, position},如下所示:
A: [head: element1] <--> [element2] <--> [tail: element3]
B: source: A, {add, element_to_add, 1}
B 的迭代器然后调用它的源列表迭代器(在本例中为 A 的),除非它遇到已修改(添加、删除或替换)的元素,在这种情况下它返回该元素然后再次使用源迭代器。
这里明显的缺点是嵌套迭代器的深度随着列表的每个修改版本而增长。这意味着不时地制作原始副本也是必要的。
有人对如何改进有任何建议吗?此外,任何指向 60 年代发明的任何可能有用的数据结构的指针都非常受欢迎:)
【问题讨论】:
-
如何将两次(不同)元素附加到 A 在您的双向链表中起作用?从 A 创建 C 时,您会破坏 B。
-
@Paŭlo Ebermann - 这确实是一个问题,即使你不破坏 B 而是从 A -> B 到 C 的变化。我认为这可以通过不检查元素来解决sourceListId 和 x 之间的索引...
标签: java algorithm functional-programming linked-list immutability