正则表达式的学习之路(为学习 自动机 )

一开始想要学习正则表达式是想学习自动机算法,后来看自动机算法是正则表达式的引擎,就决定先学一下正则表达式

以下资源取自很多网上资源,包括但不限于 百度百科 , CSDN,博客园的一些博客,我还包括一些国外文章的翻译,在此就不一一给出连接了,如有侵权,请及时联系我,我会尽量按照被侵权方要求解决问题

(辣鸡需要看其他人的资料才能学习)QAQ。。。

/*---------------------自动机--------------------------*/
DFA和NFA自动机是正则表达式的引擎,又叫正则引擎。
因此,我觉得如果想学自动机的一些算法的话,了解正则表达式是必须的。
现在先了解一下正则表达式,毕竟磨刀不误砍柴工,下面是一些东拼西凑的概念:
正则表达式 又称 规则表达式,(Regular Expression),通常用来检索,替换那些符合某个模式(规则)的文本 

//------------概念:
正则表达式是对字符串操作的一种逻辑公式,就是事先定义好的一些特定字符,以及这些特殊字符的组合
组成一个 规则 字符串 ,这个 规则字符串 用来表达对字符串的一种过滤逻辑。

//------------简介:
正则表达式是对字符串(普通字符)和特殊字符,操作的一种逻辑公式, 
用实现定义好的一些特定字符以及这些特定字符的组合,组成一个 规则字符串 
这个规则字符串 用来表达对字符串的一种过滤逻辑 。
正则表达式是一种文本模式,模式描述 在搜索文本时要匹配的一个或多个字符串 

//------------目的:
给定一个正则表达式个另一个字符串,我们可以达到两个目的:
1. 给定字符串是符合正则表达式的过滤逻辑(匹配)。 
2. 可以通过正则表达式,从字符串中获取我们想要的特定部分。

//------------符号:
匹配样例:对于字符串"testing",可以匹配到"testing"和"testing123",但是匹配不到"Testing"。
\	:转义字符
^	:匹配输入字行首。
$	:匹配输入行尾。
*	:匹配前面的子表达式任意次。*等价于{0,}。
+	:匹配前面的子表达式一次或多次(大于等于1次)。+等价于{1,}。
?	:匹配前面的子表达式零次或一次。?等价于{0,1}。
{n}	:n是一个非负整数。匹配确定的n次。
	例:"o{2}"不能匹配"Bob"中的"o",但是能匹配"food"中的两个"o"。
{n,}:n是一个非负整数。至少匹配n次。
	例:"o{2,}"不能匹配"Bob"中的"o",但能匹配"foooood"中的所有"o"。
{n,m}:m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。
	例:"o{1,3}"将匹配"fooooood"中的前三个"o"为一组,后三个"o"为一组。
	注意!!:在逗号和两个数之间不能有空格。
?	:当该字符紧跟在任何一个其他限制符('*','+','?',"{n}","{n,}","{n,m}")后面时,匹配模式是非贪婪的。
	对于字符串"oooo":
	1.非贪婪模式尽可能少地匹配所搜索的字符串。例: "o+"将尽可能多地匹配"o",得到结果["oooo"]
	2.而默认的贪婪模式则尽可能多地匹配所搜索的字符串。例: "o+?"将尽可能少地匹配"o",得到结果 ['o', 'o', 'o', 'o']
.	:匹配除"\n"和"\r"之外的任何单个字符。
a|b	:匹配x或y。
	例如,"z|food"能匹配"z"或"food"(此处请谨慎)。"[zf]ood"则匹配"zood"或"food"。
[abc]:字符集合。匹配所包含的任意一个字符。
	例:"[abc]"可以匹配"plain"中的"a"。
[^abc]:负值字符集合。匹配未包含的任意字符。
	例:"[^abc]"可以匹配"plain"中的"plin"任一字符。
[a-z]:字符范围。匹配指定范围内的任意字符。
	例:"[a-z]"可以匹配"a"到"z"范围内的任意小写字母字符。
[^a-z]:负值字符范围。匹配任何不在指定范围内的任意字符。
	例:"[^a-z]"可以匹配任何不在"a"到"z"范围内的任意字符。
