3

I'm just fooling about and strangely found it a bit tricky to parse nested brackets in a simple recursive function.

For example, if the program's purpose it to lookup user details, it may go from {{name surname} age} to {Bob Builder age} and then to Bob Builder 20.

Here is a mini-program for summing totals in curly brackets that demonstrates the concept.

  // Parses string recursively by eliminating brackets
  def parse(s: String): String = {
    if (!s.contains("{")) s
    else {
      parse(resolvePair(s))
    }
  }

  // Sums one pair and returns the string, starting at deepest nested pair
  // e.g.
  // {2+10} lollies and {3+{4+5}} peanuts
  // should return:
  // {2+10} lollies and {3+9} peanuts
  def resolvePair(s: String): String = {
    ??? // Replace the deepest nested pair with it's sumString result
  }

  // Sums values in a string, returning the result as a string
  // e.g. sumString("3+8") returns "11"
  def sumString(s: String): String = {
    val v = s.split("\\+")
    v.foldLeft(0)(_.toInt + _.toInt).toString
  }

  // Should return "12 lollies and 12 peanuts"
  parse("{2+10} lollies and {3+{4+5}} peanuts")

Any ideas to a clean bit of code that could replace the ??? would be great. It's mostly out of curiosity that I'm searching for an elegant solution to this problem.

Jack
  • 16,506
  • 19
  • 100
  • 167

2 Answers2

6

Parser combinators can handle this kind of situation:

import scala.util.parsing.combinator.RegexParsers
object BraceParser extends RegexParsers {
  override def skipWhitespace = false
  def number = """\d+""".r ^^ { _.toInt }
  def sum: Parser[Int] = "{" ~> (number | sum) ~ "+" ~ (number | sum) <~ "}" ^^ {
    case x ~ "+" ~ y => x + y
  }
  def text = """[^{}]+""".r
  def chunk = sum ^^ {_.toString } | text
  def chunks = rep1(chunk) ^^ {_.mkString} | ""
  def apply(input: String): String = parseAll(chunks, input) match {
    case Success(result, _) => result
    case failure: NoSuccess => scala.sys.error(failure.msg)
  }
}

Then:

BraceParser("{2+10} lollies and {3+{4+5}} peanuts")
//> res0: String = 12 lollies and 12 peanuts

There is some investment before getting comfortable with parser combinators but I think it is really worth it.

To help you decipher the syntax above:

  • regular expression and strings have implicit conversions to create primitive parsers with strings results, they have type Parser[String].
  • the ^^ operator allows to apply a function to the parsed elements
    • it can convert a Parser[String] into a Parser[Int] by doing ^^ {_.toInt}
    • Parser is a monad and Parser[T].^^(f) is equivalent to Parser[T].map(f)
  • the ~, ~> and <~ requires some inputs to be in a certain sequence
    • the ~> and <~ drop one side of the input out of the result
    • the case a ~ b allows to pattern match the results
    • Parser is a monad and (p ~ q) ^^ { case a ~ b => f(a, b) } is equivalent to for (a <- p; b <- q) yield (f(a, b))
    • (p <~ q) ^^ f is equivalent to for (a <- p; _ <- q) yield f(a)
  • rep1 is a repetition of 1 or more element
  • | tries to match an input with the parser on its left and if failing it will try the parser on the right
huynhjl
  • 41,520
  • 14
  • 105
  • 158
  • Thanks for the solution and thorough layout of the code. Yes, I should really invest a bit of time and get to know parser combinators better. – Jack Jun 13 '13 at 07:22
1

How about

def resolvePair(s: String): String = {
val open = s.lastIndexOf('{')
val close = s.indexOf('}', open)
if((open >= 0) && (close > open)) {
    val (a,b) = s.splitAt(open+1)
    val (c,d) = b.splitAt(close-open-1)
    resolvePair(a.dropRight(1)+sumString(c).toString+d.drop(1))
} else
    s
}   

I know it's ugly but I think it works fine.

pt2121
  • 11,720
  • 8
  • 52
  • 69
  • 1
    +1 It's not ugly ;-) Chop it all up, and put it all back together again is very much how I normally solve string replacement code. But, I'm hoping for an answer that shows a more effective or alternative style to expand my way of thinking about these kind of problems (because I do these a lot and sometimes on long complex strings). For example, using a regex to find and replace the inner curly bracket pair with it's sumString result, or maybe using Scala parser combinators in some nice or effective way. – Jack Jun 13 '13 at 04:37