You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何设置多选正则从左到右匹配,规避贪婪匹配干扰?

设置多选正则表达式,使其匹配始终从左到右,不受前置贪婪捕获行为影响

原始代码

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 )。

核心修复

  1. 调整多选分支顺序+原子组阻止回溯:把最长的分支放在最前面(,\s*hasta > hasta > al > a ),并用(?>...)原子组包裹多选分支,强制引擎从左到右匹配,不允许回溯调整前置内容。
  2. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 10:05:29