我正在尝试创建一个Scala方法,该方法将采用一个括号的父组,表示为String,然后将每个括号子组映射到不同的字母。然后它应该将它们放在它返回的地图中,所以基本上我会调用以下方法:
val s = "((2((x+3)+6)))"
val map = mapParentheses(s)
其中 s 可以包含任意数量的括号集,并且返回的地图应包含:
"(x+3)" -> 'a'
"(a+6)" -> 'b'
"(2b)" -> 'c'
"(c)" -> 'd'
所以在我的程序的其他地方我可以回想起'd'并得到“(c)”这将成为“((2b))”< / b>然后((2(a + 6))),最后((2((x + 3)+6)))。发送到方法 mapParentheses 的字符串将永远不会有不匹配的括号或主括号外的额外字符,因此永远不会发送以下项目:
a
在父括号之外(a)
在父括号之外所以我想知道是否有人知道创建这个 mapParentheses 方法的简单方法(或不容易)。
答案 0 :(得分:3)
使用Scala的解析器组合器可以非常轻松地完成此操作。首先是导入和一些简单的数据结构:
import scala.collection.mutable.Queue
import scala.util.parsing.combinator._
sealed trait Block {
def text: String
}
case class Stuff(text: String) extends Block
case class Paren(m: List[(String, Char)]) extends Block {
val text = m.head._2.toString
def toMap = m.map { case (k, v) => "(" + k + ")" -> v }.toMap
}
即,一个块表示输入的子字符串,它是一些非括号内容或括号内的字符串。
现在解析器本身:
class ParenParser(fresh: Queue[Char]) extends RegexParsers {
val stuff: Parser[Stuff] = "[^\\(\\)]+".r ^^ (Stuff(_))
def paren: Parser[Paren] = ("(" ~> insides <~ ")") ^^ {
case (s, m) => Paren((s -> fresh.dequeue) :: m)
}
def insides: Parser[(String, List[(String, Char)])] =
rep1(paren | stuff) ^^ { blocks =>
val s = blocks.flatMap(_.text)(collection.breakOut)
val m = blocks.collect {
case Paren(n) => n
}.foldLeft(List.empty[(String, Char)])(_ ++ _)
(s, m)
}
def parse(input: String) = this.parseAll(paren, input).get.toMap
}
在最后一行中使用get
非常不理想,但是你的断言证明我们可以期待格式良好的输入。
现在我们可以创建一个新的解析器并使用一些新的变量传入一个可变队列:
val parser = new ParenParser(Queue('a', 'b', 'c', 'd', 'e', 'f'))
现在试试你的测试字符串:
scala> println(parser parse "((2((x+3)+6)))")
Map((c) -> d, (2b) -> c, (a+6) -> b, (x+3) -> a)
根据需要。一个更有趣的练习(留给读者)将通过解析器线程化一些状态以避免可变队列。
答案 1 :(得分:2)
经典的递归解析问题。保持不同的位可以很方便。我们将添加一些实用方法来帮助我们以后。
trait Part {
def text: String
override def toString = text
}
class Text(val text: String) extends Part {}
class Parens(val contents: Seq[Part]) extends Part {
val text = "(" + contents.mkString + ")"
def mapText(m: Map[Parens, Char]) = {
val inside = contents.collect{
case p: Parens => m(p).toString
case x => x.toString
}
"(" + inside.mkString + ")"
}
override def equals(a: Any) = a match {
case p: Parens => text == p.text
case _ => false
}
override def hashCode = text.hashCode
}
现在你需要解析这些事情:
def str2parens(s: String): (Parens, String) = {
def fail = throw new Exception("Wait, you told me the input would be perfect.")
if (s(0) != '(') fail
def parts(s: String, found: Seq[Part] = Vector.empty): (Seq[Part], String) = {
if (s(0)==')') (found,s)
else if (s(0)=='(') {
val (p,s2) = str2parens(s)
parts(s2, found :+ p)
}
else {
val (tx,s2) = s.span(c => c != '(' && c != ')')
parts(s2, found :+ new Text(tx))
}
}
val (inside, more) = parts(s.tail)
if (more(0)!=')') fail
(new Parens(inside), more.tail)
}
现在我们已经解析了整个事情。所以让我们找到所有的位。
def findParens(p: Parens): Set[Parens] = {
val inside = p.contents.collect{ case q: Parens => findParens(q) }
inside.foldLeft(Set(p)){_ | _}
}
现在我们可以构建你想要的地图了。
def mapParentheses(s: String) = {
val (p,_) = str2parens(s)
val pmap = findParens(p).toSeq.sortBy(_.text.length).zipWithIndex.toMap
val p2c = pmap.mapValues(i => ('a'+i).toChar)
p2c.map{ case(p,c) => (p.mapText(p2c), c) }.toMap
}
证明它有效:
scala> val s = "((2((x+3)+6)))"
s: java.lang.String = ((2((x+3)+6)))
scala> val map = mapParentheses(s)
map: scala.collection.immutable.Map[java.lang.String,Char] =
Map((x+3) -> a, (a+6) -> b, (2b) -> c, (c) -> d)
我将把它作为练习留给读者弄清楚它是如何工作的,提示递归是解析递归结构的一种非常强大的方法。
答案 2 :(得分:0)
def parse(s: String,
c: Char = 'a', out: Map[Char, String] = Map() ): Option[Map[Char, String]] =
"""\([^\(\)]*\)""".r.findFirstIn(s) match {
case Some(m) => parse(s.replace(m, c.toString), (c + 1).toChar , out + (c -> m))
case None if s.length == 1 => Some(out)
case _ => None
}
如果它解析,则输出包含Option
的{{1}},这比抛出异常更好。我怀疑你真的想要一张从Map
到Char
的地图,这就是输出的地图。 String
和c
是默认参数,因此您无需自行输入。正则表达式只是意味着“任何数量的不是parens的字符,用parens封闭”(paren字符需要用“\”进行转义)。 out
找到第一个匹配并返回findFirstIn
,我们可以将其匹配,用相关字符替换该字符串。
Option[String]