我已经坐了2天了,但仍然没有找到有效的方法。 假设我们有以下字符串:
<< strong> 5629476219 <421fsaas42f> 14222 <2412f2 <2421savsar21> 12vsaf21> 412 <<< 142avsa1> 1a24> 421> 421 >
我想要的输出:
<562947621914222412421>
好吧,递归地在<>括号内可能有数据,它可以由数字和字母组成-但是第一级仅由数字组成。 我合并我要提取的数据。
我想用Python做到这一点。天真的方法当然是实现一个括号栈(这样我就知道我是在一个内部括号内还是在第一个级别),但是逐个字符进行效率很低。 我相信我可以使用一种很好的正则表达式模式,但是我还没有想出可行的方法。
对正则表达式有足够经验的人可以提供一些帮助吗?
当然,除了逐字符逐个运行char之外,其他想法也很受欢迎,运行时间对我很重要。
答案 0 :(得分:4)
有一个alternative regex module available in Python允许recursive patterns。
您可以使用such pattern for balanced brackets并将其替换为空字符串。
regex.sub(r'(?!^)<(?:[^><]*|(?R))+>', '', s)
See this regex101 demo或Python demo导致
<562947621914222412421>
答案 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>'