1. 程式人生 > >正則表示式的學習之路(為學習 自動機 )

正則表示式的學習之路(為學習 自動機 )

一開始想要學習正則表示式是想學習自動機演算法,後來看自動機演算法是正則表示式的引擎,就決定先學一下正則表示式

以下資源取自很多網上資源,包括但不限於 百度百科 , 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

未完待續。。。

很快就有