【问题标题】:parsing nested parentheses in python, grab content by level解析python中的嵌套括号,逐级抓取内容
【发布时间】:2011-05-16 03:51:02
【问题描述】:

显然这个问题在阅读后经常出现

Regular expression to detect semi-colon terminated C++ for & while loops

想了想这个问题,我写了一个函数来返回包含在任意数量的nested()里面的内容

该函数可以很容易地扩展到任何正则表达式对象,在这里发布您的想法和考虑。

任何重构建议将不胜感激

(注意,我还是 python 新手,不想弄清楚如何引发异常或其他什么,所以如果函数无法弄清楚发生了什么,我只是让函数返回“失败” )

考虑到 cmets 的编辑函数:

def ParseNestedParen(string, level):
    """
    Return string contained in nested (), indexing i = level
    """
    CountLeft = len(re.findall("\(", string))
    CountRight = len(re.findall("\)", string))
    if CountLeft == CountRight:
        LeftRightIndex = [x for x in zip(
        [Left.start()+1 for Left in re.finditer('\(', string)], 
        reversed([Right.start() for Right in re.finditer('\)', string)]))]

    elif CountLeft > CountRight:
        return ParseNestedParen(string + ')', level)

    elif CountLeft < CountRight:
        return ParseNestedParen('(' + string, level)

    return string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]

