我有一个值列表,我可以从中构建一个解析器列表,它通过映射依赖于这些值(参见示例)。那么我想做的是通过连接将解析器列表转换为单个解析器。
一种可能性是使用foldLeft
和~
:
parsers.foldLeft(success(Nil)){case (ps,p) => rs ~ p ^^ {case xs ~ x => x ::xs}} ^^ (_.reverse)
我不知道组合器解析器是如何工作的;会有一个深度为列表长度的调用堆栈吗?因此,我可能会遇到很长时间串联的SO错误吗?
是否有更不可读的方式?
假设您有一个包含两行的文件。第一行包含n个整数x_1到x_n。第二行包含根据第一行属于组的x_1 + x_2 + ... x_n整数。我想从第一行获取整数序列并创建n个解析器p_1到p_n,其中p_i解析x_i整数。
假设我有第一行的整数列表l = List(1,2,3)
。对于每个整数n
,我创建一个解析n
整数的解析器:parsers = l.map(repN(_,integer))
。
答案 0 :(得分:5)
您所描述的内容(以及您在foldLeft
和~
的实施中或多或少重新发明的内容)实际上是Haskell的sequence
对于monad(实际上您只需要一个应用仿函数,但这与此无关)。 sequence
获取monadic值列表并返回monadic值列表。 Parser
是一个monad,因此sequence
Parser
会将List[Parser[A]]
更改为Parser[List[A]]
。
Scalaz为您提供了sequence
,但我不知道是否有一种很好的方法来获取Applicative
的必要Parser
实例。幸运的是,你可以轻松地自己动手(我直接翻译the Haskell definition):
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))
提供了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
写为into
,将map
写为^^
:
val line = parsers.foldRight(success(Nil: List[List[String]])) {
(current, acc) => current into (x => acc ^^ (x :: _))
}
这与你的表述相差不远,只是我们使用了正确的折叠而不是倒转,并且没有构建并打破~
。
关于效率:我们的两个实现都会导致令人不快的调用堆栈。根据我的经验,这只是Scala解析器组合器的生活现实。引用another Stack Overflow answer,例如:
Scala的解析器组合器效率不高。他们不是 旨在成为。它们适合做相对较小的小任务 小投入。
我的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整数组的行上溢出。
答案 1 :(得分:1)
您是否考虑过使用RegexParsers
(scala.util.parsing.combinator
)?然后你可以使用正则表达式作为解析器,它可以非常快速地计算并且易于编写。
例如,如果您使用解析器组合器来解析简单算术的AST,则可以使用正则表达式来解释引用对象的标记,以便您可以解析appleList.size + 4
之类的表达式。
这是一个相当简单的例子,但它显示了解析器组合器如何组合正则表达式。
object MyParser extends RegexParsers {
val regex1 = """[abc]*""".r
val regex2 = """[def]*""".r
val parse = regex1 ~ regex2
def apply(s: String) = parseAll(parse, s)
}