pyparsing中NotAny(FollowedBy())匹配失败问题求助
问题描述
输入数据:
[gog1] [G1] [gog2] [gog3] [gog4] [G2] [gog5] [G3] [gog6]
需求:匹配所有后面未紧跟G类型项的gog类型项,预期结果为gog2、gog3、gog6。
尝试三种pyparsing模式均失败:
- 使用
pattern = poi_gog + ~poi_g仅匹配到gog6; - 使用
pattern = poi_gog + NotAny(FollowedBy(poi_g))同样仅匹配到gog6; - 使用
pattern = poi_gog + poi_not_g.suppress()仅匹配到gog2。
测试代码如下:
import pyparsing as pp from pyparsing import * def pyparsing_test(): # this also dont helps # ParserElement.enable_left_recursion(force=True) data=""" [gog1] [G1] [gog2] [gog3] [gog4] [G2] [gog5] [G3] [gog6] """ poi_type = Word(alphas).set_results_name('type') poi = Suppress('[') + poi_type + Char(nums) + Suppress(']') def cnd_is_type(val): return lambda toks: toks.type==val def cnd_is_not_type(val): return lambda toks: toks.type!=val poi_gog=poi('gog').add_condition(cnd_is_type('gog')) poi_g=poi('g').add_condition(cnd_is_type('G')) poi_not_g=poi('not_g').add_condition(cnd_is_not_type('G')) pattern = poi_gog + ~poi_g #WTF this finds only `gog6`, why?? pattern = poi_gog + NotAny(FollowedBy(poi_g)) #WTF same, only `gog6` pattern = poi_gog + poi_not_g.suppress() #WTF this works better but find only `gog2`, why not `gog3` also? r=pattern.search_string(data) print(data) print('='*10) print(r)
失败原因分析
前两种模式仅匹配gog6:
这两种写法要求poi_gog直接后续内容不是poi_g,但search_string默认是非重叠匹配,会从左到右消耗输入。比如匹配gog2时,后续的gog3会被当作匹配段的一部分消耗掉,导致无法再匹配gog3;只有末尾的gog6后面无内容,满足条件。第三种模式仅匹配gog2:
该写法要求poi_gog必须紧跟一个非G类型的poi,gog2符合条件会被匹配,但search_string消耗到gog3后不会回头匹配,同时该写法也不支持“后面无内容”的场景(比如gog6)。
正确实现方案
方法一:全量解析后筛选(直观易维护)
先解析所有poi,再逐个判断每个gog项的下一个元素是否符合要求:
import pyparsing as pp from pyparsing import * def pyparsing_test(): data=""" [gog1] [G1] [gog2] [gog3] [gog4] [G2] [gog5] [G3] [gog6] """ poi_type = Word(alphas).set_results_name('type') poi = Suppress('[') + poi_type + Char(nums) + Suppress(']') # 解析所有poi all_pois = poi.search_string(data) # 筛选符合条件的gog项 result = [] for idx, poi_tok in enumerate(all_pois): if poi_tok.type == 'gog': # 满足:是最后一个项,或者下一个项不是G类型 if idx == len(all_pois)-1 or all_pois[idx+1].type != 'G': result.append(f"{poi_tok.type}{poi_tok[1]}") print(data) print('='*10) print(result) pyparsing_test()
输出结果:
[gog1] [G1] [gog2] [gog3] [gog4] [G2] [gog5] [G3] [gog6] ========== ['gog2', 'gog3', 'gog6']
方法二:用scan_string实现直接匹配
如果要直接用pyparsing匹配模式,替换search_string为scan_string(支持遍历所有可能的匹配位置,不强制消耗输入):
import pyparsing as pp from pyparsing import * def pyparsing_test(): data=""" [gog1] [G1] [gog2] [gog3] [gog4] [G2] [gog5] [G3] [gog6] """ poi_type = Word(alphas).set_results_name('type') poi = Suppress('[') + poi_type + Char(nums) + Suppress(']') def cnd_is_type(val): return lambda toks: toks.type==val poi_gog=poi('gog').add_condition(cnd_is_type('gog')) poi_g=poi('g').add_condition(cnd_is_type('G')) # 匹配gog且后续不是G类型项 pattern = poi_gog + ~FollowedBy(poi_g) # 用scan_string遍历所有匹配位置 result = [] for tokens, _, _ in pattern.scan_string(data): result.append(f"{tokens.type}{tokens[1]}") print(data) print('='*10) print(result) pyparsing_test()
输出结果与预期一致。
内容的提问来源于stack exchange,提问作者Jhon BYaka
相关产品推荐
相关产品推荐

