Scala:对多个迭代器进行高效迭代

时间:2013-02-28 03:02:47

标签: scala iterator scala-collections

我正在编写一个应用程序服务器,并且有一个消息发送循环。消息由字段组成,因此可以视为迭代字段的迭代器。并且有一个由消息循环处理的消息队列,但循环在任何时候都是可破坏的(例如,当套接字缓冲区已满时)并且可以在以后恢复。目前的实施如下:

private val messageQueue: Queue[Iterator[Field]]

sent = 0
breakable {
  for (iterator <- messageQueue) {
    for (field <- iterator) {
      ... breakable ...
    }
    sent += 1
  }
} finally messageQueue.trimStart(sent)

这可以工作并且不错,但是我认为如果我可以使用使用++运算符连接迭代器的迭代器替换队列,我可以使代码更清晰一些。说:

private val messageQueue: Iterator[Field] = message1.iterator ++ message2.iterator ++ ...

breakable {
  for (field <- messageQueue) {
    ... breakable ...
  }
}

现在代码看起来更干净但是存在性能问题。连接的迭代器在内部形成(非平衡)树,因此next()操作需要O(n)的时间。因此,迭代总体上需要O(n ^ 2)的时间。

总而言之,消息只需要处理一次,因此队列不需要是Traversable。迭代器(TraversableOnce)会这样做。我想将消息队列视为连续迭代器的集合,但++存在性能问题。是否有一个很好的解决方案可以使代码更清晰但同时又高效?

2 个答案:

答案 0 :(得分:2)

您是否考虑过使用Stream#:::懒洋洋地将您的邮件连接在一起?

private val messageQueue: Stream[Field] = message1.toStream #::: message2.toStream #::: ...

breakable {
  for (field <- messageQueue) {
    ... breakable ...
  }
}

至于时间复杂度,我相信你连接的迭代器数量是O(n)(你需要为每个迭代器调用toStream并将它们#:::连接在一起) 。但是,单个toStream#:::操作应该是O(1),因为它们是懒惰的。以下是toStream的{​​{1}}实现:

Iterator

这将花费一些时间,因为Stream.cons的第二个参数是按名称调用的,因此在您实际访问尾部之前不会对其进行评估。

然而,转换为Stream 为每个元素访问添加一个常量的开销因子,即不是仅仅在迭代器上调用def toStream: Stream[A] = if (self.hasNext) Stream.cons(self.next, self.toStream) else Stream.empty[A] ,而是需要做一些额外的方法调用强制流的惰性尾部并访问包含的值。

答案 1 :(得分:2)

如果你只是压扁它们怎么办?

def flattenIterator[T](l: List[Iterator[T]]): Iterator[T] = l.iterator.flatten