具有线性串联和附加的有效不可变数组支持序列

时间:2017-01-29 06:50:48

标签: arrays algorithm sequence fold

可能大多数算法和数据结构课程都包含一个可扩展的数组缓冲区,其缓冲成本为O(1),用于通过增加基础数组的因子来附加元素。在命令式语言中,它通常是列表的最有效实现。

即使在不可变的情况下,支持数组的序列仍然具有很多优点,但在函数式语言中,反复附加到缓冲区的循环通常被替换为折叠:

//imperative pattern:
val buffer = new ArrayBuffer
for (list <- lists) {
    buffer append list
}

//functional pattern:
foldLeft(Nil)( (acc, list) => list :::acc)(lists)

哪种方法适用于链表,但在天真的实现中,数组的成本为O(n ^ n)。但是,可以调整增长缓冲区的技巧来实现有效的不可变数据结构:

class ArrayList {
    val array :Array
    val firstIndex :Int
    val length :Int
    var ownsPrefix :Boolean
    var ownsSuffix :Boolean
}

因此,虽然ArrayList的每个实例都代表数组firstIndex .. firstIndex+length的常量部分,但保证不会更改该部分中的内容,但可以在多个实例之间共享。 附加的可变标志ownsPrefixownsSuffix定义实例是否可以在第一个/最后一个元素之前更改数组元素的内容(任何结构都不使用数组的相应部分) )。如果是这样,并且有足够的可用空间,则连接操作将简单地将附加/前置列表的元素复制到缓冲区的正确片段,关闭相应的标志,并返回表示数组的组合部分的值,< em>现在拥有缓冲区。

这是一个简单的技巧,可以解决最常用的单个结构重复增长的用例问题,但我还没有看到它实际上是在任何标准或流行的库中实现的。

我的问题是:它是否有名称,是否已经过分析,或者它是否具有高效的开源实现?我已经在scala中实现了它以供我自己使用,并且想开源它,但是不愿意重新发明轮子并且很乐意结合已经为它发明的任何调整和优化。

2 个答案:

答案 0 :(得分:1)

我没有见过这样的事情,但也许手指树也满足了类似的需求。

TurinArrayList:

  • O(n)存储
  • O(1)索引
  • 摊销O(1)追加/前置
  • O(n)连接

手指树:

  • O(n)存储
  • O(log m)索引,其中m是距起点或终点的距离
    例如O(1)检索第一个或最后一个
  • 摊销O(1)追加/前置
  • 摊销O(log n)连接

手指树也有更好的分享。例如,fingerTreefingerTree :+ 1fingerTree :+ 2将(平均)共享大部分存储空间,而不是arrayListarrayList :+ 1,如果我正确理解你对它的描述,arrayList :+ 2将始终复制至少一个完整的数组。

从好的方面来说,你应该有一个更简单的实现,真正的恒定时间随机访问,以及CPU缓存友好的连续内存布局。

答案 1 :(得分:0)

我不确定这个具体的想法,但它与uniqueness typing有关,这是一种在类型系统中指定可以安全地改变值的方法,以及copy-on-write,它使用引用计数推迟复制值的工作以使其变异。