微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Scala的mutable.ListBuffer似乎使用了List的尾部函数,但它被记录为具有线性复杂性?

scala的2.12.8 current documentation开始,List的尾部是常量,ListBuffer的尾部是线性的.但是,查看 source code,看起来尾部函数没有覆盖,并且在大多数用例中(例如删除head元素),List的尾部函数被显式调用.由于ListBuffer似乎只是一个带有长度var和指向最后一个元素的指针的List包装器,为什么它是线性的?

我对两种方法进行了计时,看起来List的尾部是常量,而ListBuffer的尾部确实是线性的:

import scala.collection.mutable
import scala.collection.immutable

val immutableList: immutable.List[Int] = (1 to 10000).toList
val mutableListBuffer: mutable.ListBuffer[Int] = mutable.ListBuffer.empty[Int] ++= (1 to 10000).toList

// warm-up
(1 to 100000).foreach(_ => immutableList.tail)
(1 to 100000).foreach(_ => mutableListBuffer.tail)

// Measure
val start = System.nanoTime()
(1 to 1000).foreach(_ => immutableList.tail)
val middle = System.nanoTime()
(1 to 1000).foreach(_ => mutableListBuffer.tail)
val stop = System.nanoTime()

println((middle - start) / 1000)
println((stop - middle) / 1000)

结果如:

1076
86010

但是,如果使用诸如remove(0)之类的函数使用List的尾部,则它是常量,具有以下结果:

1350
1724

我希望线性复杂性来自构建一个全新的列表返回,但由于内部结构是一个List,为什么不返回List的尾部呢?

解决方法

ListBuffer不扩展List,并且它不覆盖tail的事实并不意味着它使用List#tail.如果你看一下 tail on ListBuffer文档的定义类部分,你会看到它来自TraversableLike,它的定义如下:

override def tail: Repr = {
  if (isEmpty) throw new UnsupportedOperationException("empty.tail")
  drop(1)
}

如果你看一下drop,你会发现它使用一个构建器来构造一个包含除第一个元素之外的所有元素的新集合,这解释了为什么它是线性的.

正如talex在上面的注释中暗示的那样,ListBuffer#tail必须返回一个新的集合,因为原始缓冲区可以被修改,标准库设计者已经决定你不希望那些修改反映在你从尾部获得的结果中.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