数据清洗--2. regular expression 正则表达

数据清洗–2. regular expression 正则表达


目录


前言

正则表达是数据清洗中不可或缺的一项技能。


一、正则表达是什么

正则表达是用来描述文本规律/模式的一系列的符号。


二、正则表达能干什么

正则表达式在从文本(如日志文件、HTML/XML文件和其他文档)中查找、替换和提取信息时非常有用。
比如提取邮箱或验证输入文本是否符合邮箱格式
数据清洗--2. regular expression 正则表达
figure generate by https://regexper.com


三、正则表达的语法

字母候选集(Character sets)

grey gray

可以用 gr[ea]y 来匹配
数据清洗--2. regular expression 正则表达

注意 :

  1. 只匹配候选集中1个字母;
  2. 候选集中的顺序是无影响的;
  3. 匹配是大小写敏感的。
    即 grAy, graay, 和 graey 是不会被匹配到的

字母候选域(Character ranges)

[0 − 9], [a − z] or [A − Z]

这里先给一个错误的示范:
想匹配50-99之间的数字,并不能用[50-99]表示。
[50-99]表示的是
数据清洗--2. regular expression 正则表达
也就是说跟[0-9]是一样的。

现在在给大家展示一个正确的例子:

XRA 000, 1AA 1AA
请问如何设计一个正则表达式,来同时可以满足上面两个字符串?

答案: [A-Z0-9][A-Z][A-Z]\s[0-9][A-Z0-9][A-Z0-9]

数据清洗--2. regular expression 正则表达



反字母候选集(Negative character sets)

除了用正则表达式可以表达想要哪些字母,还可以用正则表达式表达不要哪些字母,如:

hog dog bog
这三个词中,希望保留hog 和dog,该如何操作呢?

你可以用 :

[hd]og
觉得麻烦? 那你可以试试:
[^b]og

思考:

see[^mn] 会匹配“see”,“see ”中的哪个,还是都会匹配,亦或者都不匹配??
Try the regular expression in Pythex~

特殊字符在候选集内(Metacharacters inside character sets)

var(9), var[0]
如何使用一个正则表达式来满足上述两个字符串? 我们需要匹配"(" ")"和"[" "]"

数据清洗--2. regular expression 正则表达

注意:第一个表达式是正确的,需要用"" 来取消第一个"]“的代码意义变成一个单纯的字符,否则系统会认为表达式2中倒数第二个 “]” 和倒数第四个”["是一对。

下表是一些Re的缩写
数据清洗--2. regular expression 正则表达
思考:
[^\d\s] 和[\D\S] 是一样的吗?

答案: 必然不一样
第一个是:既不是数字,也不是空格类
第二个是:不是数字或者不是空格类
数据清洗--2. regular expression 正则表达

数据清洗--2. regular expression 正则表达

数据清洗--2. regular expression 正则表达

重复元字符(Repetition Expressions)

数据清洗--2. regular expression 正则表达
*: 匹配0个或多个该字符
+:匹配1个或多个该字符
?:匹配0个或1个该字符
假设我们想匹配

oops ooops ooooops oooooops
但是不匹配
ops
以下表达式哪个是正确的?
  • oo*ps
  • ooo*ps
  • oo+ps
  • oo?ps

自己去试试吧~ tips: 上面有效的 re 不止一个哦~


另外,还可以定量的重复
  • \d{2} 只匹配2个数字
  • \d{2,4} 匹配2~4个数字
  • \d{2,} 至少匹配2个数字

当然这个方法也可以用在上面那个例子, 试试 o{2:}ps

再举个例子
匹配

report_2016_09 | assignment_2016_9
budget_16_08 | assignment_08_7
但是不匹配
report_201609_39 | assignment_6_9000
budget_2345678_08 | assignment_000999_7

我们可以用什么表达式呢?
通过观察,有效的字符串由几个字母,一个下划线,2-4位数字,下划线,1-2位数字组成。
re可以写作:
\w+_\d{2,4}_\d{1,2}

字符组(Grouping)

可以把想捕捉的固定字符串组合放到()中,来实现对固定字符串的捕捉。
比如:

(abc)+
可以匹配 abc, abcabc 或 abcabcabc

数据清洗--2. regular expression 正则表达
例二:
想匹配 I *** you:

I love you, I hate you, I like you, I miss you

可以用

I (.*) you

ps: “.” 表示除换行符(\n)之外的任意单字符,".*"表示在 I 和 you 之间有0个或多个任意字符。


