正则表达式学习

在刷LeetCode的时候发现很多恶心的字符串规则判断题目都可以转化成正则表达式来做,也正好通过这些题来熟悉下正则的用法,未来还是希望能自己在不查的情况下凭空写出来正则的~

另外作为一个nlp的同学,正则不会还是差点意思啊哈哈哈,这个博客可能以后会不断扩充吧~

菜鸟工具在线正则表达式验证工具

菜鸟工具在线正则表达式验证工具:https://c.runoob.com/front-end/854/

正则总结

匹配数字相关

\d 是匹配一个数字(0到9),在程序实现上一般需要再用 \ 转义一下,即 \\d

如果要匹配多个数字的话,就使用 \\d+,这里 + 代表一个或者多个

如果要匹配小数,中间带着小数点 . 的话,就是 \\d+.\\d+,注意这里还没有加入正负号的匹配

加号 + 与乘号 *

* 表示匹配前面的字符0个或多个

+ 表示前面的字符1个或多个

问号 ?

直接跟在表达式后面,表示匹配前面的一次或者零次,类似于{0, 1}的用法,[+-]{0,1}\\d+.\\d+[+-]?\\d+.\\d+ 应该是等价的意思,匹配前面有没有+-这些符号

指数符^ 和 dollar符 $

^ 用来匹配起始位置,$ 用来匹配结束位置,配合使用一般用作检验,比如检验一段文本是否只包含数字 ^[0-9]*$

如果 ^ 使用在中括号中,则有一种not的感觉

小括号 ()

小括号起到了一种组的概念,首先可以按照顺序使用 \\1\\2 进行访问匹配,例如在html/xml标签的匹配中,可以用 <(div)>[^<]*</\\1> 来进行匹配div标签,这样中间 [^<]* 的意思就是匹配不是 < 的任意字符,后边的 </\\1> 能够自动对应到尾部标签去;注意这样的可能存在的嵌套关系匹配替换,可以每次循环迭代把内层的不断替换成一些特殊字符或者空,直到匹配不到为止;

1
2
3
4
5
text: <div><font color='red'>hello, world</font><div>tmp2</div></div>

re: <(div)>[^<]*</\1>

result: <div>tmp2</div>

中括号 [] 与竖线 |

