【问题标题】:separate string into contents in parentheses vs brackets vs flat text将字符串分隔成括号、方括号和纯文本中的内容
【发布时间】:2013-07-02 23:59:42
【问题描述】:

我需要一种方法,在 python 中给出一个文本字符串,将其内容分成一个列表,按 3 个参数分割 - 最外面的括号与最外面的括号与普通文本,保留原始语法。

例如,给定一个字符串

(([a] b) c ) [d] (e) f

预期的输出将是这个列表:

['(([a] b) c )', '[d]', '(e)', ' f']

我用正则表达式尝试了几件事,例如

\[.+?\]|\(.+?\)|[\w+ ?]+

这给了我

>>> re.findall(r'\[.+?\]|\(.+?\)|[\w+ ?]+', '(([a] b) c ) [d] (e) f')
['(([a] b)', ' c ', ' ', '[d]', ' ', '(e)', ' f']

(错误列表中的项目c)

我也试过贪心版的,

\[.+\]|\(.+\)|[\w+ ?]+

但是当字符串具有相同类型的单独运算符时它就不足了:

>>> re.findall(r'\[.+\]|\(.+\)|[\w+ ?]+', '(([a] b) c ) [d] (e) f')
['(([a] b) c ) [d] (e)', ' f']

然后我从正则表达式转而使用堆栈:

>>> def parenthetic_contents(string):
    stack = []
    for i, c in enumerate(string):
        if c == '(' or c == '[':
            stack.append(i)
        elif (c == ')' or c == ']'):
            start = stack.pop()
            yield (len(stack), string[start + 0:i+1])

对于方括号和圆括号来说效果很好,除了我无法获得平面文本(或者我有,但我不知道?):

>>> list(parenthetic_contents('(([a] b) c ) [d] (e) f'))
[(2, '[a]'), (1, '([a] b)'), (0, '(([a] b) c )'), (0, '[d]'), (0, '(e)')]

我不熟悉 pyparsing。起初它看起来好像 nestedExpr() 可以解决问题,但它只需要一个分隔符(() 或 [],但不能同时使用两者),这对我不起作用。

我现在完全没有想法了。欢迎提出任何建议。

【问题讨论】:

标签: python regex stack pyparsing


【解决方案1】:

仅经过非常轻微的测试(并且输出包括空白)。与@Marius 的回答(以及关于需要PDA 的paren 匹配的一般规则)一样,我使用了一个堆栈。但是,我内心有一点额外的偏执狂。

def paren_matcher(string, opens, closes):
    """Yield (in order) the parts of a string that are contained
    in matching parentheses.  That is, upon encounting an "open
    parenthesis" character (one in <opens>), we require a
    corresponding "close parenthesis" character (the corresponding
    one from <closes>) to close it.

    If there are embedded <open>s they increment the count and
    also require corresponding <close>s.  If an <open> is closed
    by the wrong <close>, we raise a ValueError.
    """
    stack = []
    if len(opens) != len(closes):
        raise TypeError("opens and closes must have the same length")
    # could make sure that no closes[i] is present in opens, but
    # won't bother here...

    result = []
    for char in string:
        # If it's an open parenthesis, push corresponding closer onto stack.
        pos = opens.find(char)
        if pos >= 0:
            if result and not stack: # yield accumulated pre-paren stuff
               yield ''.join(result)
               result = []
            result.append(char)
            stack.append(closes[pos])
            continue
        result.append(char)
        # If it's a close parenthesis, match it up.
        pos = closes.find(char)
        if pos >= 0:
            if not stack or stack[-1] != char:
                raise ValueError("unbalanced parentheses: %s" %
                    ''.join(result))
            stack.pop()
            if not stack: # final paren closed
                yield ''.join(result)
                result = []
    if stack:
        raise ValueError("unclosed parentheses: %s" % ''.join(result))
    if result:
        yield ''.join(result)

print list(paren_matcher('(([a] b) c ) [d] (e) f', '([', ')]'))
print list(paren_matcher('foo (bar (baz))', '(', ')'))

