Scala:在可变序列中最快`remove(i:Int)`

时间:2010-12-14 17:03:08

标签: performance scala scala-collections

如果我打算在单线程环境中进行大量的索引删除操作(例如scala.collection.mutable),我应该采用remove(i: Int)包中的哪个实现?最明显的选择ListBuffer表示可能需要线性时间,具体取决于缓冲区大小。是否有一些log(n)的集合甚至是此操作的恒定时间?

3 个答案:

答案 0 :(得分:7)

删除运算符(包括buf remove i)不属于Seq,但它实际上是Bufferscala.mutable特征的一部分。 (见Buffers

请参阅Performance Characteristics上的第一个表格。我猜buf remove i具有与插入相同的特征,它对于ArrayBufferListBuffer都是线性的。 正如Array Buffers中所述,它们在内部使用数组,而Link Buffers使用链接列表(仍然是O(n)用于删除)。

作为替代方案,不可变Vector可能会给你一个有效的恒定时间。

  

向量表示为具有高分支因子的树。每个树节点最多包含32个向量元素或最多包含32个其他树节点。 [...]因此,对于所有合理大小的向量,元素选择涉及最多5个基本数组选择。当我们写出元素访问是“有效的恒定时间”时,这就是我们的意思。

scala> import scala.collection.immutable._
import scala.collection.immutable._

scala> def remove[A](xs: Vector[A], i: Int) = (xs take i) ++ (xs drop (i + 1))
remove: [A](xs: scala.collection.immutable.Vector[A],i: Int)scala.collection.immutable.Vector[A]

scala> val foo = Vector(1, 2, 3, 4, 5)
foo: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5)

scala> remove(foo, 2)
res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 4, 5)

但是,请注意,在数据量非常大之前,具有大量开销的高常量时间可能无法赢得快速线性访问。

答案 1 :(得分:1)

根据您的具体用例,您可以使用LinkedHashMap中的scala.collection.mutable

虽然您无法通过索引删除,但您可以在常量时间内通过唯一键删除,并在迭代时保持确定性排序。

scala> val foo = new scala.collection.mutable.LinkedHashMap[String,String]
foo: scala.collection.mutable.LinkedHashMap[String,String] = Map()

scala> foo += "A" -> "A"
res0: foo.type = Map((A,A))

scala> foo += "B" -> "B"
res1: foo.type = Map((A,A), (B,B))

scala> foo += "C" -> "C"
res2: foo.type = Map((A,A), (B,B), (C,C))

scala> foo -= "B"
res3: foo.type = Map((A,A), (C,C))

答案 2 :(得分:0)

如果最后一个元素是要删除的元素,则Java的ArrayList实际上具有恒定的时间复杂度。查看从其源代码复制的以下代码段,

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

如您所见,如果numMoved等于0,则remove将不会移位并复制该数组。在某些情况下,这可能非常有用。例如,如果您不太在意排序,则要删除元素,可以始终将其与最后一个元素交换,然后从ArrayList中删除最后一个元素,这实际上使{{1 }}一直保持恒定的时间。我曾希望remove会这样做,但不幸的是事实并非如此。