中括号用来匹配单个字符,是否属于中括号中的一个字符 [0-9] [A-Z] 这样都也是可以的,注意如果待正则匹配的内容中已经具有中括号(其他括号也是同理),需要用 \[ 把其他待匹配的做一个转义的感觉

竖线 | 就是一个或的意思,虽然不知道为什么要和中括号写在一个类别hhh,感觉使用场景上来说还是很灵活的

大括号 {}

匹配出现几次那种感觉,例如 \\d{1,3} 就是匹配出现一段文本中1次到3次的数字,这里还可以补充一下问号 ? 的作用,有一种最小匹配的感觉

好了,1+1=2学会(废)了!开始搞题!

BJUTACM OJ 2017年12月蓝桥杯预选赛题目ip地址判断

题目链接

题目大意

于是由于各种各样的原因, 出现了一个叹号。

我们都已经看到了工具下载及比赛规则的地址是 http://172.21.17.211/

下面你就来判断一下一个网址是否符合 http://a.b.c.d/ 的格式吧。 (a, b, c, d 均为长度在 [1, 5] 且由数字 0-9 构成的字符串)

输入
一行字符串。 长度小于50。

输出
如果输入符合要求。 输出 “Yes”。
否则输出 “No” 。

(输出不含引号)

样例输入

1
http://172.21.17.211/

样例输出

1
Yes

分析和解答

这个题对自己影响是真的大,当年蓝桥预选赛靠着这个题在大一上从毫无基础的小白就到学校预选赛的前30名,用C语言一条规则一条规则的适配,AC的那一刻真是最难忘的会议之一~

现在再来做这个题的话,过了这么多年了,发现用非常简单的正则就能写出来,也就把题总结在这里了~

1
2
3
4
5
6
7
8
9
10
11
import re
pattern = re.compile(r'http://[0-9]{1,5}\.[0-9]{1,5}\.[0-9]{1,5}\.[0-9]{1,5}/')

if __name__ == '__main__':
input_str = str(input())
tmp_str = re.sub(pattern, '', input_str)

if len(tmp_str) == 0:
print("Yes")
else:
print("No")

65.有效数字

题目链接

题目大意

有效数字(按顺序)可以分成以下几个部分:

  1. 一个 小数 或者 整数
  2. (可选)一个 'e''E' ,后面跟着一个 整数

小数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+''-'
  2. 下述格式之一:
    1. 至少一位数字,后面跟着一个点 '.'
    2. 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
    3. 一个点 '.' ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+''-'
  2. 至少一位数字

部分有效数字列举如下:["2", "0089", "-0.1", "+3.14", "4.", "-.9", "2e10", "-90E3", "3e+7", "+6e-1", "53.5e93", "-123.456e789"]

部分无效数字列举如下:["abc", "1a", "1e", "e3", "99e2.5", "--6", "-+3", "95a54e53"]

给你一个字符串 s ,如果 s 是一个 有效数字 ,请返回 true

示例1:

1
2
输入:s = "0"
输出:true

示例2:

1
2
输入:s = "e"
输出:false

示例3:

1
2
输入:s = "."
输出:false

提示:

  • 1 <= s.length <= 20
  • s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,或者点 '.'

分析和解答

这个题要分两部分考虑,首先考虑单独表示一个整数或者小数,然后再考虑科学计数法的内容,科学计数法就是 e-5 或者 e10 这样的

单独考虑整数: [+-]{0,1}\\d+
解释:+- 号可以出现0次或者1次(也可以用 ? 替代), {0,1}\\d+ 匹配整数;

考虑小数,情况1: [+-]{0,1}\\d+\\.\\d+
解释:+- 号可以出现0次或者1次(也可以用 ? 替代),后面是 x.x 形式的小数;

考虑小数,情况2: [+-]{0,1}\\d+\\.
解释:后面是 x. 形式的小数;

考虑小数,情况3: [+-]{0,1}\\.\\d+
解释:后面是 .x 形式的小数;

然后考虑科学计数法,即 从e开始后面的位置 ,情况有:

[eE][+-]?× -> 不能带小数点,因为不能是e2.5次方这样的,所以正则的写法为 ([eE][+-]?\\d+)? ,这里只能匹配一次,所以要把整体的带上括号

合并上述内容,前边的必须有,科学计数法不一定要有:

(([+-]?\\d+)|([+-]?\\d+\\.\\d+)|([+-]?\\d+\\.)|([+-]?\\.\\d+))([eE][+-]?\\d+)?

这里还有个坑的内容,例如对于 3. 这种case,如果把整数的匹配写在前面,那么只会优先匹配到 3 了,所以要把小数写在前面,整数写在后面

(([+-]?\\d+\\.\\d+)|([+-]?\\d+\\.)|([+-]?\\.\\d+)|([+-]?\\d+))([eE][+-]?\\d+)?

补充还有个坑的地方,就是说 1+3 这种case,在后面的话小数部分应该写成 ([eE][+-]?\\d+)? ,而不能写成多一个问号 ([eE]?[+-]?\\d+)? 只有出现了e或者E,才能让+-出现0或1次

非转义的final写法: (([+-]?\d+\.\d+)|([+-]?\d+\.)|([+-]?\.\d+)|([+-]?\d+))([eE][+-]?\d+)?

代码如下所示,这里的注意点是

  1. re.compile(ur'') 就可以不用转义各个字符了,转义还是个考虑的重点问题;
  2. 替换成一个特殊字符 # ,最后可以用这个特殊字符做一些判断,避免单个 e 一类的问题;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution(object):
def isNumber(self, s):
"""
:type s: str
:rtype: bool
"""
import re

pattern = re.compile(ur'(([+-]?\d+\.\d+)|([+-]?\d+\.)|([+-]?\.\d+)|([+-]?\d+))([eE][+-]?\d+)?') # 这里不用转义了
tmp_s = re.sub(pattern, "#", s)

if tmp_s != '#':
return False
else:
return True

591.标签验证器

题目链接

题目大意

给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法。合法的代码片段需要遵守以下的所有规则:

  1. 代码必须被 合法的闭合标签包围 。否则,代码是无效的。
  2. 闭合标签 (不一定合法)要严格符合格式:<TAG_NAME>TAG_CONTENT</TAG_NAME> 。其中,<TAG_NAME> 是起始标签,</TAG_NAME> 是结束标签。起始和结束标签中的 TAG_NAME 应当相同。当且仅当 TAG_NAME 和 TAG_CONTENT 都是合法的,闭合标签才是 合法的
  3. 合法的 TAG_NAME 仅含有大写字母,长度在范围 [1,9] 之间。否则,该 TAG_NAME 是不合法的。
  4. 合法的 TAG_CONTENT 可以包含其他 合法的闭合标签cdata (请参考规则7)和任意字符(注意参考规则1)除了不匹配的 < 、不匹配的起始和结束标签、不匹配的或带有不合法 TAG_NAME 的闭合标签。否则,TAG_CONTENT 是不合法的
  5. 一个起始标签,如果没有具有相同 TAG_NAME 的结束标签与之匹配,是不合法的。反之亦然。不过,你也需要考虑标签嵌套的问题。
  6. 一个 < ,如果你找不到一个后续的 > 与之匹配,是不合法的。并且当你找到一个 <</ 时,所有直到下一个>的前的字符,都应当被解析为 TAG_NAME(不一定合法)。
  7. cdata 有如下格式:<![CDATA[CDATA_CONTENT]]>。CDATA_CONTENT 的范围被定义成 <![CDATA[ 和后续的第一个 ]]> 之间的字符。
  8. CDATA_CONTENT 可以包含 任意字符 。cdata 的功能是阻止验证器解析 CDATA_CONTENT ,所以即使其中有一些字符可以被解析为标签(无论合法还是不合法),也应该将它们视为 常规字符

合法代码的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
输入: "<DIV>This is the first line <![CDATA[<div>]]></DIV>"

输出: True

解释:

代码被包含在了闭合的标签内: <DIV></DIV>

TAG_NAME 是合法的,TAG_CONTENT 包含了一些字符和 cdata 。

即使 CDATA_CONTENT 含有不匹配的起始标签和不合法的 TAG_NAME,它应该被视为普通的文本,而不是标签。

所以 TAG_CONTENT 是合法的,因此代码是合法的。最终返回True。


输入: "<DIV>>> ![cdata[]] <![CDATA[<div>]>]]>]]>>]</DIV>"

输出: True

解释:

我们首先将代码分割为: start_tag|tag_content|end_tag 。

start_tag -> "<DIV>"

end_tag -> "</DIV>"

tag_content 也可被分割为: text1|cdata|text2 。

text1 -> ">> ![cdata[]] "

cdata -> "<![CDATA[<div>]>]]>" ,其中 CDATA_CONTENT 为 "<div>]>"

text2 -> "]]>>]"


start_tag 不是 "<DIV>>>" 的原因参照规则 6 。
cdata 不是 "<![CDATA[<div>]>]]>]]>" 的原因参照规则 7 。

不合法代码的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
输入: "<A>  <B> </A>   </B>"
输出: False
解释: 不合法。如果 "<A>" 是闭合的,那么 "<B>" 一定是不匹配的,反之亦然。

输入: "<DIV> div tag is not closed <DIV>"
输出: False

输入: "<DIV> unmatched < </DIV>"
输出: False

输入: "<DIV> closed tags with invalid tag name <b>123</b> </DIV>"
输出: False

输入: "<DIV> unmatched tags with invalid tag name </1234567890> and <CDATA[[]]> </DIV>"
输出: False

输入: "<DIV> unmatched start tag <B> and unmatched end tag </C> </DIV>"
输出: False

注意:

  • 为简明起见,你可以假设输入的代码(包括提到的 任意字符 )只包含 数字, 字母, '<','>','/','!','[',']'' '

分析和解答

这个题还是挺难的,也是一种正则表达式的练习吧,总之对正则表达式又熟了一分,想法和题解就写在代码的注释里了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution(object):
def isValid(self, code):

# 使用正则表达式偷鸡了,正好作为一个正则表达式题目的练习了
import re

# 去掉CDATA字段变化为空,注意由于'['括号字符在正则表达式中具有特殊的含义,所以要'\'字符完成转义
# 【badcase4】CDATA必须被标签包含
# 【badcase5】.*?和.*的区别?
code_remove_cdata = re.sub(r"<!\[CDATA\[.*?\]\]>", "#", code)

# 【badcase2】如果全是CDATA匹配到的就返回False
if len(code_remove_cdata) == 1:
return False

# 【badcase1】这里要特别注意正则表达式括号的转义问题,另外[^<]*也很关键
# 【badcase3】要替换成一个非法字符,这样最后保证所有代码段都被包含在里面
prev = code_remove_cdata
code_remove_cdata_tag = re.sub(r"<([A-Z]{1,9})>[^<]*</\1>", "#", code_remove_cdata)

print("code_remove_cdata: ", code_remove_cdata)
print("code_remove_cdata_tag: ", code_remove_cdata_tag)

# 一直正则替换
while prev != code_remove_cdata_tag:
prev = code_remove_cdata_tag
# 【badcase1】这里要特别注意正则表达式括号的转义问题,另外[^<]*也很关键
# 【badcase3】要替换成一个非法字符,这样最后保证所有代码段都被包含在里面
code_remove_cdata_tag = re.sub(r"<([A-Z]{1,9})>[^<]*</\1>", "#", code_remove_cdata_tag)

print("code_remove_cdata_tag: ", code_remove_cdata_tag)

if code_remove_cdata_tag == "#":
return True
else:
return False



# if __name__ == '__main__':

# s = Solution()
# code = "<DIV>This is the first line <![CDATA[<div> <![cdata]> [[]]</div> ]]> <DIV> <A> <![CDATA[<b>]]> </A> <A> <C></C></A></DIV> </DIV>"

# # badcase1
# # "<DIV> unmatched < </DIV>" -> [^<]*的问题,要求中甲不能匹配<

# # badcase2
# # "<![CDATA[wahaha]]]><![CDATA[]> wahaha]]>" -> 如果经过第一步处理后就空了,那就要返回False

# # badcase3
# # "<A></A><B></B>" 要输出False

# # badcase4
# # "<![CDATA[ABC]]><TAG>sometext</TAG>" CDATA必须被标签包含

# # badcase5,.*和.*?还是有区别的,
# # "<DIV>This is the first line <![CDATA[<div> <![cdata]> [[]]</div> ]]> <DIV> <A> <![CDATA[<b>]]> </A> <A> <C></C></A></DIV> </DIV>"
# # 如果是.*匹配(贪婪匹配),会替换成:<DIV>This is the first line # </A> <A> <C></C></A></DIV> </DIV>
# # 如果是.*?匹配(最小匹配),会替换成:<DIV>This is the first line # <DIV> <A> # </A> <A> <C></C></A></DIV> </DIV>


# print("output res: ", s.isValid(code))

正则表达式学习
http://example.com/2022/05/06/正则表达式学习/
作者
Curious;
发布于
2022年5月6日
许可协议