【问题标题】:Python parsing bracketed blocksPython解析括号中的块
【发布时间】:2010-12-11 17:03:12
【问题描述】:

在 Python 中解析匹配括号中包含的文本块的最佳方法是什么?

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

最初应该返回:

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

将其作为输入应该返回:

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

应该返回:

[ "{ c }" ]

[ "c" ]

[]

【问题讨论】:

  • 我很好奇。在你的最后一个例子中,它会返回 [ "{ c }" ] 然后返回 [ "c" ] 然后返回 [ ]?
  • 第二个语句不应该是 ["{ a } { b } { { { c } } }"] 吗?
  • 第二个语句是 [ "{ a } { b } { { { c } } }" ],正是你写的……我将完成整个序列,以便人们确切地知道我想要什么
  • 我想你想要 [ "{ a } { b } { { { c } } }" ] 去 [ "a", "b", "{ { c } }" ],我说的对吗?

标签: python parsing text-parsing brackets


【解决方案1】:

或者这个 pyparsing 版本:

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

【讨论】:

  • 这比公认答案中的算法更具可读性,而且我也相信一个广泛使用(因此经过测试)的库,而不是我自己开发的解决方案。需要注意的一件非常重要的事情:如果您的整个表达式没有被一对分组符号包围,那么这将只处理 first 分组表达式。如果您总是想处理整个表达式,您可以通过添加一对外部分组符号来强制执行此操作。
【解决方案2】:

伪代码:

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.

【讨论】:

  • 太棒了!我脑子里有这样的设计,但没有那么好。出于兴趣,这种解析器有正式名称吗?我熟悉我们在 uni 做的一个模块中的递归下降解析器,但不是这种。
  • 我不确定它是否有名字。这可能只是进行括号匹配的一种短路方式,而无需进行成熟的递归下降解析器。也许其他人知道得更好。
  • 补充一点:计数器代表你在“递归”中的深度,无需进行函数调用。
  • 您的缩进已关闭。如所写,输入“{a}{b}”您将返回“a{b}”而不是“ab”。 “这里”部分需要缩进。这是 Python 和缩进计数。
  • 不,我认为他的缩进是正确的。如果循环以计数器为零而终止,那么一切都很好,并且找到了匹配的括号对。但是,如果它以 counter != zero 终止,那么循环就会因为没有更多字符而终止,因此括号不匹配。
【解决方案3】:

我是 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']

【讨论】:

    【解决方案4】:

    使用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]
    

    输出:

    节点 +- '{' +- 节点 | +- '{' | +-'一个' | `-'}' +- 节点 | +- '{' | +-'b' | `-'}' +- 节点 | +- '{' | +- 节点 | | +- '{' | | +- 节点 | | | +- '{' | | | +- 'c' | | | `-'}' | | `-'}' | `-'}' `-'}'

    【讨论】:

      【解决方案5】:

      更清洁的解决方案。这将返回包含在最外层括号中的字符串。如果返回 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]
      

      【讨论】:

        【解决方案6】:

        您也可以一次解析它们,尽管我发现 {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:]))
        

        【讨论】:

          【解决方案7】:

          如果您想使用解析器(在本例中为 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" 丢弃)并包含一个嵌套表达式或任何字母。

          最后,这是一个 lepl 版本的“一体式”解析器,它给出的结果格式与上面的 pyparsing 示例相同,但(我相信)在输入中空格的显示方式上更灵活:

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

          【讨论】:

            【解决方案8】:

            使用Grako (grammar compiler)

            #!/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"
                    ]
                ]
            ]
            

            【讨论】:

              【解决方案9】:

              这是我为类似用例提出的解决方案。这大致基于公认的伪代码答案。我不想为外部库添加任何依赖项:

              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
              

              【讨论】:

                猜你喜欢
                • 2018-01-17
                • 1970-01-01
                • 1970-01-01
                • 2016-12-09
                • 2021-12-02
                • 1970-01-01
                • 2011-04-27
                • 1970-01-01
                • 2010-09-26
                相关资源
                最近更新 更多