【问题标题】:Parentheses matching in Scala --- functional approachScala中的括号匹配---函数式方法
【发布时间】:2012-04-04 11:35:17
【问题描述】:

假设我想解析一个带有各种左括号和右括号的字符串(我在标题中使用了括号,因为我相信它更常见——不过问题是一样的),这样我就可以将所有更高级别分开一个列表。

给定:

[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]

我想要:

List("[hello:=[notting],[hill]]", "[3.4(4.56676|5.67787)]", "[the[hill[is[high]]not]]")

我这样做的方法是计算左括号和右括号,并在我的计数器为 0 时添加到列表中。但是,我有一个丑陋的命令式代码。您可以假设原始字符串格式正确。

我的问题是:解决这个问题的好方法是什么?

注意:我曾考虑过使用 for...yield 构造,但考虑到计数器的使用,我无法获得简单的条件(我也必须有条件来更新计数器)而且我不知道我是如何做到的在这种情况下可以使用这种结构。

【问题讨论】:

  • 参见“解析器组合器”:stackoverflow.com/search?q=scala+parser+combinators
  • 类似情况:blog.tmorris.net/haskell-scala-java-7-functional-java-java。 cmets 中的代码是最有用的部分。
  • @AlexanderAzarov,每次我使用解析器组合器时,我都觉得我需要更多的经验才能熟练地在几乎一定的时间内获得解决方案。这里是不是有点矫枉过正?
  • 当然,解析器组合器是非常通用的工具。当您知道如何使用它时,您将能够非常快速地提出一个可靠的解决方案并且代码更具可读性。像往常一样,这是一个权衡(“维护成本”与“性能”),因为手写算法很可能会更高效。

标签: scala functional-programming


【解决方案1】:

使用 Scala 解析器组合库的快速解决方案:

import util.parsing.combinator.RegexParsers

object Parser extends RegexParsers {
  lazy val t = "[^\\[\\]\\(\\)]+".r

  def paren: Parser[String] =
    ("(" ~ rep1(t | paren) ~ ")" |
     "[" ~ rep1(t | paren) ~ "]") ^^ {
      case o ~ l ~ c => (o :: l ::: c :: Nil) mkString ""
    }

  def all = rep(paren)

  def apply(s: String) = parseAll(all, s)
}

在 REPL 中检查它:

scala> Parser("[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]")
res0: Parser.ParseResult[List[String]] = [1.72] parsed: List([hello:=[notting],[hill]], [3.4(4.56676|5.67787)], [the[hill[is[high]]not]])

【讨论】:

  • 做起来似乎很容易。我总共花了几个小时在这上面,我所拥有的或多或少是这样的:"[" ~ rep(paren ~ opt(t)) ~ "]" | "[" ~ rep(t ~ opt(paren)) ~ "]".
【解决方案2】:

怎么样:

def split(input: String): List[String] = {
  def loop(pos: Int, ends: List[Int], xs: List[String]): List[String] =
    if (pos >= 0)
      if ((input charAt pos) == ']') loop(pos-1, pos+1 :: ends, xs)
      else if ((input charAt pos) == '[')
        if (ends.size == 1) loop(pos-1, Nil, input.substring(pos, ends.head) :: xs)
        else loop(pos-1, ends.tail, xs)
      else loop(pos-1, ends, xs)
    else xs
  loop(input.length-1, Nil, Nil)
}

scala> val s1 = "[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]"
s1: String = [hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]

scala> val s2 = "[f[sad][add]dir][er][p]"
s2: String = [f[sad][add]dir][er][p]

scala> split(s1) foreach println
[hello:=[notting],[hill]]
[3.4(4.56676|5.67787)]
[the[hill[is[high]]not]]

scala> split(s2) foreach println
[f[sad][add]dir]
[er]
[p]

【讨论】:

  • +1,我的回答是基于具体案例,而不是通用案例,这显然需要更复杂且显然是迭代的解决方案。
【解决方案3】:

考虑到您的要求,计算括号似乎非常好。您将如何以实用的方式做到这一点?您可以使状态显式传递。

首先我们定义我们的状态,在blocks 中累积结果或连接下一个block 并跟踪深度:

case class Parsed(blocks: Vector[String], block: String, depth: Int)

然后我们编写一个处理后返回下一个状态的纯函数。希望我们能仔细看看这个函数并确保它是正确的。

def nextChar(parsed: Parsed, c: Char): Parsed = {
  import parsed._
  c match {
    case '[' | '(' => parsed.copy(block = block + c,
                                  depth = depth + 1)
    case ']' | ')' if depth == 1 
                   => parsed.copy(blocks = blocks :+ (block + c),
                                  block = "",
                                  depth = depth - 1)
    case ']' | ')' => parsed.copy(block = block + c,
                                  depth = depth - 1)
    case _         => parsed.copy(block = block + c)
  }
}

然后我们只是使用foldLeft 来处理具有初始状态的数据:

val data = "[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]"
val parsed = data.foldLeft(Parsed(Vector(), "", 0))(nextChar) 
parsed.blocks foreach println

返回:

[hello:=[notting],[hill]]
[3.4(4.56676|5.67787)]
[the[hill[is[high]]not]]

【讨论】:

  • 该死,我也有同样的想法。
【解决方案4】:

你有一个丑陋的命令式解决方案,那么为什么不做一个好看的呢? :)

这是对 huynhjl 解决方案的命令式翻译,但只是为了表明命令式有时很简洁,也许更容易理解。

  def parse(s: String) = {
    var res = Vector[String]()
    var depth = 0
    var block = ""
    for (c <- s) {
      block += c
      c match {
        case '[' => depth += 1
        case ']' => depth -= 1
                    if (depth == 0) {
                      res :+= block
                      block = ""
                    }
        case _   =>
      }
    }
    res
  }

【讨论】:

    【解决方案5】:

    试试这个:

    val s = "[hello:=[notting],[hill]][3.4(4.56676|5.67787)][the[hill[is[high]]not]]"
    s.split("]\\[").toList
    

    返回:

    List[String](
      [hello:=[notting],[hill],
      3.4(4.56676|5.67787),
      the[hill[is[high]]not]]
    )
    

    【讨论】:

    • 看这个例子:“[f[sad][add]dir][er][p]”。您的建议显然不能解决一般情况。此外,我不是在寻找快速修复,我有一个完美的工作代码。
    • 正在对提供的特定案例进行操作。 @hyunhjl 的回答特别有趣……
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-25
    • 2012-03-11
    • 2017-03-25
    相关资源
    最近更新 更多