web-dev-qa-db-ja.com

Python括弧で囲まれたブロックの解析

Pythonで一致する括弧に含まれるテキストのチャンクを解析するための最良の方法は何でしょうか?

"{ { a } { b } { { { c } } } }"

最初に返す必要があります:

[ "{ a } { b } { { { c } } }" ]

これを入力として置くと、次が返されます。

[ "a", "b", "{ { c } }" ]

返されるはずです:

[ "{ c }" ]

[ "c" ]

[]
30
Martin

疑似コード:

For each string in the array:
    Find the first '{'. If there is none, leave that string alone.
    Init a counter to 0. 
    For each character in the string:  
        If you see a '{', increment the counter.
        If you see a '}', decrement the counter.
        If the counter reaches 0, break.
    Here, if your counter is not 0, you have invalid input (unbalanced brackets)
    If it is, then take the string from the first '{' up to the '}' that put the
     counter at 0, and that is a new element in your array.
24
Claudiu

または、このpyparsingバージョン:

>>> from pyparsing import nestedExpr
>>> txt = "{ { a } { b } { { { c } } } }"
>>>
>>> nestedExpr('{','}').parseString(txt).asList()
[[['a'], ['b'], [[['c']]]]]
>>>
41
PaulMcG

私はPythonが初めてなので、気楽にやってみてください。

def balanced_braces(args):
    parts = []
    for arg in args:
        if '{' not in arg:
            continue
        chars = []
        n = 0
        for c in arg:
            if c == '{':
                if n > 0:
                    chars.append(c)
                n += 1
            Elif c == '}':
                n -= 1
                if n > 0:
                    chars.append(c)
                Elif n == 0:
                    parts.append(''.join(chars).lstrip().rstrip())
                    chars = []
            Elif n > 0:
                chars.append(c)
    return parts

t1 = balanced_braces(["{{ a } { b } { { { c } } } }"]);
print t1
t2 = balanced_braces(t1)
print t2
t3 = balanced_braces(t2)
print t3
t4 = balanced_braces(t3)
print t4

出力:

['{ a } { b } { { { c } } }']
['a', 'b', '{ { c } }']
['{ c }']
['c']
6
Jack M.

lepl を使用して解析($ easy_install leplからインストール可能):

from lepl import Any, Delayed, Node, Space

expr = Delayed()
expr += '{' / (Any() | expr[1:,Space()[:]]) / '}' > Node

print expr.parse("{{a}{b}{{{c}}}}")[0]

出力:

ノード
 +-'{' 
 +-ノード
 | +-'{' 
 | +-'a' 
 | `-'}' 
 +-ノード
 | +-'{' 
 | +-'b' 
 | `-'}' 
 +-ノード
 | +-'{' 
 | +-ノード
 | | +-'{' 
 | | +-ノード
 | | | +-'{' 
 | | | +-'c' 
 | | | `-'}' 
 | | `-'}' 
 | `-'}' 
`-'}' 
5
jfs

よりクリーンなソリューション。これは、最も外側のブラケットで囲まれた文字列を返します。 Noneが返された場合、一致はありませんでした。

def findBrackets( aString ):
   if '{' in aString:
      match = aString.split('{',1)[1]
      open = 1
      for index in xrange(len(match)):
         if match[index] in '{}':
            open = (open + 1) if match[index] == '{' else (open - 1)
         if not open:
            return match[:index]
2
Kenny

一度に解析することもできますが、{a}"a"ではなく["a"]を意味しますが、少し奇妙です。形式を正しく理解している場合:

import re
import sys


_mbrack_rb = re.compile("([^{}]*)}") # re.match doesn't have a pos parameter
def mbrack(s):
  """Parse matching brackets.

  >>> mbrack("{a}")
  'a'
  >>> mbrack("{{a}{b}}")
  ['a', 'b']
  >>> mbrack("{{a}{b}{{{c}}}}")
  ['a', 'b', [['c']]]

  >>> mbrack("a")
  Traceback (most recent call last):
  ValueError: expected left bracket
  >>> mbrack("{a}{b}")
  Traceback (most recent call last):
  ValueError: more than one root
  >>> mbrack("{a")
  Traceback (most recent call last):
  ValueError: expected value then right bracket
  >>> mbrack("{a{}}")
  Traceback (most recent call last):
  ValueError: expected value then right bracket
  >>> mbrack("{a}}")
  Traceback (most recent call last):
  ValueError: unbalanced brackets (found right bracket)
  >>> mbrack("{{a}")
  Traceback (most recent call last):
  ValueError: unbalanced brackets (not enough right brackets)
  """
  stack = [[]]
  i, end = 0, len(s)
  while i < end:
    if s[i] != "{":
      raise ValueError("expected left bracket")
    Elif i != 0 and len(stack) == 1:
      raise ValueError("more than one root")
    while i < end and s[i] == "{":
      L = []
      stack[-1].append(L)
      stack.append(L)
      i += 1
    stack.pop()
    stack[-1].pop()
    m = _mbrack_rb.match(s, i)
    if m is None:
      raise ValueError("expected value then right bracket")
    stack[-1].append(m.group(1))
    i = m.end(0)
    while i < end and s[i] == "}":
      if len(stack) == 1:
        raise ValueError("unbalanced brackets (found right bracket)")
      stack.pop()
      i += 1
  if len(stack) != 1:
    raise ValueError("unbalanced brackets (not enough right brackets)")
  return stack[0][0]


