scala视图过滤器不懒惰?

时间:2016-03-01 10:06:54

标签: scala scala-collections

在尝试理解流,迭代器和集合视图之间的差异时,我偶然发现了以下奇怪的行为。

这里的代码(map和filter只是打印输入并将其转发):

object ArrayViewTest {
  def main(args: Array[String]) {
    val array = Array.range(1,10)

    print("stream-map-head: ")
    array.toStream.map(x => {print(x); x}).head

    print("\nstream-filter-head: ")
    array.toStream.filter(x => {print(x); true}).head

    print("\niterator-map-head: ")
    array.iterator.map(x => {print(x); x}).take(1).toArray

    print("\niterator-filter-head: ")
    array.iterator.filter(x => {print(x); true}).take(1).toArray

    print("\nview-map-head: ")
    array.view.map(x => {print(x); x}).head

    print("\nview-filter-head: ")
    array.view.filter(x => {print(x); true}).head
  }
}

及其输出:

stream-map-head: 1
stream-filter-head: 1
iterator-map-head: 1
iterator-filter-head: 1
view-map-head: 1
view-filter-head: 123456789    // <------ WHY ?

为什么过滤器调用视图会处理整个数组? 我希望过滤器的评估只能通过调用head来驱动一次,就像在所有其他情况下一样,特别是在视图中使用map时。

我错过了什么见解?

(评论的小问题,为什么迭代器上没有头?)

修改 scala.Array.range(1,10)scala.collection.mutable.ArraySeq.range(1,10)scala.collection.mutable.ArrayBuffer.range(1,10)实现了同样的奇怪行为(此处为scala.collection.mutable.StringBuilder.newBuilder.append("123456789"))。 但是,对于所有其他可变集合以及所有不可变集合,视图上的过滤器按预期工作并输出1

2 个答案:

答案 0 :(得分:3)

我认为必须这样做Array是一个可变的索引序列。它的视图也是一个可变的集合:)所以当它创建一个视图时,它会创建一个在原始集合和过滤集合之间进行映射的索引。并且懒惰地创建这个索引并没有多大意义,因为当有人请求第i个元素时,无论如何都可以遍历整个源数组。从某种意义上说,在您调用head之前不会创建此索引,这仍然是懒惰的。仍然没有在scala文档中明确说明,它看起来像一个乍一看的bug。

对于迷你方面的问题,我认为迭代器上head的问题是人们期望head是纯函数,即你应该能够调用它n次并且它应该返回每次都是相同的结果。迭代器本质上是可变的数据结构,通过契约只能遍历一次。这可以通过缓存迭代器的第一个元素来克服,但我发现这非常令人困惑。

答案 1 :(得分:3)

似乎head使用isEmpty

trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] { self =>
...
override /*IterableLike*/
def head: A = if (isEmpty) super.head else this(0)

isEmpty使用length

trait IndexedSeqOptimized[+A, +Repr] extends Any with IndexedSeqLike[A, Repr] { self =>
  ...
  override /*IterableLike*/
  def isEmpty: Boolean = { length == 0 }

length的实现是在Filtered中使用的,它遍历整个数组

trait Filtered extends super.Filtered with Transformed[A] {
  protected[this] lazy val index = {
    var len = 0
    val arr = new Array[Int](self.length)
    for (i <- 0 until self.length)
      if (pred(self(i))) {
        arr(len) = i
        len += 1
      }
    arr take len
  }
  def length = index.length
  def apply(idx: Int) = self(index(idx))
}

Filtered特征仅在调用filter

时使用
protected override def newFiltered(p: A => Boolean): Transformed[A] =
 new { val pred = p } with AbstractTransformed[A] with Filtered

这就是使用filter而不使用map

时的原因