如何克隆一个迭代器?

17

假设我有一个迭代器:

val it = List("a","b","c").iterator

我想要它的一个副本; 我的代码是:

val it2 = it.toList.iterator

这是正确的,但似乎不太好。有没有其他API可以做到这一点?


为什么?一旦你克隆了它,原始迭代器将被消耗并且无用,这样就打败了克隆的初衷... - Kevin Wright
2
@Kevin,这并不一定是这样的,对吧?抽象地说,似乎有可能有一个操作,可以给我一个迭代器,它将返回与源迭代器相同的序列 - 当然,状态问题可能会使所有迭代器都无法实现。它似乎并不固有地需要消耗源迭代器。 - The Archetypal Paul
val 切换到 def 会使每次引用符号(在上面的示例中命名为 it)时都获得一个新的迭代器。在许多情况下,这种方式可能会感觉更简单。 - matanster
2个回答

19

你要查找的方法是 duplicate

scala> val it = List("a","b","c").iterator
it: Iterator[java.lang.String] = non-empty iterator

scala> val (it1,it2) = it.duplicate
it1: Iterator[java.lang.String] = non-empty iterator
it2: Iterator[java.lang.String] = non-empty iterator

scala> it1.length
res11: Int = 3

scala> it2.mkString
res12: String = abc

4
警告:这里使用可变的“队列”来缓存迭代器之间的差异,可能导致意外的内存问题。此外,“next”和“hasNext”对于新的迭代器是同步的,这使它们比普通迭代器慢得多。 - Daniel C. Sobral
3
另一个警告:虽然 it1it2 可以独立使用,但是调用 it.next 会同时推进这两个副本!而且,这些副本从 it 的当前元素开始,而不是列表的开头。不幸的是,duplicate 的文档说明特别不好。 - Raphael
2
已授予警告。它们很重要,但如果您仔细考虑您所请求的内容,则它们也是“不言自明”的:当然,如果您有一个迭代器并且您想要两个不同步的迭代器,那么您将需要某种存储,您只能从您所在的位置开始,而不能返回到丢失的开头,并且如果您不只是复制所有东西,那么您将需要同步以找出哪些迭代器都留下了什么以及什么可以抓取。 - Rex Kerr
1
@Rex Kerr,我想问一下,我能否将重复的迭代器之一重新分配给原始迭代器,并继续从原始迭代器上进行使用? val(it1,it2)= it.duplicate it = it1 println( it.length)println(it2.mkString) - jaywalker
2
@HaseebJaved - 为什么不呢?你只是在说it现在指的是it1所指的相同内容。 - Rex Kerr

10

警告:从Scala 2.9.0开始,使用这种方法后,原始迭代器至少会变为空。您可以val ls = it.toList; val it1 = ls.iterator; val it2 = ls.iterator来获得两个副本。或者使用duplicate(对于非列表也适用)。

Rex的回答是标准的,但实际上您的原始解决方案对于scala.collection.immutable.List而言效率最高。

可以使用该机制轻松复制列表迭代器而几乎不会增加额外开销。可以通过快速查看scala.collection.immutable.LinearSeq中iterator()的实现方式进行确认,特别是toList方法的定义,它只是返回支持Seq的_.toList,如果它是一个List(正如您的情况),则是identity。

在调查您的问题之前,我不知道列表迭代器具有这种属性,我非常感谢这些信息......这意味着许多"列表削尖"算法可以使用迭代器作为削尖器,在Scala不可变列表上高效实现。


我希望除了问题,我也能够收藏评论,因为你提出了一个非常好的用例/观点。 - Dylan Lacey
相关帖子:http://stackoverflow.com/questions/16380592/spec2-breaks-my-test-data-due-to-the-way-it-works-with-iterator - ses

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接