Python中的正则表达式:仅当不在列表中时才将单词和数字分开

Python中的正则表达式:仅当不在列表中时才将单词和数字分开,python,regex,regex-lookarounds,Python,Regex,Regex Lookarounds,我有一份清单,里面有一些我需要保留的替代品。例如,替代列表:['1st','2nd','10th','100th','1st nation','xlr8','5pin','h20'] 通常,包含字母数字字符的字符串需要按如下方式拆分数字和字母: text = re.sub(r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)', ' ', text, 0, re.IGNORECASE) 但是,有些字母数字单词是替换列表的一部分,不能分开。例如,包含替换列表

我有一份清单,里面有一些我需要保留的替代品。例如,替代列表:
['1st','2nd','10th','100th','1st nation','xlr8','5pin','h20']

通常,包含字母数字字符的字符串需要按如下方式拆分数字和字母:

text = re.sub(r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)', ' ', text, 0, re.IGNORECASE)
但是,有些字母数字单词是替换列表的一部分,不能分开。例如,包含替换列表一部分的
1ST
的以下字符串不应分开,应省略它们,而不是添加空格:

Original            Regex                Expected
1ST DEF 100CD  -->  1 ST DEF 100 CD  --> 1ST DEF 100 CD
ABC 1ST 100CD  -->  ABC 1 ST 100 CD  --> ABC 1ST 100 CD
100TH DEF 100CD ->  100 TH DEF 100 CD -> 100TH DEF 100 CD
10TH DEF 100CD  ->  10 TH DEF 100 CD  -> 10TH DEF 100 CD 
为了在上面的示例中获得预期的列,我尝试在regex中使用
IF-THEN-ELSE
方法,但是Python中的语法出现了一个错误:

(?(?=condition)(then1|then2|then3)|(else1|else2|else3))
根据语法,我应该有如下内容:

Original       Regex
ABC10 DEF  --> ABC 10 DEF
ABC DEF10  --> ABC DEF 10
ABC 10DEF  --> ABC 10 DEF
10ABC DEF  --> 10 ABC DEF

?(?!1ST)((?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)))

?(?!1ST)((?您可以使用lambda函数检查匹配字符串是否在排除列表中:

import re

subs = ['1st','2nd','1st nation','xlr8','5pin','h20']
text = """
ABC10 DEF
1ST DEF 100CD
ABC 1ST 100CD
AN XLR8 45X
NO H20 DEF
A4B PLUS
"""

def add_spaces(m):
    if m.group().lower() in subs:
        return m.group()
    res = m.group(1)
    if len(res):
        res += ' '
    res += m.group(2)
    if len(m.group(3)):
        res += ' '
    res += m.group(3)
    return res

text = re.sub(r'\b([^\d\s]*)(\d+)([^\d\s]*)\b', lambda m: add_spaces(m), text)
print(text)
输出:

ABC 10 DEF
1ST DEF 100 CD
ABC 1ST 100 CD
AN XLR8 45 X
NO H20 DEF
A 4 B PLUS
1ST DEF 100 CD
ABC 1ST 100 CD
WEST 12TH APARTMENT
您可以将lambda函数简化为

def add_spaces(m):
    if m.group().lower() in subs:
        return m.group()
    return m.group(1) + ' ' + m.group(2) + ' ' + m.group(3)
但这可能会导致输出字符串中出现额外的空白

text = re.sub(r' +', ' ', text)

使用
regex
(*跳过)(*失败)
f-strings
的另一种方法:

import regex as re

lst = ['1st','2nd','1st nation','xlr8','5pin','h20']

data = """
ABC10 DEF
ABC DEF10
ABC 10DEF
10ABC DEF
1ST DEF 100CD
ABC 1ST 100CD"""

rx = re.compile(
    rf"""
    (?:{"|".join(item.upper() for item in lst)})(*SKIP)(*FAIL)
    |
    (?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)
    """, re.X)

data = rx.sub(' ', data)
print(data)

处理异常时,最简单、最安全的方法是使用“”方法。替换时,此技巧意味着:保留捕获的内容,删除匹配的内容,反之亦然。在正则表达式中,您必须使用替换,并在一个(或复杂场景中的一些)周围使用捕获组在遇到匹配后,他们能够分析匹配结构

因此,首先,使用异常列表构建替换的第一部分:

exception_rx = "|".join(map(re.escape, exceptions))
注意
re.escape
在需要时添加反斜杠,以支持异常中的任何特殊字符。如果您的异常都是字母数字,则不需要反斜杠,您只需使用
exception_rx=“|”。.join(exceptions)
。甚至
exception_rx=rf'\b(?:{“|”。join(exceptions)})\b'
仅将它们作为整词进行匹配

下一步,您需要一个模式,该模式将查找所有匹配项,而不考虑上下文,即I:

并使用
.sub()替换:

在这里,
lambda x:x.group(1)或“
表示如果组1匹配,则返回组1值,否则用空格替换

见:


您误解了“conditional”这个词。这个构造没有帮助。您可以使用负lookaheads来限制数字检查,如
(?另一个想法:
re.sub(r'\s*)(?)?!(?谢谢@WiktorStribiżew。它很有魅力。你能推荐一些regex书籍来详细学习吗?环顾四周?我仍然不确定regex是如何一步一步地处理的。其他答案对你有帮助吗?或者我应该发布上面的任何解决方案并进行解释吗?谢谢@WiktorStribiżew,你的解决方案有效,但我不知道我对lookbehind中嵌套的负向前看有点困惑。我试图弄清楚,当regex与
1ST
不同时,它是如何一步一步地添加空格的,而当is
1ST
时,它是如何不添加空格的
,lambda中的x为每次迭代获取字符串,例如
1ST DEF 100CD
。但是,如果我直接尝试s.group(1),我得到的是
str对象没有属性组
。如果它不是来自string_lst的字符串,lambda中的x是什么?为什么我不能用text=re.sub(r'({exception_rx})获得相同的结果|{generic_rx}',r'\1'或'',s)?@Juan您不能使用字符串替换模式,因为如果组1匹配,您需要一个替换,如果组1不匹配,则需要另一个替换。我在Postgresql中重用了正则表达式模式。没有lambda,我怎么能得到相同的结果?这与第一条注释有关。非常感谢。@JuanPerez您在Postgresql中不能这样做,这些函数不支持端口回调作为替换参数。
generic_rx = r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)'
rx = re.compile(rf'({exception_rx})|{generic_rx}', re.I)   
s = rx.sub(lambda x: x.group(1) or " ", s)
import re

exceptions = ['1st','2nd','10th','100th','1st nation','xlr8','5pin','h20', '12th'] # '12th' added
exception_rx = '|'.join(map(re.escape, exceptions))
generic_rx = r'(?<=\d)(?=[^\d\s])|(?<=[^\d\s])(?=\d)'
rx = re.compile(rf'({exception_rx})|{generic_rx}', re.I)

string_lst = ['1ST DEF 100CD','ABC 1ST 100CD','WEST 12TH APARTMENT']
for s in string_lst:
    print(rx.sub(lambda x: x.group(1) or " ", s))
1ST DEF 100 CD
ABC 1ST 100 CD
WEST 12TH APARTMENT