贪婪或非贪婪(greedy or lazy)

贪婪匹配策略:会匹配满足条件的最常的字符串,* 和 + 都是贪婪匹配。
非贪婪匹配策略: 会匹配满足条件的最短的字符串,? 是非贪婪匹配


举个例子
我们有这样一串string

"dog", "cat", "fish"

尝试以下两种re,我们会获得不一样的pattern(匹配片段)

  1. (".+"),*
    数据清洗--2. regular expression 正则表达
    re1 是贪婪匹配策略,会找到符合条件的最长的片段,即整个string
  2. (".+?"),*

数据清洗--2. regular expression 正则表达
re2 加入了 “?” 匹配策略变成了非贪婪,就会找满足re的最短的片段,即每个单词。
所以当你编写的re 不能很好的找出想要的片段时,不妨试着改变一下匹配策略。


或(Or)

当想匹配(A or B) + C 这样的pattern的时候就需要使用or的逻辑了, 符号为 ”|“。

举例:

apple juice, orange juice, pear juice, berry juice

以上一堆果汁中,只要匹配苹果汁和橙汁时,就需要用到or的逻辑
Re可以写作:
(apple|orange) juice

数据清洗--2. regular expression 正则表达

四、代码部分(Python)

以下练习以Python语言为例

贪婪&非贪婪匹配初体验

#pip install re
import re # regular expression的包

# 首先我们来看一下不同的匹配策略对于相同的一段string,
# 是如何匹配的
# 我们可以用re.findall(re,string) 来匹配我们编写的正则表达式
str1 = re.findall(r'.*', 'Please find all.')
print (str1) 
# output: ['Please find all.', '']

str2 = re.findall(r'.?', 'Please find all.')
print (str2) 
# output: ['P', 'l', 'e', 'a', 's', 'e', ' ', 'f', 'i', 'n', 'd', ' ', 'a', 'l', 'l', '.', '']

str3 = re.findall(r'.+', 'Please find all.')
print (str3)
# output: ['Please find all.']

str1 = re.findall(r'l+', 'Please find all')
print (str1)
# output: ['l', 'll']

判断一个日期是不是正确的日期

# task:判断输入的日期是不是一个正确的日期。
# 日期是日月年格式,年份可能是2位,也可能是4位数字表示
# 假设所有年份都是闰年,即2月份有29天。

# 判断输入的日期是不是一个正确的日期
def date(pattern, m):
    if re.match(pattern, m):
        print (m + " is a date")
    else:
        print (m + " is NOT a date")
# 当regular expression比较复杂时,可以用
# ```(?x)```来表示,然后再告诉系统 "?x" 到底是什么
# ```xxx```在Python中表示多行raw string
# 逻辑看不懂的同学,可以把代码放到 
# https://regexper.com 里看看具体逻辑
regex = r'''(?x)
    (?:
    # February (29 days every year)
    (([12]\d|0?[1-9])[-/ ]0?2)|
    # 30-day months
      ([12]\d|0?[1-9]|30)[-/ ](0?[469]|11)|
    # 31-day months
     ([12]\d|0?[1-9]|3[01])[-/ ](0?[13578]|1[02])
    ) [-/ ]
    # Year
    (\d{2,4})
'''

date(regex, "28/02/2019")
date(regex, "31/04/2019")
date(regex, "29/05/2019")
date(regex, "31/06/2019")
# 28/02/2019 is a date
# 31/04/2019 is NOT a date
# 29/05/2019 is a date
# 31/06/2019 is NOT a date

有兴趣的小伙伴可以思考一下如何完善日期的判断,把是否是闰年考虑进去。


提取IP地址

# IP地址的规律是4段1~3位数字,中间用 . 隔开。
# 当然,这种re并不包括判断IP的有效性,
# 有兴趣的同学可以考虑考虑如何实现判断IP的有效性
pattern_ip = r'\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}'

all_ip = re.findall(pattern_ip, text)

提取邮箱

pattern_email = r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
str_email = re.findall(pattern_email, text)

总结

以上就是今天整理的内容,本文仅仅简单介绍了正则表达式的使用,其实还有很多进阶的正则表达式使用方法,比如定界符 “^” “$”, uncapture group (非捕获组) “?:” “?=” “?<=” “?!” “?<!” 等等。。。

在实际操作中还是有很多困难等着大家解决的[手动捂脸],代码和核心还是多想,多看,多练。与君共勉~

上一篇:c++ 实现


下一篇:UVA10400 Game Show Math【DFS】