纯功能并发跳过列表

时间:2010-08-15 22:35:03

标签: scala haskell f# clojure purely-functional

Skip lists(Pugh,1990)提供了具有对数时间操作的排序词典,如搜索树,但skip lists are much more amenable to concurrent updates

是否有可能创建一个高效的纯功能并发跳过列表?如果没有,是否有可能创建任何类型的高效纯函数并发排序字典?

4 个答案:

答案 0 :(得分:38)

跳过列表的属性使它们适用于并发更新(即大多数加法和减法是本地的)也会使它们对不可变性不利(即列表中的许多早期项目最终指向后面的项目,并且必须改变。)

具体来说,跳过列表包含如下结构:

NODE1 ---------------------> NODE2 ---------...
  |                           |
  V                           V
NODE1a --> NODE1b ---------> NODE2a --> NODE2b --> NODE2c --- ...

现在,如果您有更新,例如删除NODE2bNODE1b,您可以在本地处理:您只需将2a指向2c分别是1a2a,你已经完成了。不幸的是,因为叶节点都指向一个节点,所以它不是一个功能(不可变)更新的好结构。

因此,树形结构更适合不变性(因为损坏总是局部受限 - 只是你关心的节点及其直接父母通过树根)。

并发更新不适用于不可变数据结构。如果您考虑一下,任何功能性解决方案都会将A更新为f(A)。如果您需要两个更新,一个由f提供,另一个由g提供,您几乎必须执行f(g(A))g(f(A)),或者您必须拦截请求和创建一个可以一次性应用的新操作h = f,g(或者你必须做其他各种非常聪明的事情)。

但是,并发读取对于不可变数据结构非常有效,因为您可以保证不会发生状态更改。如果你不认为你可以在任何其他写操作中断之前有一个读/写循环,那么你永远不必锁定读。

因此,写入繁重的数据结构可能更好地实现(并且像跳过列表,你只需要在本地锁定),而读取繁重的数据结构可能更好地实现不可变(树更多的地方)自然数据结构)。

答案 1 :(得分:16)

Andrew McKinlay的解决方案是真正的"真正的"这里有一个真正的跳过列表的功能解决方案,但它有一个缺点。你支付对数时间来访问一个元素,但现在突变超出head元素变得毫无希望。你想要的答案被埋没在无数的路径副本中!

我们可以做得更好吗?

问题的一部分是从-infinity到你的项目有多条路径。

但是如果你仔细考虑搜索跳过列表的算法,你就不会使用这个事实。

我们可以认为树中的每个节点都有一个首选链接,它是左边最顶层的链接,在某种意义上可以被认为是拥有'那个条目。

现在我们可以考虑手指'手指的概念。数据结构,这是一种功能技术,使您可以专注于一个特定的元素,并提供一个回到根的路径。

现在我们可以从一个简单的跳过列表开始

-inf-------------------> 16
-inf ------> 8 --------> 16
-inf -> 4 -> 8 -> 12 --> 16

按级别展开:

-inf-------------------> 16
  |                       |
  v                       v
-inf ------> 8 --------> 16
  |          |            |
  v          v            v
-inf -> 4 -> 8 -> 12 --> 16

删除除了首选指针之外的所有指针:

-inf-------------------> 16
  |                       |
  v                       v
-inf ------> 8           16
  |          |            |
  v          v            v
-inf -> 4    8 -> 12     16

然后你可以移动手指'通过跟踪你必须翻转的所有指针到达位置8。

-inf ------------------> 16
   ^                      |
   |                      v
-inf <------ 8           16
   |         |            |
   v         v            v
-inf -> 4    8 -> 12     16

从那里可以删除8,将手指推到其他地方,然后你可以用手指继续浏览结构。

以这种方式看,我们可以看到跳过列表中的特权路径形成了生成树!

如果您在树中只有特权指针并使用&#34;瘦节点&#34;那么用手指移动1步是O(1)操作。像这样。如果您使用胖节点,那么左/右手指移动可能会更加昂贵。

所有操作都保持为O(log n),您可以像往常一样使用随机跳过列表结构或确定性结构。

那就是说,当我们将跳转列表分解为首选路径的概念时,我们得到一个跳过列表只是一个树,其中包含一些冗余的非首选链接,我们不需要插入/搜索/删除,使得从右上角开始的每条路径的长度都是O(log n),概率很高或者保证取决于您的更新策略。

即使没有手指,您也可以使用此表单在树中维护每次插入/删除/更新的O(log n)预期时间。

现在,你问题中没有意义的关键词是&#34;并发&#34;。纯功能数据结构没有就地突变的概念。你总是会产生新的东西。在某种意义上,并发功能更新很容易。每个人都有自己的答案!他们只是无法看到对方&#39;

答案 2 :(得分:6)

不是跳过列表,但似乎与问题描述相符:Clojure的持久红黑树(参见PersistentTreeMap.java)。来源包含此通知:

/**
 * Persistent Red Black Tree
 * Note that instances of this class are constant values
 * i.e. add/remove etc return new values
 * <p/>
 * See Okasaki, Kahrs, Larsen et al
 */

这些树维护元素的顺序,并且在Rich Hickey使用单词的意义上是“持久的”(不可变并且能够在构造更新版本时保持其性能保证)。

如果您想要使用它们,可以使用函数sorted-map在Clojure代码中构造实例。

答案 3 :(得分:4)

如果你只需要在跳过列表的前面,那么应该可以创建一个持久的不可变版本。

这种跳过列表的优点是“随机”访问。例如您可以比常规单链表更快地访问第n个元素。