5

たとえば、複数の括弧で囲まれたグループを持つ文字列を解析して、各グループを保持する文字列のリストにしたい場合

"((a b c) a b c)"

の中へ

["((a b c) a b c)","( a b c)"]

parsecを使用してそれを行うにはどうすればよいですか? の使用は良さそうにbetween見えますが、開始値と終了値で分離することはできないようです。

4

2 に答える 2

11

私は再帰パーサーを使用します:

data Expr = List [Expr] | Term String

expr :: Parsec String () Expr
expr = recurse <|> terminal

terminalこの場合、これらは文字列のように見えるので、プリミティブはどこにありますか

 where terminal = Term <$> many1 letter

そしてrecurse_

       recurse  = List <$>
                  (between `on` char) '(' ')' (expr `sepBy1` char ' ')

Exprこれで、収集できる s の素敵なツリーができました

collect r@(List ts) = r : concatMap collect ts
collect _           = []
于 2013-04-24T04:35:18.327 に答える
9

jozefg の解決策は、私が思いついたものとほとんど同じですが (そして、彼のすべての提案に完全に同意します)、いくつかの小さな違いがあり、2 番目の回答を投稿する必要があると思いました。

  1. 最初の例の期待される結果により、スペースで区切られた部分を個々のサブツリーとして扱う必要はありません。
  2. さらに、予想される結果 (つまり、文字列のリスト) を実際に計算する部分を見るのも興味深いかもしれません。

だからここに私のバージョンがあります。jozefg によって既に提案されているように、タスクをいくつかのサブタスクに分割します。それらは:

  1. 文字列を解析して、ある種の木を表す代数データ型にします。
  2. このツリーの (目的の) サブツリーを収集します。
  3. 木をひもに変えます。

1に関しては、まずツリーデータ型が必要です

import Text.Parsec
import Text.Parsec.String
import Control.Applicative ((<$>))

data Tree = Leaf String | Node [Tree]

次に、文字列をこの型の値に解析できる関数。

parseTree :: Parser Tree
parseTree = node <|> leaf
  where
    node = Node <$> between (char '(') (char ')') (many parseTree)
    leaf = Leaf <$> many1 (noneOf "()")

私のバージョンでは、括弧の間の穴の文字列をLeafノードと見なします (つまり、空白で分割しません)。

次に、関心のあるツリーのサブツリーを収集する必要があります。

nodes :: Tree -> [Tree]
nodes (Leaf _) = []
nodes t@(Node ts) = t : concatMap nodes ts

最後に、 s のShow-instance をTree使用すると、それらを文字列に変換できます。

instance Show Tree where
  showsPrec d (Leaf x) = showString x
  showsPrec d (Node xs) = showString "(" . showList xs . showString ")"
    where
      showList [] = id
      showList (x:xs) = shows x . showList xs

次に、元のタスクは、たとえば次のように解決できます。

parseGroups :: Parser [String]
parseGroups = map show . nodes <$> parseTree

> parseTest parseGroups "((a b c) a b c)"
["((a b c) a b c)","(a b c)"]
于 2013-04-24T06:00:18.640 に答える