例如,考虑一个小文件。
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]]
并覆盖next
和hasNext
。对于像Scala这样的高级语言来说,这种方法看起来很乏味且很省事。
我意识到还有其他流媒体数据的抽象,比如Iteratees,这可能会让这更容易,但这对于正在学习新语言的人来说并不容易。
在Scala中编写上述代码的好方法是什么?
答案 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