映射子集括号来个字符(Mapping sub-sets of parentheses to char

2019-09-22 11:43发布

我试图创建一个Scala的方法,将带括号的一个父组,表示为一个字符串,然后括号的每个子组映射到不同的字母。 那么就应该把这些地图中,它会返回,所以基本上我把下面的方法是这样的:

val s = "((2((x+3)+6)))"
val map = mapParentheses(s)

其中,S可以包含任意数量的套括号,并返回应包含地图:

"(x+3)" -> 'a'

"(a+6)" -> 'b'

"(2b)" -> 'c'

"(c)" -> 'd'

因此,其他地方在我的节目,我记得'd',并获得“(三)”,这将成为“((2B))”,然后((图2(a + 6))),最后((2((X + 3 )+6)))。 发送到方法字符串将永远不会有括号不匹配,或额外的字符主父括号外面,所以下面的项目将永远不会被发送:

  • “(FSF)一”,因为a是父括号外
  • “(A(AA))(一)”,因为(a)是父括号外
  • “((一)”,因为括号是无与伦比的
  • “)A(”因为括号是无与伦比的

所以,我想知道是否有人知道的创建这个方法的简单(或不容易)的方式。

Answer 1:

经典递归解析问题。 它可以很方便持有不同的比特。 我们将添加一些实用方法来帮助我们出来以后。

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)

我会离开它作为一个练习,读者要弄清楚它是如何工作的,与暗示递归解析递归结构一个非常强大的方式。



Answer 2:

你可以用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)

如预期的。 一个更有趣的运动(留给读者)将通过解析器线程一些状态,以避免可变队列。



Answer 3:

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包含Map ,如果它解析,这比如果没有抛出异常好。 我怀疑你真的想从地图CharString ,所以这就是这个输出。 cout的默认参数,所以你不需要输入他们自己。 正则表达式只是意味着“任何数量的不括号,eclosed在括号字符”(括号中的字符需要用“\”进行转义)。 findFirstIn找到第一个匹配,并返回一个Option[String] ,其中我们可以对模式匹配,与相关字符替换该字符串。

val s = "((2((x+3)+6)))"
parse(s)  //Some(Map(a -> (x+3), b -> (a+6), c -> (2b), d -> (c)))
parse("(a(aa))(a)") //None


文章来源: Mapping sub-sets of parentheses to chars