【问题标题】:Python/Regex - Expansion of Parentheses and SlashesPython/Regex - 括号和斜线的扩展
【发布时间】:2013-08-16 01:21:40
【问题描述】:

我正在寻找一种方法来扩展由斜杠分隔的数字。除了斜线,括号可以用在一些(或所有)数字周围来表示一个“组”,它可以重复(直接在括号后面的次数)或反向重复(后面跟着's'作为在第二组示例中显示)。一些例子是:

1          -> ['1']                          -> No slashes, no parentheses
1/2/3/4    -> ['1', '2', '3', '4']           -> basic example with slashes
1/(2)4/3   -> ['1', '2', '2', '2', '2', '3'] -> 2 gets repeated 4 times
1/(2/3)2/4 -> ['1', '2', '3', '2', '3', '4'] -> 2/3 is repeated 2 times
(1/2/3)2   -> ['1', '2', '3', '1', '2', '3'] -> Entire sequence is repeated twice
(1/2/3)s   -> ['1', '2', '3', '3', '2', '1'] -> Entire sequence is repeated in reverse
1/(2/3)s/4 -> ['1', '2', '3', '3', '2', '4'] -> 2/3 is repeated in reverse

在最一般的情况下,甚至可能有嵌套的括号,我知道这通常会使正则表达式的使用变得不可能。在我需要处理的当前数据集中,没有这样的嵌套集,但我可以看到它在未来的潜在用途。例如:

1/(2/(3)2/4)s/5 -> 1/(2/3/3/4)s/5
                -> 1/2/3/3/4/4/3/3/2/5
                -> ['1', '2', '3', '3', '4', '4', '3', '3', '2', '5']

我当然知道正则表达式不能做到所有这些(尤其是重复/反转括号集)。但是,如果我能得到一个正则表达式,它至少将括号中的字符串与括号中的字符串分开,那么我可能可以很容易地制作一些循环来处理其余部分。所以,我正在寻找的正则表达式会做这样的事情:

1               -> ['1']
1/2/3/4         -> ['1', '2', '3', '4']
1/(2)4/3        -> ['1', '(2)4', '3']
1/(2/3)2/4      -> ['1', '(2/3)2', '4']
1/(2/(3)2/4)s/5 -> ['1', '(2/(3)/2/4)s', '5']

然后我可以循环这个结果并继续扩展任何括号,直到我只有数字。

编辑

我在原始帖子中并不完全清楚。在我试图使示例尽可能简单的过程中,我可能过度简化了它们。这需要适用于 >= 10 的数字以及负数。

例如:

1/(15/-23)s/4   -> ['1', '(15/-23)s', '4']
                -> ['1', '15', '-23', '-23', '15', '4']

