Scala Seq.grouped吃了我的迭代器

时间:2012-10-24 10:34:22

标签: java scala iterator

作为一名C#程序员,我对Java / Scala迭代器设计有一个粗略的理解。

我试图(懒惰 - 因为源可能很大)从RecordReader(在某些第三方库中)读取记录。我需要每100条记录做一些额外的工作。

for (group <- reader.iterator.zipWithIndex.grouped(100)) {
  for ((record, i) <- group) {
    println(i + "|" + record.key)
  }
  // ...
}

每次都会反复给我最后一条记录。

如果我不使用grouped,它可以正常工作,我会得到每条记录。我错过了关于延迟流式传输或Java迭代器的事情吗?

2 个答案:

答案 0 :(得分:3)

我认为问题可能是Record.key只返回某个变量的当前值,该变量在迭代器被消耗时发生变异 (而不是让记录在施工时实际捕获关键值)。 一个例子可能会让它更清晰。 首先,让我们使用scala REPL来编写一些显示问题的测试代码:

case class Record( key: Int )
def getRecordIterator: Iterator[Record] = {
  var currentKey: Int = 0
  (1 to 10).iterator.map{ i => 
    currentKey += 1
    new Record( currentKey )
  }
}

然后我们可以尝试迭代而不使用grouped

for ((record, i) <- getRecordIterator.zipWithIndex) {
  println(i + "|" + record)
}

这给了我们(正如预期的那样)

0|Record(1)
1|Record(2)
2|Record(3)
3|Record(4)
4|Record(5)
5|Record(6)
6|Record(7)
7|Record(8)
8|Record(9)
9|Record(10)

然后使用grouped

for (group <- getRecordIterator.zipWithIndex.grouped(3)) {
  for ((record, i) <- group) {
    println(i + "|" + record)
  }
  println("---")
}

给出了:

0|Record(1)
1|Record(2)
2|Record(3)
---
3|Record(4)
4|Record(5)
5|Record(6)
---
6|Record(7)
7|Record(8)
8|Record(9)
---
9|Record(10)
---    

到现在为止,一切都很好。

现在让我们稍微改变Record的定义:

trait Record {
  def key: Int
  override def toString = "Record(" + key + ")"
}
def getRecordIterator: Iterator[Record] = {
  var currentKey: Int = 0
  (1 to 10).iterator.map{ i => 
    currentKey += 1
    new Record{ def key = currentKey }
  }    
}

通过此更改,我们在不使用grouped时仍然会得到相同的结果,但这是我们在使用group时获得的结果:

0|Record(3)
1|Record(3)
2|Record(3)
---
3|Record(6)
4|Record(6)
5|Record(6)
---
6|Record(9)
7|Record(9)
8|Record(9)
---
9|Record(10)
---

问题的根源在于,在迭代器上调用next这一事实会改变Record.get返回的值。 这个问题可以更加轻松地说明:

val it = getRecordIterator
val r1 = it.next
println(r1) // prints "Record(1)" as expected
val r2 = it.next
println(r2) // prints "Record(2)" as expected
println(r1) // this now prints "Record(2)", not "Record(1)" anymore!

答案 1 :(得分:2)

要进行故障排除,请尝试在另一个打印正在进行的内容的迭代器中修饰迭代器:

def wrap[T](i: Iterator[T]) = new Iterator[T] {
  def hasNext = { val b = i.hasNext; println("hasNext => " + b); b }
  def next() = { val n = i.next(); println("next() => " + n); n }
}

val reader = Iterator.from(20).take(10).toList
for (group <- wrap(reader.iterator).zipWithIndex.grouped(5)) {
  for ((v, i) <- group) println("[" + i + "] = " + v)
}

首次实例化迭代器时,在迭代器上调用wrap。这将打印如下:

hasNext => true
hasNext => true
next() => 20
hasNext => true
next() => 21
hasNext => true

这可以帮助您确定迭代器是否表现不佳......例如,如果没有调用hasNext,库可能无法正确处理多次调用next。在这种情况下,您可以修改wrap,以便使迭代器正常运行。还有一件事,从症状来看,感觉你已经在调用分组之前使用了迭代器。所以要格外小心并检查之前是否使用过相同的迭代器引用。