def main(args):
  if args:
    print >>sys.stderr, "unexpected arguments: %r" % args
  import doctest
  r = doctest.testmod()
  print r
  return r[0]

if __name__ == "__main__":
  sys.exit(main(sys.argv[1:]))
2
Roger Pate

パーサー(この場合はlepl)を使用したいが、最終的に解析されたリストではなく中間結果が必要な場合は、次のようなものを探していると思います。

>>> nested = Delayed()
>>> nested += "{" + (nested[1:,...]|Any()) + "}"
>>> split = (Drop("{") & (nested[:,...]|Any()) & Drop("}"))[:].parse
>>> split("{{a}{b}{{{c}}}}")
['{a}{b}{{{c}}}']
>>> split("{a}{b}{{{c}}}")
['a', 'b', '{{c}}']
>>> split("{{c}}")
['{c}']
>>> split("{c}")
['c']

最初は不透明に見えるかもしれませんが、とても簡単です:o)

nestedは、ネストされた大括弧のマッチャーの再帰的な定義です(定義内の「+」と[...]は、一致した後、すべてを単一の文字列として保持します)。次にsplitは、 "{" ... "}"( "Drop"で破棄)で囲まれ、次のいずれかを含むものの可能な限り多く( "[:]")と一致することを示します。ネストされた式または任意の文字。

最後に、上記のpyparsingの例と同じ形式で結果を提供する「オールインワン」パーサーのleplバージョンを次に示しますが、入力でのスペースの表示方法について(私は)より柔軟です。

>>> with Separator(~Space()[:]):
...     nested = Delayed()
...     nested += Drop("{") & (nested[1:] | Any()) & Drop("}") > list
...
>>> nested.parse("{{ a }{ b}{{{c}}}}")
[[['a'], ['b'], [[['c']]]]]
2
andrew cooke

Grako(文法コンパイラ) の使用:

#!/usr/bin/env python
import json
import grako # $ pip install grako

grammar_ebnf = """
    bracketed = '{' @:( { bracketed }+ | any ) '}' ;
    any = /[^{}]+?/ ;
"""
model = grako.genmodel("Bracketed", grammar_ebnf)
ast = model.parse("{ { a } { b } { { { c } } } }", "bracketed")
print(json.dumps(ast, indent=4))

出力

[
    "a", 
    "b", 
    [
        [
            "c"
        ]
    ]
]
1
jfs

これは、同様のユースケースで私が思いついたソリューションです。これは、受け入れられた疑似コードの回答に大まかに基づいていました。外部ライブラリの依存関係を追加したくありませんでした。

def parse_segments(source, recurse=False):
    """
    extract any substring enclosed in parenthesis
    source should be a string
    """
    unmatched_count = 0
    start_pos = 0
    opened = False
    open_pos = 0
    cur_pos = 0

    finished = []
    segments = []

    for character in source:
        #scan for mismatched parenthesis:
        if character == '(':
            unmatched_count += 1
            if not opened:
                open_pos = cur_pos
            opened = True

        if character == ')':
            unmatched_count -= 1

        if opened and unmatched_count == 0:
            segment = source[open_pos:cur_pos+1]
            segments.append(segment)
            clean = source[start_pos:open_pos]
            if clean:
                finished.append(clean)
            opened = False
            start_pos = cur_pos+1

        cur_pos += 1

    assert unmatched_count == 0

    if start_pos != cur_pos:
        #get anything that was left over here
        finished.append(source[start_pos:cur_pos])

    #now check on recursion:
    for item in segments:
        #get rid of bounding parentheses:
        pruned = item[1:-1]
        if recurse:
            results = parse_tags(pruned, recurse)
            finished.expand(results)
        else:
            finished.append(pruned)

    return finished
0
Charles Brandt