在日常处理文本数据中,如果问我什么技术最重要和最常用,第一反应绝对不是虽说听起来low点但实际上效果并不差的字和词的one-hot编码,也不是什么据说可以解析词的相似度的word2vec(虽说效果也还可以),更别提近2,3年火得一塌糊涂的预训练语言模型(u1s1,身边认识搞nlp的,除了搞算法研究领域和知识图谱,不然没几个会在工程上用起这个)。而是出现了几十年了但目前在文本预处理领域占绝对地位,甚至可以说匹配领域都占有一席之地的陈旧技术——正则文法。
当然,从理论上说,其实正则文法也并不low,毕竟它可以算是乔姆斯基语言体系中有限状态自动机的应用——比它更高级的上下文无关文法其实到目前为止应用也不算特别多,更别提说可以完美表示自然语言的上下文有关文法(反正我没听过有实现过)。但主要是这东西出现太久了,我们都太习惯了它的存在,才感觉好像一般般。
先放最基本正则表达式的学习资料:——github上有一份37k star的链接,本渣称之为地表最强
https://github.com/ziishaned/learn-regex
但是,正则表示式也有一个尴尬的点,它单纯就是个表达式的匹配语法,不同的语言实现都不同,调用的api也不同,这也出现了我在浅析java和python的迭代里面说的问题——不同语言使用相同功能时候经常混淆,所以本文着重于看java和python的不同,具体正则的学习可以看上面大神写的github教程。
Java版的正则:
Java的正则主要运用了两个包,一个Pattern,一个Matcher。
可以理解为Pattern是用于正则表达式的编译表示,Matcher则是用于输入字符串进行解释和匹配操作的引擎。
Pattern其实就3个方法:split,matches,compile
其中核心就1个:compile。因为split算是特殊的,需要先complie再使用,而且Java字符串本身自带了带正则的split方法——日常直接用字符串的split不香么。而matches方法在Matcher类中也有另外的实现,因此没必要单独记。
Matcher则稍微复杂一点,它本身是Pattern进行compile后再调用matcher返回的结果,几乎所有的正则操作都在这个包上,但最常用也就下面几个方法:
matches:需要全文都匹配才返回true
lookingAt方法:从第一个字符开始部分匹配,但只返回第一个
find方法:部分匹配(这也是日常生活中最常用的),返回多个——常搭配循环while语句
当然,在数据处理的时候,更多是将匹配到的字符串进行分开或者替换,这时候就可以使用split或者replace函数,Java的String也自带了带有正则功能的函数(split,replaceAll,replaceFirst)。此外,还需要注意的一点是,在java中“\\”代表正则符号的一个“\", 因此,如果想匹配字符串含有“\”的模式,则需要是“\\\\”
public class regex_learn {
public static void main(String[] args){
// ####################### Pattern ###################################
// matcher方法——整句全部匹配
System.out.println(Pattern.matches(".*ef", "abcdefgef"));
// split方法——两者等价Pattern.compile(delimeter).split(str)
// split1和split2等价
String[] split1 = Pattern.compile("123").split("123456123");
String[] split2 = "123456123".split("123");
// compile 方法
Pattern p = Pattern.compile(".*ef");
// ############################# Matcher方法 ###############################
// matcher方法——整句全部匹配
Pattern pattern = Pattern.compile(".*ef");
Matcher matcher = pattern.matcher("abcdefgef");
System.out.println(matcher.matches());
// lookingAt()方法——从第一个字符开始部分匹配,有且只返回第一个
Pattern pattern2 = Pattern.compile("ef");
Matcher matcher2 = pattern2.matcher("abcdefgef");
System.out.println(matcher2.lookingAt());
// find用于循环——部分匹配,当存在多个的时候,返回多个
Pattern pattern3 = Pattern.compile("ef");
Matcher matcher3 = pattern3.matcher("abcdefgef");
System.out.println("find方法");
while (matcher3.find()){
System.out.println(matcher3.group());
// start和end需要已经匹配后再进行输出
System.out.println(matcher3.start());
System.out.println(matcher3.end());
}
// replace方法
Pattern pattern4 = Pattern.compile("ef");
Matcher matcher4 = pattern4.matcher("abcdefgef");
System.out.println(matcher4.replaceFirst("efg"));// 只替换第一个
System.out.println("abcdefgef".replaceFirst("ef","efg"));// 同上
System.out.println(matcher4.replaceAll("efg"));// 替换全部
System.out.println("abcdefgef".replaceAll("ef","efg"));// 同上
// 非正则的replace
System.out.println("abcdefgef".replace("ef","efg"));// 输入的第一个是字符串
}
}
Python的正则
因为使用正则更多是在数据预处理阶段,因此一般来讲,python使用正则会比java更多。跟java的正则相比,python有如下特点:
1. str没有自带正则的函数
2. 没有pattern类(但有类似的实现),返回值几乎都是Matcher
3. api的名字不同
4. 所有方法都封装在re这个包里面
5. 在写正则的str时候,需要加上r""
其他不同详细可看代码注释
import re
# fullmatch 相当于java的match,返回Match对象
print(re.fullmatch(r"abc", "abc"))
print(re.fullmatch(r"abc", "abcd"))
# match()函数试图从字符串的开始部分对模式进行匹配,能匹配,则返回返回Match对象(只返回一个)
# 相当于java的lookingat
print(re.match(r"abc","abcdefabc"))
# <re.Match object; span=(0, 3), match='abc'>,能匹配,则返回返回Match对象
print(re.match(r"abc","babcdef"))
# None
# finditer,迭代器的每个是Match对象
# 相当于java的find
print(re.finditer(r"abc", "1abc3abc"))
for i in re.finditer(r"abc","1abc3abc"):
print(i)
# findall 从任意位置匹配,返回全部列表
print(re.findall(r"abc", "1abc3abc"))
# search ,从任意位置匹配,只返回第一个匹配到的Match对象
# 这种搜索方法java貌似没有
print(re.search(r"abc","babcdefabc"))
# 相当于Pattern,预编译
compile_reg = re.compile(r"abc")
print(compile_reg.fullmatch("abc"))
print(compile_reg.fullmatch("abcd"))
print(compile_reg.match("abcdef"))
print(compile_reg.match("babcdef"))
print(compile_reg.search("babcdefabc"))
print(compile_reg.finditer("1abc3abc"))
print(compile_reg.findall("1abc3abc"))
test = 'abcdef'
if re.match(r'abc', test):
print('ok')
else:
print('failed')
# Match的对象的方法有
# python Matcher 的分组较为麻烦,要正则表达式本身存在()分组才可匹配
print(compile_reg.match("abcdef").groups()) #这里为空元组
print(compile_reg.match("abcdef").group())
print(compile_reg.match("abcdef").span())
print(compile_reg.match("abcdef").start())
print(compile_reg.match("abcdef").end())
# 替代,相当于java的replace
print(re.sub(r"abc", "0", "abcefgabc")) # 相当于java的replaceAll
print(re.sub(r"abc", "0", "abcefgabc",1)) # 相当于java的replaceFirst
print("abcefgabc".replace(r"abc", "0"))# python str不像java那样自带了正则
# 切分
print(re.split("a", "abcab"))
print("abcab".split("a"))# python str不像java那样自带了正则