正则表达式模式递归-在python中

时间:2019-12-21 15:07:17

标签: python regex algorithm pattern-matching

我已经坐了2天了,但仍然没有找到有效的方法。 假设我们有以下字符串:

  

<< strong> 5629476219 <421fsaas42f> 14222 <2412f2 <2421savsar21> 12vsaf21> 412 <<< 142avsa1> 1a24> 421> 421 >

我想要的输出:

  

<562947621914222412421>

好吧,递归地在<>括号内可能有数据,它可以由数字和字母组成-但是第一级仅由数字组成。 我合并我要提取的数据。

我想用Python做到这一点。天真的方法当然是实现一个括号栈(这样我就知道我是在一个内部括号内还是在第一个级别),但是逐个字符进行效率很低。 我相信我可以使用一种很好的正则表达式模式,但是我还没有想出可行的方法。

对正则表达式有足够经验的人可以提供一些帮助吗?

当然,除了逐字符逐个运行char之外,其他想法也很受欢迎,运行时间对我很重要。

4 个答案:

答案 0 :(得分:4)

有一个alternative regex module available in Python允许recursive patterns

您可以使用such pattern for balanced brackets并将其替换为空字符串。

regex.sub(r'(?!^)<(?:[^><]*|(?R))+>', '', s)

See this regex101 demoPython demo导致

  

<562947621914222412421>

(?R)处,图案从一开始就粘贴,就像(?0)一样。使用(?!^)来省略first <

答案 1 :(得分:4)

  

当然,除了逐字符逐个运行char之外,其他想法也很受欢迎,运行时间对我很重要。

当然,任何正则表达式也必须逐字符遍历字符串。不要轻易排除“天真的”解决方案:事实证明,这种简单的方法比到目前为止所发布的所有三个答案更有效。


这是一个像您的“天真的”解决方案:但它不需要堆栈,因为只有一种开放式括号。即使有多种类型的括号,如果您还想检测出括号何时不匹配,也只需要堆叠即可。

def chars_at_level(s):
    out = ['<']
    nesting_level = 0

    for c in s:
        if c == '<':
            nesting_level += 1
        elif c == '>':
            nesting_level -= 1
        elif nesting_level == 1:
            out.append(c)

    out.append('>')
    return ''.join(out)

示例:

>>> s = '<5629476219<421fsaas42f>14222<2412f2<2421savsar21>12vsaf21>412<<<142avsa1>1a24>421>421>'
>>> chars_at_level(s)
'<562947621914222412421>'

现在进行性能比较。尽管Seb的解决方案很接近,但它击败了其他三个解决方案。

>>> timeit(lambda: chars_at_level(s))
7.594452977000401
>>> timeit(lambda: parse(s)) # Seb's solution using re.sub
7.817124693000096
>>> timeit(lambda: regex_sub(s)) # bobble bubble's recursive regex
9.322779934999744
>>> timeit(lambda: nested_list(s)) # Ajax1234's nested list solution
17.795835303999866

但是,在最坏的情况下,Seb的解决方案在<<<<<<1>>>>>>之类的字符串上效率低得多,因为它在长度为O( n)的字符串上执行O( n )替换),运行时间为O( n ²)。在此字符串上,其他两个发布的解决方案似乎仍然是O( n ),尽管我必须增加系统递归限制才能使用Ajax1234的解决方案。 “天真的”解决方案仍然是最快的。

>>> t = (1000 * '<') + '1' + (1000 * '>')
>>> timeit(lambda: chars_at_level(t), number=1000)
0.1329130509998322
>>> timeit(lambda: parse(t), number=1000) # Seb's solution using re.sub
31.281542531000014
>>> timeit(lambda: regex_sub(t), number=1000) # bobble bubble's recursive regex
0.705901896999876
>>> timeit(lambda: nested_list(t), number=1000) # Ajax1234's nested list solution
1.1296931150000091

顺便说一句,即使您确实想通过堆栈来扩充“天真的”解决方案,它仍然只需要O( n )时间。更改此算法以使字符处于任何其他嵌套级别也很简单。

答案 2 :(得分:3)

这是一种方法;它以递归方式删除内部完整标签<[^<>]*>,直到仅保留外部级别元素为止:

def parse(string):
    while True:
        output = re.sub(r'(?<!^)<([^<>]*)>(?!$)', '', string)
        if output == string:
            break
        string = output
    return output
>>> string = '<5629476219<421fsaas42f>14222<2412f2<2421savsar21>12vsaf21>412<<<142avsa1>1a24>421>421>'
>>> parse(string)
'<562947621914222412421>'
>>> %timeit parse(string)
6.57 µs ± 99.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

在正则表达式本身中也应该有一种方法来进行递归,但是我不能完全实现它。内置模块re不支持此功能,但是regex支持。 Here就是一些想法。

答案 3 :(得分:2)

您可以使用递归从结构创建嵌套列表。然后,要产生所需的结果,您只需在列表中找到所有顶级字符串:

import re
data = '<5629476219<421fsaas42f>14222<2412f2<2421savsar21>12vsaf21>412<<<142avsa1>1a24>421>421>'
def get_data(d):
  if (val:=next(d, None)) not in {'<', '>'}:
     yield val
  if val == '<':
     yield list(get_data(d))
  if val is not None and val != '>':
     yield from get_data(d)

result = '<'+''.join(i for i in list(get_data(iter(re.findall('[^\<\>]+|[\<\>]', data))))[0] if isinstance(i, str))+'>'

输出:

'<562947621914222412421>'