如何将项目T从迭代器分组为迭代器[Seq [T]]

时间:2014-05-20 22:48:44

标签: scala iterator

例如,考虑一个小文件。

one
two
three

four
five

six
seven
eight
nine

我想编写一个代码,它需要一个行迭代器it: Iterator[String]并创建一个遍历各个部分的迭代器sectionIt: Iterator[Seq[String]]

在C#和Ruby中,使用yield关键字很容易实现。有talk of how to add that keyword to scala,但它取决于编译器插件。

创建sectionIt的一种方法是直接创建Iterator[Seq[String]]并覆盖nexthasNext。对于像Scala这样的高级语言来说,这种方法看起来很乏味且很省事。

我意识到还有其他流媒体数据的抽象,比如Iteratees,这可能会让这更容易,但这对于正在学习新语言的人来说并不容易。

在Scala中编写上述代码的好方法是什么?

2 个答案:

答案 0 :(得分:2)

使用yield,您可以使用Ruby或C#Stream完成大部分内容:

def splitOnBlankLines(iter: Iterator[String]): Iterator[Seq[String]] = {
  def asStream(list: List[String]): Stream[List[String]] = {
    if (iter.hasNext) {
      val line = iter.next()
      if (line == "")
        list.reverse #:: asStream(Nil)
      else
        asStream(line :: list)
    } else {
      list.reverse #:: Stream.empty
    }
  }
  asStream(Nil).iterator
}

每当我们想要yield时,我们将#::与我们想要返回的值(在这种情况下为list.reverse)和表示流的其余部分的表达式一起使用。 #::将此表达式作为名称参数,因此在需要Stream的其余部分之前,它不会执行。返回最后一个值时,我们使用Stream.empty表示不再生成任何值。

可以将Stream的这种行为与continuation插件结合起来,以获得在语法上等同于Ruby或C#的yield(在所有二十行代码中),但是continuation插件不太可能永远变得稳定。

但是,手动编写Iterator几乎一样简单:

import scala.annotation.tailrec

class BlankLineSplittingIterator(iter: Iterator[String]) extends Iterator[Seq[String]] {
  def hasNext = iter.hasNext
  def next = {
    if (!iter.hasNext)
      Iterator.empty.next
    @tailrec def untilBlank(list: List[String]): List[String] = {
      val line = iter.next()
      if (line == "" || !iter.hasNext)
        list.reverse
      else
        untilBlank(line :: list)
    }
    untilBlank(Nil)
  }
}

答案 1 :(得分:2)

另一个答案略有不同:

  def section(it: Iterator[String]): Iterator[Seq[String]] = { 
    def spanned(it: Iterator[String]): Stream[Seq[String]] =
      if (!it.hasNext) Stream.empty
      else { val (a, b) = it span (_ != "") ; a.toSeq #:: spanned(b drop 1) }
    spanned(it).iterator
  }

它有点懒散,空行之间阅读的行为是不同的:

scala> lazysplit.Test.splitOnBlankLines(f"%n%n%n%n%n".lines).size
res0: Int = 6

scala> lazysplit.Test.section(f"%n%n%n%n%n".lines).size
res1: Int = 5