()	:将'(' 和 ')' 之间的表达式定义为"组"(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 "\1" 到"\9" 的符号来引用。
|	将两个匹配条件进行逻辑 或 。
	例:正则表达式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:这个元字符不是所有的软件都支持的。

//------------引擎:
正则引擎可以分为两大类,DFA,NFA
DFA引擎:
在线执行,不要求回溯(永远不测试相同字符两次)。DFA引擎确保匹配最长可能的字符串;
DFA引擎只包含有限状态,因此不能匹配具有反响引用的模式;
并且因为他不构造显示扩展,所以不能捕获子表达式。

NFA引擎:
运行 贪婪的 回溯算法, 以 指定顺序 测试正则表达式的所有可能的扩展并接受 第一个匹配项 
传统的NFA构造正则表达式的特定扩展以获得成功的匹配,所以他可以捕获子表达式匹配和匹配的反向引用 
传统的NFA回溯可以访问完全相同的状态多次(如果通过不同的路径到达该状态)
因此,在最坏的情况下,它的执行速度可能非常慢。
传统的NFA接受它找到的第一个匹配,所以它还可能会导致其他(可能更长)匹配未被发现。

POSIX NFA引擎:(现在不想学,就不看了,以后再补上吧) 



这里的自动机特指有限状态自动机,简称FA
根据状态转移的性质又分为确定的自动机 DFA 和非确定的自动机 NFA 。
FA的表达能力等价于正规表达式或者正规文法 。
FA可以看做是一个 有向带权图 。
图的 定点集合 成为自动机的 状态集合,
图的 权值集合 为自动机的 字母集合 。
图的 边 代表了自动机中 状态变化的情况。
此外,根据需要,自动机还需指定 初始状态 和终态。

FA最基本的左右就是形式化描述,而且有利于编程实现,以下开始介绍DFA自动机 
//-----------------------DFA自动机 -----------
https://blog.csdn.net/u012061345/article/details/52092436?locationNum=11
https://blog.csdn.net/qq_36827957/article/details/74357283
//-----
考虑仅有字符{a,b}组成的字符串,要求字符串中 字母b 必须成对出现,否则字符串非法。
这个规则实现起来非常简单,不需要自动机也完全可以。但是我们现在考虑是有自动机进行判断。 
该规则的正规表达式描述是:(a|bb)*。*运算代表重复若干次,包括0次。
做一个图来表示描述该规则的DFA。

令 状态1 为初始状态,显然在 状态1 上,我们还没有违反规则。因此,经过字母a 以后我们还可以回到 状态1 。
经过 字母b 后就不能回到 状态1 了,此时需要一个新状态,令其为 状态2。
状态2表示 待定的 状态,在这个状态时不能肯定字符串是非法的,但也不是合法的。
在 状态2 上,如果经过 字母b ,就回到了合法的状态(状态1);
如果经过 字母a ,则该字符肯定是非法的。建立一个 状态3 ,表示非法状态。
状态3 比较简单,已经到了非法状态,其后的任何字母都不会改变这个状态了。
因此,该DFA表示为:

 		1								3
		^                               ^
		|a								|a,b
		|								|
		V								V
		1				2				3
		      ---b-->		---a--->						
			  <--b---
			  
程序实现:
状态和字母都被编码成整数,使用一个矩阵表示状态转移,再写一个函数表示自动机的运行,
对于每个字符串,从状态1开始运行,运行完毕进行状态判断即可。
最后能停留在状态1的字符串才是符合规则的,其他的都是非法的。

代码:
#include<cstdio>
int DFA[4][2]={
	{0},	//0 0,1
	{1,2},	//1 0,1
	{3,1},	//2 0,1
	{3,3}	//3 0,1
};

int START_STATE=1;

int run(char const word[])
{
	int state=START_STATE;
	for(char const *p=word;*p;++p)
	{
		state=DFA[state][*p-'a'];
		if(state==3)
			return state;
	}
	return state;
}

int main()
{
	char a[],b[],c[];
	printf("%d\n",run(a));
	printf("%d\n",run(b));
	printf("%d\n",run(c));
	return 0;
}

//--------------HDU-2206-IP的计算
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2206 
ac代码, 错误状态记作0 
int const T[17][3]={
//		d.other
//		0 1 2 
/*0*/	0,0,0,
/*1*/	2,0,0,
		3,5,0,
		4,5,0,
		0,5,0,
/*5*/	6,0,0,
		7,9,0,
		8,9,0,
		0,9,0,
/*9*/	10,0,0,
		11,13,0,
		12,13,0,
		0,13,0,
/*13*/	14,0,0,
		15,0,0,
		16,0,0,
		0,0,0
};

inline int judge(char ch)
{
	if(ch>='0'&&ch<='9')//是个数 
		return 0;
	if(ch=='.')//是点 
		return 1;
	return 2;//啥都不是 
}

int run(char const word[])
{
	int state=1;
	for(char const *p=word;*p;++p)//遍历每个字符 
	{
		state=T[state][judge(*p)];
		if(state==0)
			return 0;
	}
	return state;// 
}

inline bool isfinal(int state)
{
	return state==14||state==15||state==16;
}

int main()
{
	char s[110];
	while(gets(s))
	{
		if(isfinal(run(s))==0)//不符合要求 
		{
			cout<<"NO"<<endl;
			continue;
		}
		int a,b,c,d;
		sscanf(s,"%d.%d.%d.%d",&a,&b,&c,&d);
		if(a>255||b>255||c>255||d>255)
			cout<<"NO"<<endl;
		else
			cout<<"YES"<<endl;
	}
}

//----------POJ-3332-Parsing Real Numbers
题目链接:http://poj.org/problem?id=3332 
题目大意就是输入一串字符串,然后判断这个串中是否包含有效的实数(任意书写方式,包括但不限于科学计数法等) 
是则输出: LEGAL  否则: ILLEGAL

解题思路:
对于一个 字符串,它的状态有以下几种:

一个符合要求的数字可以拆解成下面的式子: 
	+-	d	.	d	Ee	+-	d	_	 
  1    2  3   4   5    6   7  8    9 
然后输入对应的DFA转移数组,下面是AC代码:

//#pragma comment(linker, "/STACK:1024000000,1024000000") 
 
#include<stdio.h>
#include<string.h>  
#include<math.h>  
#include<stdlib.h>

//#include<map>   
//#include<set>
#include<deque>  
#include<queue>  
#include<stack>  
#include<bitset> 
#include<string>  
#include<fstream>
#include<iostream>  
#include<algorithm>  
using namespace std;  
 
#define ll long long  
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b) 
#define clean(a,b) memset(a,b,sizeof(a))// 水印 
//std::ios::sync_with_stdio(false);
const int MAXN=1e5+5;
const ll INF=1e18;
const ll mod=1e9+7; 