【问题讨论】:

    标签: python regex


    【解决方案1】:

    由于您正在处理嵌套括号,因此正则表达式在此帮不上什么忙。它不能像你最后想要的那样轻松地将字符串转换为列表。

    您最好自己解析字符串。你可以试试这个代码,只是为了满足你最后的要求:

    在不丢失括号的情况下将字符串解析为列表:

    def parse(s):
    
        li = []
    
        open = 0
        closed = False
        start_index = -1
    
        for index, c in enumerate(s):
            if c == '(':
                if open == 0:
                    start_index = index
                open += 1
    
            elif c == ')':
                open -= 1
                if open == 0:
                    closed = True
    
            elif closed:
                li.append(s[start_index: index + 1])
                closed = False
    
            elif open == 0 and c.isdigit():
                li.append(c)
    
        return li
    

    这将为字符串'1/(2/(3)2/4)s/5' 提供以下列表:

    ['1', '(2/(3)2/4)s', '5']
    

    对于字符串'1/(15/-23)s/4',根据您更改的要求,这给出:

    ['1', '(15/-23)s', '4']
    

    现在,您需要注意将括号进一步向上分解以获得不同的列表元素。


    将带括号的字符串扩展为单个列表元素:

    在这里您可以使用正则表达式,只需一次处理最内层的括号:

    import re
    
    def expand(s):
    
        ''' Group 1 contains the string inside the parenthesis
            Group 2 contains the digit or character `s` after the closing parenthesis
    
        '''    
        match = re.search(r'\(([^()]*)\)(\d|s)', s)
        if match:
            group0 = match.group()
            group1 = match.group(1)
            group2 = match.group(2)
            if group2.isdigit():
                # A digit after the closing parenthesis. Repeat the string inside
                s = s.replace(group0, ((group1 + '/') * int(group2))[:-1])
            else:
                s = s.replace(group0, '/'.join(group1.split('/') + group1.split('/')[::-1]))
    
        if '(' in s:
            return expand(s)
    
        return s
    
    li = parse('1/(15/-23)2/4')
    
    for index, s in enumerate(li):
        if '(' in s:
            s = expand(s)
            li[index] = s.split('/')
    
    import itertools
    print list(itertools.chain(*li))
    

    这将为您提供所需的结果:

    ['1', '15', '-23', '-23', '15', '4']
    

    上面的代码遍历parse(s)方法生成的列表,然后对每个元素递归展开最里面的括号。

    【讨论】:

    • 查看我的编辑。它似乎不适用于大于 10 的数字或负数。我没有在我的帖子中这么说,但其他解决方案之一似乎可以处理它们。
    • @ScottB。好吧,您从未说过,您需要处理大于 10 的数字或负数,正如您所说。但是,您的问题得到了解决,这很好。
    • 对,我没有提到它,但我可能会结合使用 Joowani 的帖子和你的后半部分来扩大括号。谢谢。
    • @ScottB。编辑 expand 方法来处理负数和数字 > 10
    • @ScottB。是的,看来expand 本身就足够了。
    【解决方案2】:

    这是完成此任务的另一种方法。

    def expand(string):
    
        level = 0
        buffer = ""
        container = []
    
        for char in string:
    
            if char == "/":
                if level == 0:
                    container.append(buffer)
                    buffer = ""
                else:
                    buffer += char
            elif char == "(":
                level += 1
                buffer += char
            elif char == ")":
                level -= 1
                buffer += char
            else:
                buffer += char
    
        if buffer != "":
            container.append(buffer)
    
        return container
    

    【讨论】:

    • 谢谢。这正是我在第二部分中要求的,但 Rohit 的回答实际上做了一切。也赞成这个。
    【解决方案3】:

    正则表达式对于这项工作来说是完全错误的工具。关于为什么正则表达式不合适有一个冗长的解释(If you want to know why, here's an online course)。一个简单的递归解析器很容易编写来处理这个问题,您可能在完成正则表达式调试之前就已经完成了。

    这是一个缓慢的一天,所以我自己写了它,并完成了文档测试。

    def parse(s):
        """
        >>> parse('1')
        ['1']
        >>> parse('1/2/3/4')
        ['1', '2', '3', '4']
        >>> parse('1/(2)4/3')
        ['1', '2', '2', '2', '2', '3']
        >>> parse('1/(2/3)2/4')
        ['1', '2', '3', '2', '3', '4']
        >>> parse('(1/2/3)2')
        ['1', '2', '3', '1', '2', '3']
        >>> parse('1/(2/3)s/4')
        ['1', '2', '3', '3', '2', '4']
        >>> parse('(1/2/3)s')
        ['1', '2', '3', '3', '2', '1']
        >>> parse('1/(2/(3)2/4)s/5')
        ['1', '2', '3', '3', '4', '4', '3', '3', '2', '5']
        """
        return _parse(list(s))
    
    
    def _parse(chars):
        output = []
        while len(chars):
            c = chars.pop(0)
            if c == '/':
                continue
            elif c == '(':
                sub = _parse(chars)
                nextC = chars.pop(0)
                if nextC.isdigit():
                    n = int(nextC)
                    sub = n * sub
                    output.extend(sub)
                elif nextC == 's':
                    output.extend(sub)
                    output.extend(reversed(sub))
            elif c == ')':
                return output
            else:
                output.extend(c)
    
        return output
    
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    

    【讨论】:

      猜你喜欢
      • 2014-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多