Scala 用谓词拆分迭代器
我需要一个可以将Scala 用谓词拆分迭代器,scala,iterator,Scala,Iterator,我需要一个可以将迭代器[Char]拆分为行的方法(由\n和\r分隔) 为此,我编写了一个通用方法,它获取一个迭代器和一个谓词,并在每次谓词为true时分割迭代器。 这类似于span,但将在每次谓词为true时分割,而不仅仅是第一次 这是我的实现: def iterativeSplit[T](iterO: Iterator[T])(breakOn: T => Boolean): Iterator[List[T]] = new Iterator[List[T]] { private v
迭代器[Char]
拆分为行的方法(由\n
和\r
分隔)
为此,我编写了一个通用方法,它获取一个迭代器和一个谓词,并在每次谓词为true时分割迭代器。
这类似于span
,但将在每次谓词为true时分割,而不仅仅是第一次
这是我的实现:
def iterativeSplit[T](iterO: Iterator[T])(breakOn: T => Boolean): Iterator[List[T]] =
new Iterator[List[T]] {
private var iter = iterO
def hasNext = iter.hasNext
def next = {
val (i1,i2) = iter.span(el => !breakOn(el))
val cur = i1.toList
iter = i2.dropWhile(breakOn)
cur
}
}.withFilter(l => l.nonEmpty)
它在小的输入上运行良好,但在大的输入上运行非常慢,有时会出现堆栈溢出异常
以下是重新创建问题的代码:
val iter = ("aaaaaaaaabbbbbbbbbbbccccccccccccc\r\n" * 10000).iterator
iterativeSplit(iter)(c => c == '\r' || c == '\n').length
运行期间的堆栈跟踪为:
...
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
at scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:615)
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
at scala.collection.Iterator$$anon$18.hasNext(Iterator.scala:591)
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
at scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:615)
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
at scala.collection.Iterator$$anon$18.hasNext(Iterator.scala:591)
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
at scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:615)
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
at scala.collection.Iterator$$anon$18.hasNext(Iterator.scala:591)
at scala.collection.Iterator$$anon$1.hasNext(Iterator.scala:847)
...
查看源代码(我使用的是scala 2.10.4)
第591行是span
中第二个迭代器的hasNext
,第651行是dropWhile
中迭代器的hasNext
我猜这两个迭代器使用得不正确,但我不明白为什么可以将代码简化为以下内容,这似乎解决了问题:
def iterativeSplit2[T](iter: Iterator[T])(breakOn: T => Boolean): Iterator[List[T]] =
new Iterator[List[T]] {
def hasNext = iter.hasNext
def next = {
val cur = iter.takeWhile(!breakOn(_)).toList
iter.dropWhile(breakOn)
cur
}
}.withFilter(l => l.nonEmpty)
与其使用span
(因此每次调用next
时都需要更换iter
),只需在原始iter
上使用takeWhile
和dropWhile
。那么就不需要var
我认为最初堆栈溢出的原因是重复调用span
创建了一个长链的Iterator
s,每个hasNext
方法调用其父Iterator
的hasNext
。如果查看for迭代器
,可以看到每个span
都会创建新的迭代器,将对hasNext
的调用转发到原始迭代器(通过buffereditor
,这会进一步增加调用堆栈)
更新在咨询了之后,尽管我的上述解决方案似乎有效,但不建议使用它-具体请参见:
特别重要的是要注意,除非另有说明,
在调用迭代器上的方法后,决不能使用迭代器。
[…]使用旧迭代器是未定义的,可能会发生更改,并且可能会导致对新迭代器的更改
适用于takeWhile
和dropWhile
(和span
),但不适用于next
或hasNext
可以像在原始解决方案中一样使用span
,但可以使用流而不是迭代器和递归:
def split3[T](s: Stream[T])(breakOn: T => Boolean): Stream[List[T]] = s match {
case Stream.Empty => Stream.empty
case s => {
val (a, b) = s.span(!breakOn(_))
a.toList #:: split3(b.dropWhile(breakOn))(breakOn)
}
}
但是表演非常糟糕。我相信一定有更好的方法
更新2:这里有一个非常重要的解决方案,它具有更好的性能:
import scala.collection.mutable.ListBuffer
def iterativeSplit4[T](iter: Iterator[T])(breakOn: T => Boolean): Iterator[List[T]] =
new Iterator[List[T]] {
val word = new ListBuffer[T]
def hasNext() = iter.hasNext
def next = {
var looking = true
while (looking) {
val c = iter.next
if (breakOn(c)) looking = false
else word += c
}
val w = word.toList
word.clear()
w
}
}.withFilter(_.nonEmpty)
做得好,谢谢。现在也不需要使用
dropWhile
,因为过滤器会处理断开的元素。我已经更新了我的答案,因为我意识到我的原始解决方案使用了文档中强烈反对的技术。这很好,谢谢!为了防止在拆分迭代器上使用toList
时出错,我添加了while(looking&&iter.hasNext)