您所描述的内容(以及您在实施中或多或少重新发明的内容)foldLeft
and ~
)本质上是 Haskell 的sequence http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad.html对于单子(实际上你只需要一个应用函子,但这与这里无关)。sequence
接受一元值列表并返回一元值列表。Parser
是一个单子,所以sequence
for Parser
会改变一个List[Parser[A]]
into a Parser[List[A]]
.
Scalaz http://code.google.com/p/scalaz/给你sequence
,但我不知道是否有一个很好的方法来获得必要的Applicative
实例为Parser
。幸运的是,你可以很容易地推出自己的(我直接翻译哈斯克尔定义 http://haskell.org/ghc/docs/latest/html/libraries/base/src/Control-Monad.html#sequence):
import scala.util.parsing.combinator._
object parser extends RegexParsers {
val integer = """\d+""".r
val counts = List(1, 2, 3)
val parsers = counts.map(repN(_, integer))
val line = parsers.foldRight(success(Nil: List[List[String]])) {
(m, n) => for { x <- m ; xs <- n } yield (x :: xs)
}
def apply(s: String) = parseAll(line, s)
}
这给了我们List(List(1), List(2, 3), List(4, 5, 6))
for parser("1 2 3 4 5 6")
, 如预期的。
(请注意,我正在使用RegexParsers
这里作为一个方便的完整示例,但该方法更通用。)
如果我们脱糖的话,发生的事情可能会更清楚一些for
理解:
val line = parsers.foldRight(success(Nil: List[List[String]])) {
(current, acc) => current.flatMap(x => acc.map(x :: _))
}
我们可以写flatMap
as into
and map
as ^^
:
val line = parsers.foldRight(success(Nil: List[List[String]])) {
(current, acc) => current into (x => acc ^^ (x :: _))
}
这与您的公式并不太远,只是我们使用的是正确的折叠而不是反转,并且没有建立和分解~
s.
关于效率:我们的两种实现都会导致令人不愉快的调用堆栈。根据我的经验,这只是 Scala 解析器组合器的一个事实。去引用另一个堆栈溢出答案 https://stackoverflow.com/questions/6011141/scala-parser-combinators-vs-antlr-java-generated-parser/6011722#6011722, 例如:
Scala 的解析器组合器效率不高。他们不是
设计为。它们适合完成相对较小的任务
小投入。
My sequence
-y 方法解决了问题的“更具可读性”部分,并且几乎肯定是使用 Scala 解析器组合器解决问题的最简洁方法。它比您的实施效率稍高,并且对于几千个左右的组来说应该没问题。如果你需要处理的事情不止于此,你就必须把目光投向外部scala.util.parsing.combinator
。我会推荐如下内容:
def parse(counts: Seq[Int], input: String): Option[Seq[Seq[Int]]] = {
val parsed = try {
Some(input.split(" ").map(_.toInt))
} catch {
case _ : java.lang.NumberFormatException => None
}
parsed.flatMap { ints =>
if (ints.length != counts.sum) None
else Some(counts.foldLeft((Seq.empty[Seq[Int]], ints)) {
case ((collected, remaining), count) => {
val (m, n) = remaining.splitAt(count)
(m.toSeq +: collected, n)
}
}._1.reverse)
}
}
不能保证,但在我的系统上,它不会在包含 100k 整数组的行上溢出。