在尝试理解流,迭代器和集合视图之间的差异时,我偶然发现了以下奇怪的行为。
这里的代码(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
。
答案 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