8

さまざまな開始ブラケットと終了ブラケットを含む文字列を解析したいとします(タイトルには括弧を使用しました。これは、より一般的であると考えているためです。それでも質問は同じです)。これにより、すべての上位レベルがリストで区切られます。

与えられた:

[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構造を使用することを考えましたが、カウンターを使用すると、単純な条件を取得できず(カウンターを更新するためだけに条件が必要です)、これをどのように使用できるかわかりません。この場合、構築します。

4

5 に答える 5

7

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]])
于 2012-04-04T18:30:49.800 に答える
4

どうですか:

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]
于 2012-04-04T14:00:10.643 に答える
2

要件を考えると、括弧を数えることは完全に問題ないようです。機能的な方法でそれをどのように行いますか?状態を明示的に渡すことができます。

blocksしたがって、最初に、次の結果を累積または連結しblock、深さを追跡する状態を定義します。

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

次に、次の状態を返す処理を行う純粋関数を記述します。うまくいけば、この1つの関数を注意深く調べて、それが正しいことを確認できます。

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]]
于 2012-04-04T15:14:02.833 に答える
2

あなたは醜い命令型の解決策を持っているので、見栄えの良いものを作ってみませんか?:)

これは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
  }
于 2012-04-04T16:54:38.240 に答える
0

これを試して:

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]]
)
于 2012-04-04T11:53:51.723 に答える