菜鸟请教正则表达式问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
noming
V2EX    问与答

菜鸟请教正则表达式问题

  •  
  •   noming 2019-09-21 06:15:55 +08:00 2516 次点击
    这是一个创建于 2217 天前的主题,其中的信息可能已经有所发展或是发生改变。

    想匹配一段文字中"start"到"end"之间的内容, 文中除了"start"和"end"其他的可能是任意字符, 该段文字中可能存在多个"start", 怎样写正则表达式只匹配从"end"之前最近的那个"start"到"end"之间的内容.

    Situated about 150 miles (240 km) north of Las Vegas, the remote start hamlet of just 50 year-round start residents lacks a grocery store end or even a gasoline station.

    如上段文字, 只匹配"start residents lacks a grocery store end". 写了好久没弄出来, 也不知道如何搜索该问题. 谢了!

    25 条回复    2019-09-21 17:26:06 +08:00
    delectate
        1
    delectate  
       2019-09-21 06:46:55 +08:00   1
    C:\Users\Delectate>python
    Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import re
    >>> tmpStr="Situated about 150 miles (240 km) north of Las Vegas, the remote start hamlet of just 50 year-round start residents lacks a grocery store end or even a gasoline station."
    >>> re.findall(r'start.*end', tmpStr)
    ['start hamlet of just 50 year-round start residents lacks a grocery store end']
    >>>
    Nasei
        2
    Nasei  
       2019-09-21 07:14:06 +08:00   2
    \bstart((?!start).)*end\b
    geelaw
        3
    geelaw  
       2019-09-21 07:30:27 +08:00   3
    用理论帮助思考,考虑一个 NFA,它的状态是
    x/s/st/sta/star/start/starts/startst/startsta/startstar/startstart/starte/starten/startend
    你希望这个机器接受 start 开头 end 结尾且中间没有 start 或者 end 的字符串,初始状态是 x。

    用 q+r = q' 表示 q 之后看到 r 就进入 q',只有 startend 是接受状态

    读入最开始的 start:
    x+s = s
    s+t = st
    st+a = sta
    sta+r = star
    star+t = start

    中间可能出现 start:
    start/starts/startst/startsta/startstar/starte/starten+s = starts
    starts+t = startst
    startst+a = startsta
    startsta+r = startstar
    startstar+t = startstart

    中间可能出现 end:
    start/starts/startst/startsta/startstar/starte/starten+e = starte
    starte+n=starten
    starten+d=startend

    任何其他字符:
    start+[^se] = start
    starts+[^set] = start
    startst+[^sea] = start
    startsta+[^ser] = start
    startstar+[^set] = start
    starte+[^sen] = start
    starten+[^sed] = start

    通过 NFA 转换为正则表达式的算法得出一个写法是这样的

    start((e(ne|e)*(ns|s)|s)(t(a(r(t(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(te(ne|e)*(n[^sed]|[^sen])|e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^ser])|e(ne|e)*(n[^sed]|[^sen])|[^sea])|e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^se])*((e(ne|e)*(ns|s)|s)(t(a(r(t(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(te(ne|e)*nd|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)((.(e(ne|e)*(ns|s)|s)(t(a(r(t(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(te(ne|e)*(n[^sed]|[^sen])|e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^ser])|e(ne|e)*(n[^sed]|[^sen])|[^sea])|e(ne|e)*(n[^sed]|[^sen])|[^set])|.e(ne|e)*(n[^sed]|[^sen]))((e(ne|e)*(ns|s)|s)(t(a(r(t(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(te(ne|e)*(n[^sed]|[^sen])|e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^ser])|e(ne|e)*(n[^sed]|[^sen])|[^sea])|e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^se])*((e(ne|e)*(ns|s)|s)(t(a(r(t(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(te(ne|e)*nd|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|.(e(ne|e)*(ns|s)|s)(t(a(r(t(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(te(ne|e)*nd|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|.e(ne|e)*nd)*
    geelaw
        4
    geelaw  
       2019-09-21 07:34:02 +08:00
    呃,上面那个似乎有错误,正确的结果应该是:

    start((e(ne|e)*(ns|s)|s)(t(a(r(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(r(e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^ser])|e(ne|e)*(n[^sed]|[^sen])|[^sea])|e(ne|e)*(n[^sed]|[^sen])|[^set])|e(ne|e)*(n[^sed]|[^sen])|[^se])*((e(ne|e)*(ns|s)|s)(t(a(r(e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)|e(ne|e)*(ns|s)|s)*(t(a(re(ne|e)*nd|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)|e(ne|e)*nd)
    IvanLi127
        5
    IvanLi127  
       2019-09-21 08:49:30 +08:00 via Android   1
    本来感觉还会写的,看完楼上大佬解答,我怂了
    xml123
        6
    xml123  
       2019-09-21 09:04:29 +08:00
    想到一个比较笨的方法,如果只有一个 end,可以考虑字符串逆序然后用懒惰模式
    noming
        7
    noming  
    OP
       2019-09-21 09:09:37 +08:00
    @delectate 这个还是会包含第一个"start"

    @Nasei 简洁明了, 多谢! 明白了(?!abc)的用法

    @geelaw 理论功底深厚, 谢谢!

    @xml123 逆序怎么操作? 我查查资料
    noming
        8
    noming  
    OP
       2019-09-21 09:11:10 +08:00
    @IvanLi127 膜拜各位大佬
    xml123
        9
    xml123  
       2019-09-21 09:13:25 +08:00
    @noming #7 字符串逆序之后,dne.*?trats
    noqwerty
        10
    noqwerty  
       2019-09-21 09:22:58 +08:00   1
    或者直接 re.search("(start.*)?(stat.*end)", s).group(2)
    ipwx
        11
    ipwx  
       2019-09-21 09:25:12 +08:00
    零款断言能解决的事情,3L 搞这么复杂,无语了。

    http://ideone.com/G6SzMw
    ipwx
        12
    ipwx  
       2019-09-21 09:27:55 +08:00 via Android
    @noqwerty 每次客户端我总是把回复错误点击成感谢。

    anyway,你这个不行啊。它要有更多 start 呢?
    noqwerty
        13
    noqwerty  
       2019-09-21 09:34:14 +08:00
    @ipwx #12 多个也没问题啊,第一组是 greedy 的会把所有前面的 start 都匹配到: https://regex101.com/r/2jv8x3/1
    injector
        14
    injector  
       2019-09-21 10:11:12 +08:00
    \bstart(?!.*start).*\bend\b
    ipwx
        15
    ipwx  
       2019-09-21 11:38:10 +08:00
    WoW。。。。 我现在觉得 3L 是对的。

    @Nasei @injector 我们用的零宽断言没法把所有这样的 start-end 对给搞出来。 @noqwerty 当然你那个也不行。

    但是 3L 老哥的 NFA 却可以。

    https://regex101.com/r/cDExSo/1
    ipwx
        16
    ipwx  
       2019-09-21 11:38:37 +08:00
    Nasei
        17
    Nasei  
       2019-09-21 11:49:57 +08:00
    @ipwx 我试了下我的,可以啊
    ipwx
        18
    ipwx  
       2019-09-21 11:52:25 +08:00
    @Nasei 哦刚刚看错了。你这个确实可以。棒!

    不过 NFA/DFA 转 Regex 好像确实有点那么意思。
    geelaw
        19
    geelaw  
       2019-09-21 12:08:11 +08:00
    @xml123 #9 startendend 会拿到太长的结果。

    @ipwx #11 对于我来说零宽断言不够“纯粹”。

    而且重点是方法很简单(结果确实很复杂),所谓“理论帮助思考”,是指思考的很大一部分负担由理论内部所消化类似于使用方程比算术方法简单,“条件表达为方程”(建立一个 (G)NFA )以及“解方程”(把 (G)NFA 转换为正则表达式)中,前者是比直接想出反向的算式简单的,后者是机械的。

    至于为什么直接写一个正则表达式更加困难,是因为正则表达式中对补集、交集的表达力比较弱(纯粹的正则表达式根本没有这些功能,比如计算一个正则表达式识别的语言补的正则表达式,是没有什么很直接的办法的),而 (G)NFA 表达这些很容易。

    #18 见 https://gist.github.com/GeeLaw/be3aec94a6ba7c3817ef2e16d261f616

    @noqwerty #10 startendstartend 会只能拿到第二个匹配。
    noqwerty
        20
    noqwerty  
       2019-09-21 12:28:17 +08:00 via Android
    @geelaw 哦哦,我理解错他的意思了,是要匹配到所有 start...end 对,不是只有一个 end
    autoxbc
        21
    autoxbc  
       2019-09-21 14:02:31 +08:00
    要是我的话,把 start 和 end 替换成 html tag,丢给浏览器解析,然后 CSS Selectors,XPath 随便玩

    每次你用正则解析序列化的结构数据,就是重新写了一遍这种数据结构的解析器 -- 鲁迅
    ipwx
        22
    ipwx  
       2019-09-21 14:06:41 +08:00
    @geelaw 嘛嘛,理论辅助思考这点我同意。

    但是我觉得作为工程问题,可维护性也是很重要的。显然在这个例子里面,零宽断言( @Nasei 版本)容易看懂,比较容易维护,就可以了。你那个版本的正则,只能作为智力游戏的结果,不能作为工程实践。

    如果有什么是正则零宽都不能解决的,我认为应该上文法解析器。当然不能是 LL/LR 这类笨重的不好维护的解析器,我觉得以 PyParsing 为代表的那种解析器更适合工程。
    ipwx
        23
    ipwx  
       2019-09-21 14:08:24 +08:00
    @geelaw 对,PyParsing 为代表的是 PEG 解析器。

    https://en.wikipedia.org/wiki/Parsing_expression_grammar

    这类我觉得更适合工程,更容易维护。因为你可以把一个一个子规则拆开来写单元测试,而不是写一长串 CFG 规则,然后用外部工具转换成根本没法调试的一坨代码。
    flynaj
        24
    flynaj  
       span class="ago" title="2019-09-21 17:17:13 +08:00">2019-09-21 17:17:13 +08:00 via Android
    start (.*?) end 这样就行,非贪婪模式,基本正则就可以,写一大串是搞什么
    imdong
        25
    imdong  
       2019-09-21 17:26:06 +08:00
    (start(:?[a-z\s]+)?end)
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1506 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 37ms UTC 16:28 PVG 00:28 LAX 09:28 JFK 12:28
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86