【讨论】:

  • 谢谢!我需要花更多的时间把它拆开来了解它是如何工作的……但最终的结果就是我想要的:)
【解决方案2】:

我设法使用一个简单的解析器来做到这一点,该解析器使用level 变量跟踪您在堆栈中的深度。

import string

def get_string_items(s):
    in_object = False
    level = 0
    current_item = ''
    for char in s:
        if char in string.ascii_letters:
            current_item += char
            continue
        if not in_object:
            if char == ' ':
                continue
        if char in ('(', '['):
            in_object = True
            level += 1
        elif char in (')', ']'):
            level -= 1
        current_item += char
        if level == 0:
            yield current_item
            current_item = ''
            in_object = False
    yield current_item

输出:

list(get_string_items(s))
Out[4]: ['(([a] b) c )', '[d]', '(e)', 'f']
list(get_string_items('(hi | hello) world'))
Out[12]: ['(hi | hello)', 'world']

【讨论】:

  • 谢谢 :) 这似乎在大多数情况下都有效。它正确地进行了分离,但平面文本被拆分为单个字符。所以,“list(get_string_items('(hi | hello) world'))”变成了“['(hi | hello)', 'w', 'o', 'r', 'l', 'd']" .不过我也许可以解决这个问题。
  • 啊,是的,抱歉,我太专注于括号而忘记了扁平文本大小写。我认为这是一个非常简单的修复,现在编辑
  • 这个脚本仍然存在一些问题(它占用了纯文本类别中的空间)。 Torek 的代码看起来像预期的那样。不过,我感谢您的回答,我可能仍会将其用于其他应用程序;)
【解决方案3】:

您仍然可以使用nestedExpr,您想创建多个表达式,每个表达式都有一种分隔符:

from pyparsing import nestedExpr, Word, printables, quotedString, OneOrMore

parenList = nestedExpr('(', ')')
brackList = nestedExpr('[', ']')
printableWord = Word(printables, excludeChars="()[]")

expr = OneOrMore(parenList | brackList | quotedString | printableWord)

sample = """(([a] b) c ")" ) [d] (e) f "(a quoted) [string] with ()'s" """

import pprint
pprint.pprint(expr.parseString(sample).asList())

打印:

[[['[a]', 'b'], 'c', '")"'],
 ['d'],
 ['e'],
 'f',
 '"(a quoted) [string] with ()\'s"']

请注意,默认情况下,nestedExpr 在嵌套结构中返回解析的内容。要保留原始文本,请将表达式包装在originalTextFor

# preserve nested expressions as their original strings
from pyparsing import originalTextFor
parenList = originalTextFor(parenList)
brackList = originalTextFor(brackList)

expr = OneOrMore(parenList | brackList | quotedString | printableWord)

pprint.pprint(expr.parseString(sample).asList())

打印:

['(([a] b) c ")" )', '[d]', '(e)', 'f', '"(a quoted) [string] with ()\'s"']

【讨论】:

    【解决方案4】:

    好吧,一旦您放弃解析嵌套表达式应该在无限深度下工作的想法,您可以通过提前指定最大深度来很好地使用正则表达式。方法如下:

    def nested_matcher (n):
        # poor man's matched paren scanning, gives up after n+1 levels.
        # Matches any string with balanced parens or brackets inside; add
        # the outer parens yourself if needed.  Nongreedy.  Does not
        # distinguish parens and brackets as that would cause the
        # expression to grow exponentially rather than linearly in size.
        return "[^][()]*?(?:[([]"*n+"[^][()]*?"+"[])][^][()]*?)*?"*n
    
    import re
    
    p = re.compile('[^][()]+|[([]' + nested_matcher(10) + '[])]')
    print p.findall('(([a] b) c ) [d] (e) f')
    

    这将输出

    ['(([a] b) c )', ' ', '[d]', ' ', '(e)', ' f']
    

    这与您上面所说的不太一样,但是您的描述和示例并没有真正明确您打算对空格做什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-12
      • 1970-01-01
      • 2021-05-16
      相关资源
      最近更新 更多