int DFA[10][6]={
	0,0,0,0,0,0,
    3,0,0,2,0,0,//数字,+- 
    3,0,0,0,0,0,//+-之后只能是数字 
    3,4,6,0,9,0,//数字循环  . Ee 直接空格(结尾) 
    5,0,0,0,0,0,// . 之后的数字 
    5,0,6,0,9,0,//数字循环 Ee 空格 
    8,0,0,7,0,0,//Ee之后的 +- 
    8,0,0,0,0,0,//+-之后的数字 
    8,0,0,0,9,0,//数字循环 空格 
    0,0,0,0,9,0 // 空格 
}; 

int get_char(char ch)
{
	if(ch<='9'&&ch>='0')
		return 0;
	if(ch=='.')
		return 1;
	if(ch=='E'||ch=='e')
		return 2;
	if(ch=='+'||ch=='-')
		return 3;
	if(ch==' ')
		return 4;
	return 5;
}

bool judge(char *s)
{
	int index=1;
	for(char *p=s;*p;++p)
	{
		index=DFA[index][get_char(*p)];
		if(!index)
			return 0;
	}
	if(index==3||index==5||index==8||index==9)
		return 1;
	else
		return 0;
}

int main()
{
	int T;
	cin>>T;
	getchar();
	while(T--)
	{
		char s[1100];
		gets(s);
		char *p=s;
		while(*p==' ')//找到第一个字符 
			p++;
		if(judge(p))
			cout<<"LEGAL"<<endl;
		else
			cout<<"ILLEGAL"<<endl;
	}
}




//------------------ 扩展链接:
正则表达式 的整理: https://blog.csdn.net/github_36498175/article/details/63262348
python中利用正则表达式爬取信息: https://blog.csdn.net/weixin_41580211/article/details/79089038
正则表达式引擎的构建: https://swtch.com/~rsc/regexp/regexp1.html

未完待续。。。

很快就有

猜你喜欢

转载自blog.csdn.net/qq_40482358/article/details/83041458