如何在使用正则表达式将行拆分为单词时转义特定的空格

时间:2018-04-10 23:51:57

标签: python regex

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

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来处理。我不知道如何在一次拆分中组合两个正则表达式。

热烈欢迎任何暗示......

2 个答案:

答案 0 :(得分:1)

PCRE regex引擎中,sub-routine受支持且recursive pattern似乎适用于包含balanced nested括号的案例。

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

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

但是python不支持(?1)中的sub-routine模式。

所以我首先尝试将每个regex(替换为另一个与众不同的角色(在此示例中为))并应用正则表达式进行拆分并最终将@转回我的pythone脚本中分别有@(

分裂的正则表达式。

)

Demo ,,,其中我将您的分隔符(?m)(\s+)(?=[^@]*(?:(?:@[^@]*){2})*$) 更改为连续的空格\S+,因为包含\s+@()'[\S]中。

Python脚本可能是这样的

possible characters set

输出

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)

答案 1 :(得分:0)

最后,在根据@WiktorStribiż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()不被视为很特别。如果要在任何一对括号内转义空格,只需删除正则表达式中的%字符。以下是修改后的结果:

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)', '  ']