【问题标题】:What does the syntax ::[T] =_ means in Scala?语法 ::[T] =_ 在 Scala 中是什么意思?
【发布时间】:2018-03-21 02:58:01
【问题描述】:

我试图了解 Listbuffer 是如何实现的。

@SerialVersionUID(3419063961353022662L)
final class ListBuffer[A]
      extends AbstractBuffer[A]
         with Buffer[A]
         with GenericTraversableTemplate[A, ListBuffer]
         with BufferLike[A, ListBuffer[A]]
         with ReusableBuilder[A, List[A]]
         with SeqForwarder[A]
         with Serializable
{
  override def companion: GenericCompanion[ListBuffer] = ListBuffer

  import scala.collection.Traversable
  import scala.collection.immutable.ListSerializeEnd

  /** Expected invariants:
   *  If start.isEmpty, last0 == null
   *  If start.nonEmpty, last0 != null
   *  If len == 0, start.isEmpty
   *  If len > 0, start.nonEmpty
   */
  private var start: List[A] = Nil
  private var last0: ::[A] = _
  private var exported: Boolean = false
  private var len = 0
  ...
}

第一个问题是,我无法理解 private var last0 的语法 ::[A] = _。虽然它看起来像一个类型,但 ::[A] 是什么意思,为什么它需要 RHS 上的 _?看起来语法是用来表示List的最后一个元素的,但是它是怎么工作的呢?

第二个问题与 ListBuffer 使用 Var 尾部而不是 Val 尾部(即使它是不可变的)来加快加法速度有关。 上面的 += 函数如何帮助它实现更快的加法???

def += (x: A): this.type = {
  if (exported) copy()
  if (isEmpty) {
    last0 = new :: (x, Nil)
    start = last0
  } else {
    val last1 = last0
    last0 = new :: (x, Nil)
    last1.tl = last0
  }
  len += 1
 this
}

最后一个问题是关于 ListBuffer 的添加是如何工作的。在空的 ListBuffer 被第一个元素填充之后, start 似乎一直指向 last0 ,并且 start 在它再次变为空之前不会改变。而在第一个之后的添加,将列表的尾部更改为由以下代码行创建的新列表。

val last1 = last0 
last0 = new :: (x, Nil)
last1.tl = last0

看起来原始列表last0的尾部被更改为包含x的新last0,但似乎这段代码并没有真正扩展开头指向的列表。它是如何工作的..?似乎将 last0 分配给 last1.tl 会将 List(x) 附加到 start 指向的 List 中,但我不明白它是如何工作的。

如果可能,您能否解释一下 start、last0、last1 将如何随着输入序列 += 1、+= 2、+= 3 等发生变化?

【问题讨论】:

  • :: is a class 是一种类型。
  • @jwvh,谢谢!那么作业的 RHS 中的“_”是什么意思?
  • RHS _ 用于将var 初始化为默认值。默认值根据其类型确定。 Int 的默认值为 0:: 实例的默认值为null。这只是many uses of the underscore 的又一个例子。
  • @jwvh。啊哈!谢谢,那么 += 操作在 ListBuffer 中是如何工作的?似乎 += 操作从 new ::(x, Nil) 创建了一个新的 List(x) 并将其分配给 start 指向的 List 的 tl 。似乎 last1.tl 只是一个 var List[T] 并且永远不会将新元素附加到 start 指向的 List 中。
  • 您在这里有大约 5 个问题。如果你有 5 个问题,请提出 5 个问题,而不是填塞然后变成一个。特别是,因为您的大部分问题已经在Stack Overflow 上被多次询问和回答,因此重复出现是题外话。

标签: scala


【解决方案1】:

:: 可以是 methodclass。在示例中,它指的是参数化类,它将一个新元素添加到List 的开头。

scala> var x: ::[Int] = _
x: ::[Int] = null

scala> x = new ::[Int](1, List(2, 3))
x: ::[Int] = List(1, 2, 3)

虽然List 是不可变的,但var x 是可变的(ListBuffer 是可变的)。它以空值开始,然后设置为新列表。

最后,+= 方法将一个元素附加到列表中,+=: 前置,两者都是恒定时间操作(与 List 不同,它具有恒定的前置时间和 O(n**2)追加。

如果你看一下::的定义,你会发现传递给构造函数的tl是一个私有的var

final case class ::[B](private var hd: B, private[scala] var tl: List[B]) extends List[B] {
  override def head : B = hd
  override def tail : List[B] = tl
  override def isEmpty: Boolean = false

这意味着它只能在包scala(它所属的地方)内修改。而ListBuffer 属于子包scala.collection.mutable,因此可以访问tl。这就是您在 last1.tl 行中的 else 块中看到的内容。

所以,关于添加一个新元素:

假设x = 42

// start is empty:
last0 = new ::(42, Nil) // List(42)
start = List(42)

现在,我们要添加附加 43:

// start is not empty
val last1 = List(42) // last1 is the same object as last0
last0 = new ::(43, Nil) // temporarily set last0 to List(43) //
last1.tl = List(43) // as last1 is the same as last0
                    // last0.tl = List(43)
                    // last0 points to the new last element
                    // start holds all elements in the buffer

“Scala 编程”(the first edition is free online) 第 22 章解释了 List 是如何构造的以及如何使用 ListBuffer

【讨论】:

  • 感谢您的帮助,但 += 是如何工作的?似乎将 List(x) 分配给尾部并没有扩展原始列表......您能否检查问题的第二部分以及我在评论中添加的部分信息?
  • 我编辑了我的答案。顺便说一句,你有 3 个问题合二为一。如果您将它们分开,可能会有更多人能够单独回答它们,而不必等待可以回答所有问题的人。
  • 感谢添加和详细信息,但我很好奇如果再添加一个会发生什么。似乎每次都将新创建的 List(x) 分配给 last1.tl,而不是在前一个 last1.tl 之后连接。如果每次分配都更改它,那么该操作如何扩展列表?
  • 您可以尝试从响应中遵循相同的逻辑。添加另一个元素将再次落入else 块中。 last1 将设置为当前的last0,即具有 2 个元素的 List... 我相信我完全回答了您的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-02
  • 2014-01-16
  • 1970-01-01
相关资源
最近更新 更多