通过谓词拆分迭代器

时间:2015-07-05 09:19:55

标签: scala iterator

我需要一种可以将Iterator[Char]拆分为行的方法(由\n\r分隔)

为此,我编写了一个获取迭代器和谓词的通用方法,并在每次谓词为真时拆分迭代器。 这类似于span,但每次谓词为真时都会拆分,而不仅仅是第一次

这是我的实施:

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行是hasNext中第二个迭代器的span,第651行是来自hasNext的迭代器中的dropWhile

我想我错误地使用了这两个迭代器,但我不明白为什么

1 个答案:

答案 0 :(得分:4)

您可以按如下方式简化代码,这似乎可以解决问题:

  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(因此您需要在每次调用iter时替换next),只需在原始takeWhiledropWhile上使用iter {1}}。然后就不需要var

我认为原始堆栈溢出的原因是反复调用span会创建一长串Iterator s,其hasNext个方法调用hasNextIterator其父Iterator。如果您查看span的{​​{3}},可以看到每个hasNext创建新的迭代器,将BufferedIterator的调用转发给原始迭代器(通过takeWhile },这进一步增加了调用堆栈。)

更新咨询了source code似乎虽然我的解决方案似乎有效,但不推荐 - 特别注意:

  

特别重要的是要注意,除非另有说明,   在调用方法之后,永远不应该使用迭代器。   [...]使用旧迭代器是未定义的,可能会发生更改,并且可能会导致对新迭代器进行更改。

适用于dropWhilespan(以及next),但不适用于hasNextspan

可以在原始解决方案中使用 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) } } ,但使用流而不是迭代器和递归:

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)

但表现非常糟糕。我相信一定有更好的方法......

更新2:这是一个非常迫切的解决方案,具有更好的性能:

app.service = $provide.service;