Scala - 懒洋洋地对有序迭代器进行分组

时间:2015-11-27 09:53:41

标签: scala stream iterator lazy-evaluation

我有Iterator[Record]record.id以这种方式订购:

record.id=1
record.id=1
...
record.id=1
record.id=2
record.id=2
..
record.id=2

特定ID的记录可能会发生很多次,因此我想编写一个以迭代器作为输入的函数,并以惰性方式返回Iterator[Iterator[Record]]输出。

我能够提出以下内容,但在500K记录之后StackOverflowError失败了:

def groupByIter[T, B](iterO: Iterator[T])(func: T => B): Iterator[Iterator[T]] = new Iterator[Iterator[T]] {
    var iter = iterO
    def hasNext = iter.hasNext

    def next() = {
      val first = iter.next()
      val firstValue = func(first)
      val (i1, i2) = iter.span(el => func(el) == firstValue)
      iter = i2
      Iterator(first) ++ i1
    }
  }

我做错了什么?

2 个答案:

答案 0 :(得分:3)

麻烦的是,每次Iterator.span调用都为trailing迭代器创建了另一个堆叠闭包,没有任何蹦床,它很容易溢出。

实际上我不认为有一个实现,它不会记住前缀迭代器的元素,因为跟随迭代器可以在前缀被排除之前访问。

即使在.span implementation中,Queue定义中也有Leading个记忆元素。

我能想象的最简单的实现是以下Stream

implicit class StreamChopOps[T](xs: Stream[T]) {
  def chopBy[U](f: T => U): Stream[Stream[T]] = xs match {
    case x #:: _ =>
      def eq(e: T) = f(e) == f(x)
      xs.takeWhile(eq) #:: xs.dropWhile(eq).chopBy(f)
    case _ => Stream.empty
  }
}

虽然它可能不是最有效的,因为它会记忆很多。但是通过适当的迭代,GC应该处理过多中间流的问题。

您可以将其用作myIterator.toStream.chopBy(f)

简单检查验证以下代码可以在没有SO的情况下运行

Iterator.fill(10000000)(Iterator(1,1,2)).flatten //1,1,2,1,1,2,...
  .toStream.chopBy(identity)                     //(1,1),(2),(1,1),(2),...
  .map(xs => xs.sum * xs.size).sum               //60000000

答案 1 :(得分:0)

受到@Odomontois实施的chopBy的启发,这是我为Iterator实施的一个chopBy。当然每个批量应该适合分配的内存。它看起来不是很优雅,但似乎有效:)

implicit class IteratorChopOps[A](toChopIter: Iterator[A]) {

 def chopBy[U](f: A => U) = new Iterator[Traversable[A]] {
  var next_el: Option[A] = None
  @tailrec
  private def accum(acc: List[A]): List[A] = {
    next_el = None
    val new_acc = hasNext match {
      case true =>
        val next = toChopIter.next()
        acc match {
          case Nil =>
            acc :+ next
          case _ MatchTail t if (f(t) == f(next)) =>
            acc :+ next
          case _ =>
            next_el = Some(next)
            acc
        }
      case false =>
        next_el = None
        return acc
    }

    next_el match{
      case Some(_) =>
        new_acc
      case None => accum(new_acc)
    }
  }

  def hasNext = {
    toChopIter.hasNext || next_el.isDefined
  }
  def next: Traversable[A] = accum(next_el.toList)
}
}

这是一个匹配尾部的提取器:

object MatchTail {
  def unapply[A] (l: Traversable[A]) = Some( (l.init, l.last) )
}