【问题讨论】:

    标签: python parsing parentheses


    【解决方案1】:

    您并没有明确说明您的功能规范是什么,但这种行为对我来说似乎是错误的:

    >>> ParseNestedParen('(a)(b)(c)', 0)
    ['a)(b)(c']
    >>> nested_paren.ParseNestedParen('(a)(b)(c)', 1)
    ['b']
    >>> nested_paren.ParseNestedParen('(a)(b)(c)', 2)
    ['']
    

    您的代码中的其他 cmets:

    • Docstring 说“生成”,但函数返回一个列表,而不是生成器。
    • 既然只返回一个字符串,为什么要在列表中返回它?
    • 什么情况下函数可以返回字符串fail
    • 反复调用re.findall然后把结果扔掉是浪费。
    • 您尝试重新平衡字符串中的括号,但一次只能使用一个括号:
    >>> ParseNestedParen(')' * 1000, 1)
    RuntimeError: maximum recursion depth exceeded while calling a Python object
    

    正如 Thomi 在 question you linked to 中所说,“正则表达式确实是错误的工作工具!”


    解析嵌套表达式的常用方法是使用堆栈,如下所示:

    def parenthetic_contents(string):
        """Generate parenthesized contents in string as pairs (level, contents)."""
        stack = []
        for i, c in enumerate(string):
            if c == '(':
                stack.append(i)
            elif c == ')' and stack:
                start = stack.pop()
                yield (len(stack), string[start + 1: i])
    
    >>> list(parenthetic_contents('(a(b(c)(d)e)(f)g)'))
    [(2, 'c'), (2, 'd'), (1, 'b(c)(d)e'), (1, 'f'), (0, 'a(b(c)(d)e)(f)g')]
    

    【讨论】:

    • 与 ParseNestedParen('(a)(b)(c)', 0) 相关的行为实际上是正确的,但是我的函数是错误的工作工具,我用 string = 编写了函数记住“some_function(another_function(some_argument))”。为什么要返回列表?不应该。好点,谢谢!我什么时候返回失败?我不知道。也许永远不会。从我编码函数时开始,它就在那里重复调用 find all 是浪费的吗?那么我应该只在 ["(",")"]] 中创建 list countparen = [re.findall(str) for str 并使用它吗?我还应该如何重新平衡括号?感谢 cmets!
    • 很难说正确对不平衡括号的处理是什么,因为我不知道该函数将用于什么。最有可能的不平衡字符串是一种输入错误,应该被忽略(对于像语法高亮这样的应用程序)或作为错误引发(对于像编译这样的应用程序)。
    • list(parenthetic_contents('a(b(c)(d)e)(f)g')) 实际上给了我[(1, 'c'), (1, 'd'), (0, 'b(c)(d)e'), (0, 'f')]
    • @Peter:这和帖子中的示例代码不一样。
    【解决方案2】:

    括号匹配需要带有下推自动机的解析器。有一些库存在,但规则很简单,我们可以从头开始编写:

    def push(obj, l, depth):
        while depth:
            l = l[-1]
            depth -= 1
    
        l.append(obj)
    
    def parse_parentheses(s):
        groups = []
        depth = 0
    
        try:
            for char in s:
                if char == '(':
                    push([], groups, depth)
                    depth += 1
                elif char == ')':
                    depth -= 1
                else:
                    push(char, groups, depth)
        except IndexError:
            raise ValueError('Parentheses mismatch')
    
        if depth > 0:
            raise ValueError('Parentheses mismatch')
        else:
            return groups
    
    print(parse_parentheses('a(b(cd)f)')) # ['a', ['b', ['c', 'd'], 'f']]
    

    【讨论】:

    • 这太棒了,您是否有更多关于 Python 中不同自动机的行为的参考资料,或者如何以这样的简单方式在 Python 中对它们进行编码?
    • @DaniPaniz 这是一个典型的例子,它的状态很少,这就是为什么它在 Python 中很容易写的原因。更复杂的示例通常需要解析器生成器,您不会手动编写它们。
    【解决方案3】:

    以下是我的 Python 解决方案,时间复杂度为 O(N)

    str1 = "(a(b(c)d)(e(f)g)hi)"
    
    def content_by_level(str1, l):
        level_dict = {}
        level = 0
        level_char = ''
        for s in str1:
            if s == '(':
                if level not in level_dict:
                    level_dict[level] = [level_char]
                elif level_char != '':
                    level_dict[level].append(level_char)
                level_char = ''
                level += 1
            elif s == ')':
                if level not in level_dict:
                    level_dict[level] = [level_char]
                elif level_char != '':
                    level_dict[level].append(level_char)
                level_char = ''
                level -= 1
            else:
                level_char += s
        
        print(level_dict) # {0: [''], 1: ['a', 'hi'], 2: ['b', 'd', 'e', 'g'], 3: ['c', 'f']}
        return level_dict[l]
    
    print(content_by_level(str1,0)) # ['']
    print(content_by_level(str1,1)) # ['a', 'hi']
    print(content_by_level(str1,2)) # ['b', 'd', 'e', 'g']
    print(content_by_level(str1,3)) # ['c', 'f']
    

    【讨论】:

      【解决方案4】:
      #!/usr/bin/env python
      import re
      
      def ParseNestedParen(string, level):
          """
          Generate strings contained in nested (), indexing i = level
          """
          if len(re.findall("\(", string)) == len(re.findall("\)", string)):
              LeftRightIndex = [x for x in zip(
              [Left.start()+1 for Left in re.finditer('\(', string)], 
              reversed([Right.start() for Right in re.finditer('\)', string)]))]
      
          elif len(re.findall("\(", string)) > len(re.findall("\)", string)):
              return ParseNestedParen(string + ')', level)
      
          elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
              return ParseNestedParen('(' + string, level)
      
          else:
              return 'fail'
      
          return [string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]]
      

      测试:

      if __name__ == '__main__':
      
          teststring = "outer(first(second(third)second)first)outer"
      
          print(ParseNestedParen(teststring, 0))
          print(ParseNestedParen(teststring, 1))
          print(ParseNestedParen(teststring, 2))
      
          teststring_2 = "outer(first(second(third)second)"
      
          print(ParseNestedParen(teststring_2, 0))
          print(ParseNestedParen(teststring_2, 1))
          print(ParseNestedParen(teststring_2, 2))
      
          teststring_3 = "second(third)second)first)outer"
      
          print(ParseNestedParen(teststring_3, 0))
          print(ParseNestedParen(teststring_3, 1))
          print(ParseNestedParen(teststring_3, 2))
      

      输出:

      Running tool: python3.1
      
      ['first(second(third)second)first']
      ['second(third)second']
      ['third']
      ['first(second(third)second)']
      ['second(third)second']
      ['third']
      ['(second(third)second)first']
      ['second(third)second']
      ['third']
      >>> 
      

      【讨论】:

      • 因此,如您所知,该函数允许不平衡括号,尽管不是以一种非常优雅的方式。
      猜你喜欢
      • 1970-01-01
      • 2011-01-07
      • 2017-08-24
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-24
      • 2020-10-09
      相关资源
      最近更新 更多