菜鸟请教正则表达式问题

2019-09-21 06:15:55 +08:00
 noming

想匹配一段文字中"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". 写了好久没弄出来, 也不知道如何搜索该问题. 谢了!

2169 次点击
所在节点    问与答
25 条回复
delectate
2019-09-21 06:46:55 +08:00
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
2019-09-21 07:14:06 +08:00
\bstart((?!start).)*end\b
geelaw
2019-09-21 07:30:27 +08:00
用理论帮助思考,考虑一个 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
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
2019-09-21 08:49:30 +08:00
本来感觉还会写的,看完楼上大佬解答,我怂了
xml123
2019-09-21 09:04:29 +08:00
想到一个比较笨的方法,如果只有一个 end,可以考虑字符串逆序然后用懒惰模式
noming
2019-09-21 09:09:37 +08:00
@delectate 这个还是会包含第一个"start"

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

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

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

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

anyway,你这个不行啊。它要有更多 start 呢?
noqwerty
2019-09-21 09:34:14 +08:00
@ipwx #12 多个也没问题啊,第一组是 greedy 的会把所有前面的 start 都匹配到: https://regex101.com/r/2jv8x3/1
injector
2019-09-21 10:11:12 +08:00
\bstart(?!.*start).*\bend\b
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
2019-09-21 11:38:37 +08:00
Nasei
2019-09-21 11:49:57 +08:00
@ipwx 我试了下我的,可以啊
ipwx
2019-09-21 11:52:25 +08:00
@Nasei 哦刚刚看错了。你这个确实可以。棒!

不过 NFA/DFA 转 Regex 好像确实有点那么意思。
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
2019-09-21 12:28:17 +08:00
@geelaw 哦哦,我理解错他的意思了,是要匹配到所有 start...end 对,不是只有一个 end

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/602716

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX