使用regex将行拆分为单词时如何转义特定的空格

2024-05-23 16:47:51 发布

您现在位置:Python中文网/ 问答频道 /正文

我想将一个字符串拆分为一个单词列表(这里“word”表示非空格字符的任意序列),但也要保留用作分隔符的连续空格组(因为空格的数量在我的数据中很重要)。对于这个简单的任务,我知道下面的regex可以完成这项工作(我使用Python作为说明性语言,但是代码可以很容易地适应任何语言,包括regex):

import re
regexA = re.compile(r"(\S+)")
print(regexA.split("aa b+b   cc dd!    :ee  "))

生成预期输出:

['', 'aa', ' ', 'b+b', '   ', 'cc', ' ', 'dd!', '    ', ':ee', '  ']

现在最困难的部分是:当一个单词包含一个左括号时,在匹配的右括号之前遇到的所有空格都不应被视为单词分隔符。换句话说:

regexB.split("aa b+b   cc(dd! :ee (ff gg) hh) ii  ")

应产生:

['', 'aa', ' ', 'b+b', '   ', 'cc(dd! :ee (ff gg) hh)', ' ', 'ii', '  ']

使用

regexB = re.compile(r'([^(\s]*\([^)]*\)|\S+)')

适用于一对圆括号,但在有内圆括号时失败。如何改进正则表达式以正确跳过内圆括号?你知道吗

最后一个问题:在我的数据中,只有以%开头的单词应该被测试“括号规则”(regexB),其他单词应该被regexA处理。我不知道如何在一次拆分中合并两个正则表达式。你知道吗

欢迎任何提示。。。你知道吗


Tags: 数据re语言单词ddeeregex括号
2条回答

最后,基于@Wiktor Stribiżew和@Thm Lee提出的答案,在测试了几个想法之后,我找到了一堆处理不同复杂程度的解决方案。为了减少依赖性,我想继续使用Python标准库中的re模块,下面是代码:

import re

text = "aa b%b(   %cc(dd! (:ee ff) gg) %hh ii)  "

# Solution 1: don't process parentheses at all
regexA = re.compile(r'(\S+)')
print(regexA.split(text))

# Solution 2: works for non-nested parentheses
regexB = re.compile(r'(%[^(\s]*\([^)]*\)|\S+)')
print(regexB.split(text))

# Solution 3: works for one level of nested parentheses
regexC = re.compile(r'(%[^(\s]*\((?:[^()]*\([^)]*\))*[^)]*\)|\S+)')
print(regexC.split(text))

# Solution 4: works for arbitrary levels of nested parentheses
n, words = 0, []
for word in regexA.split(text):
    if n: words[-1] += word
    else: words.append(word)
    if n or (word and word[0] == '%'):
        n += word.count('(') - word.count(')')
print(words)

以下是生成的输出:

Solution 1: ['', 'aa', ' ', 'b%b(', '   ', '%cc(dd!', ' ', '(:ee', ' ', 'ff)', ' ', 'gg)', ' ', '%hh', ' ', 'ii)', '  ']
Solution 2: ['', 'aa', ' ', 'b%b(', '   ', '%cc(dd! (:ee ff)', ' ', 'gg)', ' ', '%hh', ' ', 'ii)', '  ']
Solution 3: ['', 'aa', ' ', 'b%b(', '   ', '%cc(dd! (:ee ff) gg)', ' ', '%hh', ' ', 'ii)', '  ']
Solution 4: ['', 'aa', ' ', 'b%b(', '   ', '%cc(dd! (:ee ff) gg)', ' ', '%hh', ' ', 'ii)', '  ']

如OP中所述,对于我的特定数据,括号中的转义空格只能用于以%开头的单词,其他括号(例如我的示例中的单词b%b()不被认为是特殊的。如果要转义任何一对括号中的空格,只需删除regex中的%字符。以下是修改后的结果:

Solution 1: ['', 'aa', ' ', 'b%b(', '   ', '%cc(dd!', ' ', '(:ee', ' ', 'ff)', ' ', 'gg)', ' ', '%hh', ' ', 'ii)', '  ']
Solution 2: ['', 'aa', ' ', 'b%b(   %cc(dd! (:ee ff)', ' ', 'gg)', ' ', '%hh', ' ', 'ii)', '  ']
Solution 3: ['', 'aa', ' ', 'b%b(   %cc(dd! (:ee ff) gg)', ' ', '%hh', ' ', 'ii)', '  ']
Solution 4: ['', 'aa', ' ', 'b%b(   %cc(dd! (:ee ff) gg) %hh ii)', '  ']

PCRE regex引擎中,支持sub-routine,并且recursive pattern对于包含balanced nested括号的情况似乎是可行的。你知道吗

(?m)\s+(?=[^()]*(\([^()]*(?1)?[^()]*\))*[^()]*$)

Demo,,,其中(?1)表示调用子程序1,(\([^()]*(?1)?[^()]*\)),即recursive pattern,其中包括caller(?1)

但是python不支持regex中的sub-routine模式。你知道吗

因此,我首先尝试用另一个不同的字符(@替换每个()),然后应用正则表达式进行拆分,最后在pythone脚本中分别将@转换回()。你知道吗

用于拆分的正则表达式。你知道吗

(?m)(\s+)(?=[^@]*(?:(?:@[^@]*){2})*$)

Demo,,,其中我将分隔符\S+更改为连续空格\s+,因为@()包含在[\S]'possible characters set中。你知道吗

Python脚本可能是这样的

import re
ss="""aa b+b   cc(dd! :ee ((ff gg)) hh) ii  """
ss=re.sub(r"\(|\)","@",ss)      #repacing every `(`,`)` to `@`

regx=re.compile(r"(?m)(\s+)(?=[^@]*(?:(?:@[^@]*){2})*$)")
m=regx.split(ss)
for i in range(len(m)):         # turn `@` back to `(` or `)` respectively 
    n= m[i].count('@')
    if n < 2: continue
    else: 
        for j in range(int(n/2)):
            k=m[i].find('@'); m[i]=m[i][:k]+'('+m[i][k+1:]
        m[i]= m[i].replace("@",')')
print(m)

输出为

['aa', ' ', 'b+b', '   ', 'cc(dd! :ee ((ff gg)) hh)', ' ', 'ii', '  ', '']

相关问题 更多 >