模式匹配是自然语言处理信息抽取领域十分重要的一个基础性工作,也是我们常说的策略方法(与深度学习模型相对)。
如何使用规则的方式完成相关数据处理、信息抽取、分类等任务,是作为一个自然语言处理工程师必备的一项素质。
本文主要围绕NLP中的模式匹配落地场景及高效开源工具总结与分析这一主题,对模式匹配在自然语言处理落地中的使用场景以及开源的模式匹配工具进行介绍,供大家参考。
一、模式匹配概述
模式匹配,从概念上来说,指给定某种模式,检查给定的序列或字符串中是否有符合某种模式的片段。例如,给定句子“苹果是一种水果”,如果我们将 “是一种 ” 作为一种模式,则可以得到苹果和水果的匹配结果。而与模式匹配关系最为密切的,是正则表达式。
实际上,python语言中内置的re正则表达式已经可以完成此类的任务,但效率不高,有相关实践经验表明,随着需要处理的字符越来越多,正则表达式的处理速度几乎都是线性增加的,正则表达式在一个10k的词库中查找15k个关键词的时间差不多是0.165秒。
因此,如何解决在大规模文本的运行效率,目前先后出现了以AC自动机,时间复杂度为O(n)为典型代表的高性能模式匹配工具,本文就实际工作中所遇到的一些工具进行总结。
二、模式匹配在自然语言处理落地中的使用场景
1、知识图谱应用中的实体发现与链接
在基于知识图谱的应用,例如KBQA、实体识别系统中,第一步往往就是实体的提取和链接,所以在一个query中就要求提取出KB中包含的实体的名称,但是由于实体的数量动辄百万而普通的匹配方式由于每次匹配失败都需要回溯其耗时较久,而AC自动机的时间复杂度理想状态下为O(n),n为query串的长度。
一般采取的技术路线就是使用AC自动机提取出包含知识库中实体的所有子串(或者最长子串)+ NER实体识别的方式对query中的实体进行提取,再综合两个结果召回评分最高的Topn实体进行后续的链接操作。
2、搜广推中的敏感词过滤与召回
做敏感词过滤的时候要用到字符串匹配,从一个文件中读入需要匹配的敏感词,和一段文本去匹配,用string的find方法是不太合适。当你需要从一大堆数据中找寻自己想要的数据时可以使用AC自动机算法使用AC自动机算法。
另外,在广推场景中,对query进行匹配,尽可能多的找出里面的可能的关键词,然后提高召回。如果是最简单的想法可能就是一个for循环遍历词表,判断在不在文本里面。词表小的时候其实还好,但是如果词表大了,那么这个效率是很低的。
又如,在信息检索中的关键字搜索与替代场景中,另一个常用的用例是当我们有一个同义词的语料库(不同的拼写表示的是同一个单词),比如 corpus = {javascript: [‘javascript’, ‘javascripting’, ‘java script’], …} ,在软件工程师的简历中需要使用标准化的词,就需要用一个标准化的词来替换不同简历中的同义词。
三、AC自动机原理与应用流程
Aho-Corasick automaton,著名的多模匹配算法,简称AC算法,于1975年产生于贝尔实验室,主要用于多模式串匹配问题,即给几个关键词(模式串),再给一篇文章,判断关键词是否在文章中出现,或出现的次数。
Aho-Corasick算法,通过将模式串预处理为确定有限状态自动机,扫描文本一遍就能结束。其复杂度为O(n),即与模式串的数量和长度无关。其实现思想在于:按照文本字符顺序,接受字符,并发生状态转移。这些状态缓存了“按照字符转移成功(但不是模式串的结尾)”、“按照字符转移成功(是模式串的结尾)”、“按照字符转移失败”三种情况下的跳转与输出情况,因而降低了复杂度。
AC自动机执行包括三个过程,即插入模式串构建Trie树、设置失败节点、对目标串进行匹配。
1、Trie字典树
Trie是一个字符串索引的词典,检索相关项时时间和字符串长度成正比。在自然语言处理中,字符串集合常用字典树存储,这是一种字符串上的树形数据结构。字典树中每条边都对应一个字,从根节点往下的路径构成一个个字符串。
字典树并不直接在节点上存储字符串,而是将词语视作根节点到某节点之间的一条路径,并在终点节点上做个标记(表明到该节点就结束了)。要查询一个单词,指需要顺着这条路径从根节点往下走。如果能走到标记的节点,则说明该字符串在集合中,否则说明不在。
每条路径都是一个词汇,且没有子节点就可以判定该条路径结尾了,例如:欢迎、北大、北京城、北京大学的路径分别为:0-1-2、0-3-8、0-3-4-5、0-3-4-6-7
2、AC自动机的实现
在多模式环境中,AC自动是使用前缀树来存放所有模式串的前缀,然后通过失配指针来处理失配的情况。分为三个步骤:构建前缀树(生成goto表),添加失配指针(生成fail表),模式匹配(构造output表)。
1)构造前缀树
对于多模式集合{“say”,“she”,“shr”,“he”,“her”},对应的Trie树如下,其中红色标记的圈是表示为接收态:
2)添加失配指针
构造失败指针的过程如下:设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。
如果一直走到了root都没找到,那就把失败指针指向root。使用广度优先搜索BFS,层次遍历节点来处理每一个节点的失败路径。
3)模式匹配
模式匹配环节,从root节点开始,每次根据读入的字符沿着自动机向下移动。当读入的字符,在分支中不存在时,递归走失败路径。如果走失败路径走到了root节点,则跳过该字符,处理下一个字符。
因为AC自动机是沿着输入文本的最长后缀移动的,所以在读取完所有输入文本后,最后递归走失败路径,直到到达根节点,这样可以检测出所有的模式。
四、当前开源高效的模式匹配工具
当前,已经陆续开源出了一些优秀的模式匹配工具,例如python内置的re模块、经过优化的regex模块、基于AC自动机的pyahocorasick、Acora、esmre等。
1、re
re模块是Python的一个内置模块,主要定义了9个常量、12个函数、1个异常, Python 自1.5版本起增加了re模块,提供了Perl风格的正则表达式模式。
re模块提供了match、search、findall、sub、split等借口,对应实现匹配、搜索、替换以及分割功能。
其中:
- 匹配功能。 Python没有返回true和false的方法,但可以通过对match或者search方法的返回值是否是None来判断
- 搜索功能。 如果只搜索一次可以使用search或者match方法返回的匹配对象得到,对于搜索多次可以使用finditer方法返回的可迭代对象来迭代访问
- 替换功能。 可以使用正则表达式对象的sub或者subn方法来实现,也可以通过re模块方法sub或者subn来实现,区别在于模块的sub方法的替换文本可以使用一个函数来生成
- 分割功能。 可以使用正则表达式对象的split方法,需要注意如果正则表达式对象有分组的话,分组捕获的内容也会放到返回的列表中。
2、regex
regex是Matthew Barnett在2009年写了一个更强大正则表达式引擎,与有4270行C语言代码的re模块相比,regex模块有24513行C语言代码。
regex提供了模糊匹配(fuzzy matching)功能,能针对字符进行模糊匹配,提供了模糊插入、模糊删除、模糊替换等3种模糊匹配。例如:
regex.findall('(?:hello){s<=2}', 'hallo'),
其中最多容许有两个字符的错误,可以匹配出hallo。
3、Pyahocorasick
pyahocorasick是个python模块,由两种数据结构实现:trie和Aho-Corasick自动机,根据一组关键词进行匹配,返回关键词出现的位置,底层用C实现,python包装。
使用例子:
import ahocorasick
def make_AC(AC, word_set):
for word in word_set:
print(word)
AC.add_word(word,word)
return AC
def demo():
key_list = ["苹果", "香蕉", "梨", "橙子", "柚子", "火龙果", "柿子", "猕猴挑"]
AC_KEY = ahocorasick.Automaton()
AC_KEY = make_AC(AC_KEY, set(key_list))
AC_KEY.make_automaton()
test_str_list = ["我最喜欢吃的水果有:苹果、梨和香蕉", "我也喜欢吃香蕉,但是我不喜欢吃梨"]
for content in test_str_list:
name_list = set()
for item in AC_KEY.iter(content):#将AC_KEY中的每一项与content内容作对比,若匹配则返回
name_list.add(item[1])
name_list = list(name_list)
if len(name_list) > 0:
print(content, "命中:", "\t".join(name_list))
output:
"我最喜欢吃的水果,苹果、梨和香蕉",命中:["苹果","香蕉","梨"]
"我也喜欢吃香蕉,但是我不喜欢吃梨",命中:[香蕉","梨"]
4、Acora
acora是python的“fgrep”,是一个基于Aho-Corasick以及NFA-to-DFA自动机的方式快速的多关键字文本搜索引擎。
acora具备一些特性,包括在输入端适用于Unicode字符串和字节字符串。在对于大多数输入时,大约是python正则表达式引擎的2-3倍,并且能够查找重叠匹配项,即所有关键字的所有匹配项,还支持不区分大小写的搜索(~10倍于’re’,python内置的正则表达模块)。
from acora import AcoraBuilder
>>> builder = AcoraBuilder('ab', 'bc', 'de')
>>> builder.add('a', 'b')
>>> ac = builder.build()
>>> ac.findall('abc')
[('a', 0), ('ab', 0), ('b', 1), ('bc', 1)]
>>> ac.findall('abde')
[('a', 0), ('ab', 0), ('b', 1), ('de', 2)]
5、esmre
esmre同样使用的AhoCorasick自动机的方式,做了一些细微的修改,也是用C实现,python包装。与pyhrocorasick相比,esmre库不存在内存异常泄露等问题。
import esm
index = esm.Index()
index.enter('保罗')
index.enter('小卡')
index.enter('贝弗利')
index.fix()
index.query("""NBA季后赛西部决赛,快船与太阳移师洛杉矶展开了他们系列赛第三场较量,上一场太阳凭借艾顿的空接绝杀惊险胜出,此役保罗火线复出,而小卡则继续缺阵。首节开局两队势均力敌,但保罗和布克单节一分未得的拉胯表现让太阳陷入困境,快船趁机在节末打出一波9-2稍稍拉开比分,次节快船替补球员得分乏术,太阳抓住机会打出14-4的攻击波反超比分,布克和保罗先后找回手感,纵使乔治重新登场后状态火热,太阳也依旧带着2分的优势结束上半场。下半场太阳的进攻突然断电,快船则在曼恩和乔治的引领下打出一波21-3的攻击狂潮彻底掌控场上局势,末节快船在领先到18分后略有放松,太阳一波12-0看到了翻盘的希望,关键时刻雷吉和贝弗利接管比赛,正是他们出色的发挥为球队锁定胜局,最终快船主场106-92击败太阳,将总比分扳成1-2。""")
[((162, 168), '保罗'), ((186, 192), '小卡'), ((246, 252), '保罗'), ((478, 484), '保罗'), ((846, 855), '贝弗利')]
6、pampy
Pampy是一个只有150行的Python模式匹配类库,可支持单个字符匹配、匹配开头和结尾、匹配字典的key等功能。
Pampy匹配模式Pattern,可以是Python的任何类型,类或Python值,operator _和内置的类型(如int或str)提取传递给函数的变量。类型和类通过instanceof(value,pattern)来匹配,可迭代模式以递归方式匹配其所有元素。
Pampy匹配支持正则表达式,可以直接将已编译的正则表达式作为模式进行传递,通过Pampy支持多模式匹配,多个模式按照顺序进行匹配。
使用实例:
import re
from pampy import match, HEAD, TAIL, _
def what_is(pet):
return match(pet,
re.compile('(\\w+),(\\w)\\w+鳕鱼$'), lambda mygod, you: you + "像鳕鱼",
)
print(what_is('我的天,你长得真像鳕鱼')) # => '你像鳕鱼'
7、flashtext
Flashtext是一个高效的字符搜索和替换算法,基于Trie字典数据结构和AhoCorasick 的算法时间复杂度不依赖于搜索或替换的字符的数量。与AhoCorasick算法不同,Flashtext算法不匹配子字符串,只匹配最长字符(完整的单词),首先匹配最长字符串。
比如,输入一个单词 {Apple},算法就不会匹配 “I like Pineapple” 中的 apple,输入单词集合 {Machine, Learning,Machine Learning}和文档“I like Machine Learning”,算法只会去匹配 “Machine Learning” 。
实例:
1)关键字搜索
>>> from flashtext import KeywordProcessor
>>> keyword_processor = KeywordProcessor()
>>> # keyword_processor.add_keyword(<unclean name>, <standardised name>)
>>> keyword_processor.add_keyword('Big Apple', 'New York')
>>> keyword_processor.add_keyword('Bay Area')
>>> keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.')
>>> keywords_found
>>> ['New York', 'Bay Area']
2)关键字替换
>>> keyword_processor.add_keyword('New Delhi', 'NCR region')
>>> new_sentence = keyword_processor.replace_keywords('I love Big Apple and new delhi.')
>>> new_sentence
>>> 'I love New York and NCR region.'
五、正则表达式性能优化
测试demo:
private static void test2() {
String regular = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+$";
String testStr = "a_行政村行政村选择充血出现在出effg+现在重置线程这需求行政村行政村选择充血自产自销区+";
Pattern p = Pattern.compile(regular);
Matcher m = p.matcher(testStr);
boolean res = m.find();
System.out.println(res);
}
Java 语言使用的正则表达式执行引擎是 **NFA (Non-deterministic finite automaton) 非确定型有穷自动机,**这种引擎的特点是:功能强大、单存在回溯机制导致执行效率慢(回溯严重时可以导致机器 CPU 使用率 100%,直接卡死机器)。
测试代码:
private static void test1() {
String testStr = "a_行政村行政村选择充血出现在出effg现在重置线程这需求行政村行政村选择充血自产自销区";
String regular_1 = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+$";
String regular_2 = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]++$";
String regular_3 = "^[\\u4e00-\\u9fa5_a-zA-Z0-9]+?$";
List<String> regulars = new ArrayList<>();
regulars.add(regular_1);
regulars.add(regular_2);
regulars.add(regular_3);
for (String regular : regulars) {
long start, end;
start = System.currentTimeMillis();
Pattern p = Pattern.compile(regular);
Matcher m = p.matcher(testStr);
boolean res = m.find();
end = System.currentTimeMillis();
System.out.println("结果:" + JSON.toJSONString(res) + ",执行时间:" + (end - start) + "(ms)");
}
}
执行结果:
结果:true,执行时间:3(ms)
结果:true,执行时间:1(ms)
结果:true,执行时间:0(ms)
可以明显看到,虽然实现了相同的匹配功能,但效率却有所区别,原因在于这三种写法定义了正则表达式的三种匹配逻辑,我们来逐一说明:
正则表达式:ef{1,3}g
待匹配的字符串:effg
1 贪婪模式(默认)
语法: ef{1,3}g
原理: 贪婪模式是正则表达式的默认匹配方式,在该模式下,对于涉及数量的表达式,正则表达式会尽量匹配更多的内容。
说明: 我用模型图来演示一下匹配逻辑:
到第二步的时候其实已经满足第二个条件f{1,3},但我们说过贪婪模式会尽量匹配更多的内容,所以依然停在第二个条件继续遍历字符串
注意看第四步,字符g不满足匹配条件f{1,3},这个时候会触发回溯机制:指针重新回到第三个字符f处
关于回溯机制:
回溯是造成正则表达式效率问题的根本原因,每次匹配失败,都需要将之前比对过的数据复位且指针调回到数据的上一位置,想要优化正则表达式的匹配效率,减少回溯是关键。
回溯之后,继续从下一个条件以及下一个字符继续匹配,直到结束。
2 懒惰模式
语法: ef{1,3}?g
原理: 与贪婪模式相反,懒惰模式会尽量匹配更少的内容。
说明:
到第二步的时候,懒惰模式会认为已经满足条件f{1,3},所以会直接判断下一条件。
注意,到这步因为不满足匹配条件,所以触发回溯机制,将判断条件回调到上一个。
回溯之后,继续从下一个条件以及下一个字符继续匹配,直到结束。
3 独占模式
语法: ef{1,3}+g
原理: 独占模式应该算是贪婪模式的一种变种,它同样会尽量匹配更多的内容,区别在于在匹配失败的情况下不会触发回溯机制,而是继续向后判断,所以该模式效率最佳。
说明:
4 三种模式表达式
贪婪模式 | 懒惰模式 | 独占模式 |
---|---|---|
X? | X?? | X?+ |
X* | X*? | X*+ |
X+ | X+? | X++ |
X{n} | X{n}? | X{n}+ |
X{n,} | X{n,}? | X{n,}+ |
X{n,m} | X{n,m}? | X{n,m}+ |
建议1:推荐使用独占模式来优化正则比表达式,格式^[允许字符集]+ 。
建议2:推荐将Pattern 缓存下来,避免反复编译Pattern。
static final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");
建议3:优化正则中的分支选择
通过上面对正则表达式匹配逻辑的了解,我们不难想到,由于回溯机制的存在,带有分支选择的正则表达式必然会降低匹配效率
String testStr = "abbdfg";
String regular = "(aab|aba|abb)dfg";
在这个例子中,"aab"并未匹配,于是回溯到字符串的第一个元素重新匹配第二个分支"aba",以此类推,直到判断完所有分支,效率问题可想而知。
如果分支中存在公共前缀,可以进行提取:
String regular = "a(ab|ba|bb)dfg";
这样首先减少了公共前缀的判断次数,其次降低了分支造成的回溯频率,相比之下效率有所提升。
正则表达式CheatSheet
语法参考
**.**
- 除换行符以外的所有字符。**^**
- 字符串开头。**$**
- 字符串结尾。\d
,\w
,\s
- 匹配数字、字符、空格。\D
,\W
,\S
- 匹配非数字、非字符、非空格。**[abc]**
- 匹配 a、b 或 c 中的一个字母。**[a-z]**
- 匹配 a 到 z 中的一个字母。**[^abc]**
- 匹配除了 a、b 或 c 中的其他字母。**aa|bb**
- 匹配 aa 或 bb。**?**
- 0 次或 1 次匹配。*****
- 匹配 0 次或多次。**+**
- 匹配 1 次或多次。**{_n_}**
- 匹配 _n_次。**{_n_,}**
- 匹配 _n_次以上。**{_m_,_n_}**
- 最少 m 次,最多 n 次匹配。(
expr)
- 捕获 expr 子模式,以\1
使用它。(?:
expr)
- 忽略捕获的子模式。(?=
expr)
- 正向预查模式 expr。(?!
expr)
- 负向预查模式 expr。
六、常用正则表达式
一、校验数字的表达式
- n位的数字:^\d{n}$
- 至少n位的数字**:^\d{n,}$**
- m-n位的数字:^\d{m,n}$
- 零和非零开头的数字:^(0|[1-9][0-9]*)$
- 非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(.[0-9]{1,2})?$
- 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})$
- 正数、负数、和小数:^(-|+)?\d+(.\d+)?$
- 非零的正整数:\d或([1−9][0−9]∗)1,3或([1−9][0−9]∗)1,3 或 ^+?[1-9][0-9]$
- 非零的负整数:**^-[1-9][]0-9"或−[1−9]\d∗或−[1−9]\d∗*
- 非负整数:^\d+或[1−9]\d∗|0或[1−9]\d∗|0
- 非正整数:^-[1-9]\d*|0或((−\d+)|(0+))或((−\d+)|(0+))
- 非负浮点数:^\d+(.\d+)?或[1−9]\d∗.\d∗|0.\d∗[1−9]\d∗|0?.0+|0或[1−9]\d∗.\d∗|0.\d∗[1−9]\d∗|0?.0+|0
- 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))或(−([1−9]\d∗.\d∗|0.\d∗[1−9]\d∗))|0?.0+|0或(−([1−9]\d∗.\d∗|0.\d∗[1−9]\d∗))|0?.0+|0
- 正浮点数:\d*.\d*|0.\d*[1-9]\d*或(([0−9]+.[0−9]∗[1−9][0−9]∗)|([0−9]∗[1−9][0−9]∗.[0−9]+)|([0−9]∗[1−9][0−9]∗))或(([0−9]+.[0−9]∗[1−9][0−9]∗)|([0−9]∗[1−9][0−9]∗.[0−9]+)|([0−9]∗[1−9][0−9]∗))
- 负浮点数:^-([1-9]\d*.\d*|0.\d*[1-9]\d*)或(−(([0−9]+.[0−9]∗[1−9][0−9]∗)|([0−9]∗[1−9][0−9]∗.[0−9]+)|([0−9]∗[1−9][0−9]∗)))或(−(([0−9]+.[0−9]∗[1−9][0−9]∗)|([0−9]∗[1−9][0−9]∗.[0−9]+)|([0−9]∗[1−9][0−9]∗)))
- 浮点数:^(-?\d+)(.\d+)?或−?([1−9]\d∗.\d∗|0.\d∗[1−9]\d∗|0?.0+|0)或−?([1−9]\d∗.\d∗|0.\d∗[1−9]\d∗|0?.0+|0)
-
校验字符的表达式
- 汉字:^[\u4e00-\u9fa5]{0,}$
- 英文和数字:^[A-Za-z0-9]+ 或 [A−Za−z0−9]4,40 或 [A−Za−z0−9]4,40
- 长度为3-20的所有字符:^.{3,20}$
- 由26个英文字母组成的字符串:^[A-Za-z]+$
- 由26个大写英文字母组成的字符串:^[A-Z]+$
- 由26个小写英文字母组成的字符串:^[a-z]+$
- 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
- 由数字、26个英文字母或者下划线组成的字符串:^\w+或\w3,20或\w3,20
- 中文、英文、数字包括下划线:+$
- 中文、英文、数字但不包括下划线等符号:+或[\u4E00−\u9FA5A−Za−z0−9]2,20或[\u4E00−\u9FA5A−Za−z0−9]2,20
- 可以输入含有^%&',;=?\\"等字符:**\[^%&',;=?\x22]+**
- 禁止输入含有~的字符:[^~]+
-
三、特殊需求表达式
- Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
- 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+.?
- InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+.)+[\w-]+(/[\w-./?%&=]*)?$
- 手机号码:^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$
- 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
- 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
- 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号): ((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)
- 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15})|(\d18)|(\d18)|(^\d{17}(\d|X|x)$)
- 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):[a-zA-Z0-9_]{4,15}$
- 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):\w{5,17}$
- 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,10}$
- 强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.*[A-Z]).{8,10}$
- 日期格式:^\d{4}-\d{1,2}-\d{1,2}
- 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
- 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
- 钱的输入格式:
- 有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":[0-9]*$
- 这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
- 一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
- 这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧。下面我们要加的是说明可能的小数部分:+(.[0-9]+)?$
- 必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:+(.[0-9]{2})?$
- 这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:+(.[0-9]{1,2})?$
- 这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
- 1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
- 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
- xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+.[x|X][m|M][l|L]$
- 中文字符的正则表达式:[\u4e00-\u9fa5]
- 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
- 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
- HTML标记的正则表达式:<(\S*?)[^>]>.?|<.? /> ( 首尾空白字符的正则表达式:^\s**|\s*或(\s∗)|(\s∗或(\s∗)|(\s∗) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)**
- 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
- 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
- IPv4地址:((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}