如何设置多选正则从左到右匹配,规避贪婪匹配干扰?
设置多选正则表达式,使其匹配始终从左到右,不受前置贪婪捕获行为影响
原始代码
import re input_text = 'el dia corrimos juntas hasta el 11° nivel de aquella montaña hasta el 2022_-_12_-_13' #input_text = 'desde el corrimos juntas hasta el 11° nivel de aquella montaña y luego bajamos hasta la salida, hasta el 2022_-_12_-_01 21:00 hs caminamos juntas' #example 2 date_format = r"(?:\(|)\s*(\d*)_-_(\d{2})_-_(\d{2})\s*(?:\)|)" # 日期区间关联的中间文本,不能包含";"或".\s*" #some_text = r"(?:(?!\.\s*?\n)[^;])*" # 不能包含";"或".\s*\n" some_text = r"(?:(?!\.\s*)[^;])*" # 不能包含";"或".\s*" #some_text = r"(?:[^.;])*" # 不能包含";"或"." identification_re_0 = r"(?:el dia|dia|el)\s*(?:del|de\s*el|de |)\s*(" + some_text + r")\s*(?:,\s*hasta|hasta|al|a )\s*(?:el|la|)\s*" + date_format input_text = re.sub(identification_re_0, lambda m: print(m[1]), input_text, re.IGNORECASE) #print(repr(input_text)) # --> 输出
错误输出
'corrimos juntas hasta el 11° nivel de aquella montaña hast' 'corrimos juntas hasta el 11° nivel de aquella montaña y luego bajamos hasta la salida, hast'
期望正确输出
'corrimos juntas hasta el 11° nivel de aquella montaña' 'corrimos juntas hasta el 11° nivel de aquella montaña y luego bajamos hasta la salida'
疑问
为什么多选分支(?:,\s*hasta|hasta|al|a )会反向尝试匹配选项?为何它会适配前置正则(?:(?!\.\s*)[^;])*的贪婪行为?
后续尝试方案
用户给出了一个近似可行的方案,但在示例3中无法实现「当some_text未捕获内容时不添加括号」的需求,代码如下:
import re input_text = 'desde el 2022_-_12_-_10 corrimos juntas hasta el 11° nivel de aquella montaña hasta el 2022_-_12_-_13' #example 1 #input_text = 'desde el 2022_-_11_-_10 18:30 pm corrimos juntas hasta el 11° nivel de aquella montaña y luego bajamos hasta la salida, hasta el 2022_-_12_-_01 21:00 hs caminamos juntas' #example 2 #input_text = 'desde el 2022_-_11_-_10 18:30 pm hasta el 2022_-_12_-_01 21:00 hs' #example 3 # 日期区间关联的中间文本,不能包含";"或".\s*" #some_text = r"(?:(?!\.\s*?\n)[^;])*" # 不能包含";"或".\s*\n" some_text = r"(?:(?!\.\s*)[^;])*" # 不能包含";"或".\s*" #some_text = r"(?:[^.;])*" # 不能包含";"或"." identificate_hours = r"(?:a\s*las|a\s*la|)\s*(?:\(|)\s*(\d{1,2}):(\d{1,2})\s*(?:(am)|(pm))\s*(?:\)|)" # 允许不指定'am'或'pm' identificate_hours = r"(?:a\s*las|a\s*la|)\s*(?:\(|)\s*(\d{1,2}):(\d{1,2})\s*(?:(am)|(pm)|)\s*(?:\)|)" # 不允许不指定'am'或'pm' date_format = r"(?:\(|)\s*(\d*)_-_(\d{2})_-_(\d{2})\s*(?:\)|)" # 原多选分支拆分为列表逐个匹配 some_text_limiters = [r",\s*hasta", r"hasta", r"al", r"a "] for some_text_limiter in some_text_limiters: identification_re_0 = r"(?:(?<=\s)|^)(?:desde\s*el|desde|del|de\s*el|de\s*la|de |)\s*(?:día|dia|fecha|)\s*(?:del|de\s*el|de |)\s*" + date_format + r"\s*(?:" + identificate_hours + r"|)\s*(?:\)|)\s*(" + some_text + r")\s*" + some_text_limiter + r"\s*(?:el|la|)\s*(?:fecha|d[íi]a|)\s*(?:del|de\s*el|de|)\s*" + date_format + r"\s*(?:" + identificate_hours + r"|)\s*(?:\)|)" input_text = re.sub(identification_re_0, lambda m: (f"({m[1]}_-_{m[2]}_-_({m[3]}({m[4] or '00'}:{m[5] or '00'} {m[6] or m[7] or 'am'})_--_{m[9]}_-_{m[10]}_-_({m[11]}({m[12] or '00'}:{m[13] or '00'} {m[14] or m[15] or 'am'})))({m[8]})").replace(" )", ")").replace("( ", "("), input_text, re.IGNORECASE) print(repr(input_text))
解决方案
问题根源
正则引擎默认是「贪婪匹配 + 回溯」逻辑:前置的some_text用了*贪婪量词,会尽可能多捕获字符,直到后续多选分支无法匹配时,才会逐步回溯缩短前置匹配内容,尝试让多选分支适配。这就导致多选分支不是从左到右优先匹配最长选项,而是被回溯逻辑影响,最终匹配了更短的分支(比如把hasta拆成hast+a )。
核心修复
- 调整多选分支顺序+原子组阻止回溯:把最长的分支放在最前面(
,\s*hasta>hasta>al>a),并用(?>...)原子组包裹多选分支,强制引擎从左到右匹配,不允许回溯调整前置内容。 - 优化
some_text的负向预查:让some_text提前预判不会吃掉多选分支的起始字符,避免贪婪过度。
修改后的核心正则代码:
# 优化some_text:确保不会匹配到多选分支的起始字符 some_text = r"(?:(?!\.\s*|,\s*hasta|hasta|al|a )[^;])*" # 原子组包裹多选分支,阻止回溯,强制从左到右匹配最长选项 branch_group = r"(?>,\s*hasta|hasta|al|a )" identification_re_0 = r"(?:el dia|dia|el)\s*(?:del|de\s*el|de |)\s*(" + some_text + r")\s*" + branch_group + r"\s*(?:el|la|)\s*" + date_format
针对后续方案的括号优化
在lambda替换函数中判断m[8]是否为空,为空则不添加括号:
lambda m: ( f"({m[1]}_-_{m[2]}_-_({m[3]}({m[4] or '00'}:{m[5] or '00'} {m[6] or m[7] or 'am'})_--_{m[9]}_-_{m[10]}_-_({m[11]}({m[12] or '00'}:{m[13] or '00'} {m[14] or m[15] or 'am'})))" + (f"({m[8]})" if m[8].strip() else "") ).replace(" )", ")").replace("( ", "(")
内容的提问来源于stack exchange,提问作者Matt095
相关产品推荐
相关产品推荐

