diff --git a/Automata/Question3.py b/Automata/Question3.py index 42ba6bfe0079333ab9ce6789d260ad3bc7e69e7f..24f85624508e17f70a6f465a92e35cd4c73494fa 100644 --- a/Automata/Question3.py +++ b/Automata/Question3.py @@ -11,7 +11,6 @@ input_strings = myre.split(input_txt, [' ',',','"','.','(',')']) pattern = 's{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}*n' # 或者 pattern = 's[a-z]*n' -myre.match for string in input_strings: if myre.match(pattern, string.lower()): print(string) diff --git a/Automata/README.md b/Automata/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b1a9e24a691af48f51b3525ae8b20a51517ad65f --- /dev/null +++ b/Automata/README.md @@ -0,0 +1,575 @@ +[toc] +## Question1 +​ 设计一个DFA,使其接受2进制字符串w,并且所有w的逆都能被5整除。比如,该DFA接受二进制串11110,因为其逆为01111,代表十进制的15,所以可以被5整除。详述该DFA的设计。
+### 1.1 思路 +​ 首先设计一个接受的二进制串能被5整除的的DFA,然后将整个DFA的状态反转使其接受的为符合条件的二进制的逆。
+### 1.2 设计一个接受的二进制串能被5整除的DFA +* 将已扫描到的字符的余数作为当前状态,一共有5种情况,即5个状态:Q0:余数为0;Q1余数为1......根据规律列出状态转移表如下:
+
+image +
+ +* 根据状态转移表绘制状态转移图:
+
+image +
+### 1.3 反转状态转移图 +​ 将1.2中的DFA的状态反转(将箭头反向并将起始状态与接受状态对调)并根据q0的转移函数添加起始状态qs以防止出现未输入任何字符就接受的情况:
+ +
+image +
+ +## Question2 +​ 使用归纳证明法证明对于任意的NFA M=(Q,Σ,δ,q0 ,F),一定存在CFG G=(Q,Σ,P,S),使得L(G)=L(M)。 +### 2.1 思路 +​ 等价转化法:已知NFA与RE等价,故问题转化为对任意的正则表达式R,都可以用CFG G=(Q,Σ,P,S)来表示。即要证明L(R)=L(G)。
+### 2.2 文法设计 +​ RE由运算符,字符集,以及'()'组成,故根据运算优先级可以得到文法G:
+```shell +S -> A +A -> B|A"|"B # "|" 加(或) +B -> C|BC # "" 乘(直接连接),幂运算可由乘实现 +C -> D|D"*"|D"+"|CD"*"|CD"+" # "*"闭包 "+"正闭包 +D -> E|F +E -> "a","b","c","d","e","f","g"...... #终结符 +F -> "("A")" #括号递归 +``` +### 2.3 归纳法证明 +对输入的正则表达式R的运算符数量进行归纳法证明。
+* 当n=1时:
+R = "a*" | "a|a" | "a+" | "aa" | "(aa)"......
+以R =(aa)为例的派生过程:
+S=>A=>B=>C=>D=>F=>"("A")"=>"("B")"=>"("BC")"=>"("CC")"=>"("CD")"=>"("DD")"=>"("DE")"=>"("EE")"=>"(""a""a"")"即S=>(aa)
+易知其他情况也满足。
+* 假设当运算符数量为n时,正则表达式R也是合法的G
+当运算符数量为n+1时:
+R' = "R*" | "R|a" | "R+" | "Ra" | "(Ra)"...
+由于G可以派生出R,故S=>A=>....R可得A=>R
+以R' ="R|a"的派生过程为例:
+S=>A=>A"|"B=>R"|"B=>R"|"C=>R"|"D=>R"|"E=>R"|""a"即S=>R‘,成立
+对其他情况依次进行归纳,均成立
+故对任意的正则表达式R,都可以由所定义的 G得到,即对于任意的NFA M=(Q,Σ,δ,q0 ,F),一定存在CFG G=(Q,Σ,P,S),使得L(G)=L(M)。 + +## Question3 +​ 正则表达式在信息检索中具有广泛的应用。请设计并编程实现一个基于正则表达式的英文单词检索系统,要求给定任意的正则表达式作为输入,将其转换为等价的自动机,并可以根据该自动机实现从输入文本中检测并输出所有符合正则表达式描述的单词。
+​ 例如,当输入文本文件input.txt包含以下英文文本及正则表达式```s{a,…,z}*n```时:
+ +``` +Shenzhen University (SZU, Chinese: 深圳大学) is a public university established in 1983 located in Nanshan district, Shenzhen, Guangdong, China. It is accredited by the State Council of the People's Republic of China and is funded by the Shenzhen City Government. The university took its first enrollment the same year at what Deng Xiaoping called "Shenzhen Speed". Deng also was named the "father of Shenzhen University." It is regarded as the fastest developing university in China, and also one of the "Top 100 Universities in China" and one of the top university which is listed in the World Top Ranking Universities. +``` +​ 输出:
+``` +Shenzhen +Shenzhen +Shenzhen +Shenzhen +Shenzhen +``` +​ 为所有符合该正则表达式描述得单词,注意,此处单词间以空格分隔,并且对大小写字母不敏感。请在提交报告中给出详细的设计方案、关键代码实现、以及实验测试结果。
+### 3.1 思路 +​ 首先搭建一个正则表达式引擎,其中包括的功能由对正则表达式进行解析,转化为对应的自动机,并允许输入字符串到自动机中判断是否为自动所接受的字符串等功能。然后进行测试,读取input.txt并对单词进行分割,转化为小写字母,调用写好的正则表达式引擎进行匹配, 匹配成功则输出。
+### 3.2 正则表达式引擎的设计 +#### 3.2.1 整体流程 +正则表达式引擎的运行流程如下:
+* 1.建立词法分析器lexer类对正则表达式进行解析,校验正则表达式是否合法,得到对应字符的Token
+* 2.使用cell类基于BNF范式递归构建NFA
+* 3.将需要验证的字符串输入到所构建的NFA中,判断是否会被NFA所接受
+ +#### 3.2.2 词法分析器Lexer +详细代码可在```'./nfa/lexer.py'```中查询
+* 主要功能:对正则表达式进行解析与连续读取,校验正则表达式是否合法,得到对应字符的Token
+* 目前支持以下Tokens +```python +Tokens = { + '.': Token.ANY, + '(': Token.OPEN_PAREN, + ')': Token.CLOSE_PAREN, + '[': Token.SQUARE_START, + ']': Token.SQUARE_END, + '{': Token.OPEN_CURLY, + '}': Token.CLOSE_CURLY, + '*': Token.CLOSURE, + '+': Token.PLUS_CLOSURE, + '-': Token.DASH, + '|': Token.OR, +} +``` +* 对应所支持的正则表达式的语法规则: +```python +""" +规则: +"." : 用于匹配任意单个字符 +"c" : 匹配除了Token以外的输入的字符 +"[a-z]" : 匹配a-z中的任意单个字符 +"{a,b,c}" : 匹配a或b或c中的任意单个字符,也可以写作(a|b|c) +"*" : 闭包 +"+" : 正闭包 +"|" : 或(加) +"" : 默认进行连接操作(乘) +"("expr")": 支持使用括号提高运算优先级 +""" +``` +* 主要函数入口: +```python +class Lexer(object): + """ + 对正则表达式进行解析,校验正则表达式是否合法,得到对应字符的Token,目前不支持转义字符的输入 + """ + def next(self): + """ + 返回当前对应的Tokens并读入下一个字符 + """ + def get_token(self, text): + """ + 获取Tokens + """ + def check_pattern(self): + """ + 检查正则表达式括号是否匹配 + """ + def match(self, token): + """ + 将输入的token与当前的token进行比较 + """ +``` +#### 3.2.3 NFA单个节点的定义 +详细内容可在```'./nfa/cell.py'```中查询
+* 一个NFA节点包含边类型(self.edge),两个指向下一节点的边(self.next)以及转移条件(self.char_set)构成 +```python +# 根据RE转NFA的规律边的类型分为3种 +EPSILON = -1 # edge = EPSILON 对应的节点有两个出去的ε边 +CCL = -2 # edge = CCL 边对应的是字符集(包括单个字符) ,需要结合属性char_set,自动机运行时只要当前读入的字符在char_set中就满足跳转条件 +EMPTY = -3 # edge = EMPTY 一条ε边 +class Cell(object): + def __init__(self): + self.edge = EPSILON + self.next_1 = None + self.next_2 = None + self.char_set = set() +``` +* 为了便于构建NFA,这里再定义一个新的类,这个类由两个Cell的节点构成作为nfa的起始结点以及接受结点 +```python +class PairCell(object): + """ + 一对Cell的两个节点 + """ + def __init__(self): + self.start_node = None + self.end_node = None +``` + +#### 3.2.4 语义分析 +* 将Question2给出的文法转化为BNF范式 +```python +""" +词法分析按照优先级自顶向下 +expr ::= ("|" factor_connect)* # "|" 加(或) +factor_connect ::= factor | factor factor* # "" 乘(直接连接) +factor ::= term | term ("*"|"+")* # "*"闭包 "+"正闭包 +term ::= char | group | "[" char "-" char "]" | "{" char "," char "}" | "." #基本单元,终止符 +group ::= "("expr")" # 递归解决括号优先级 +""" +``` + +#### 3.2.5 NFA的构建 +详细内容可在```'./nfa/bulider.py'```中查询
+##### 3.2.5.1 NFA自动机创建入口 +* create_nfa()的主要功能就是根据pattern_string创建lexer,然后定义nfa开始节点和接受结点并调用expr()开始创建过程,最终返回自动机的起始节点 +```python +def create_nfa(pattern_string): + """NFA自动机创建入口 + pattern_string :: RE表达式 + return :: nfa起始节点 + """ + global lexer + lexer = Lexer(pattern_string) + lexer.next() + nfa_cells_final = PairCell() + expr(nfa_cells_final) + return nfa_cells_final.start_node +``` +##### 3.2.5.2 term元素的构建 +
+image +
+ +* 匹配输入的单个字符,创建两个节点A,B,定义A->B的边的类型为CCL,且char_set为当前字符 +```python +def nfa_single_char(nfa_cells): + """ + L 匹配单个字符 + """ + if not lexer.match(Token.L): + return False + start = nfa_cells.start_node = Cell() + nfa_cells.end_node = nfa_cells.start_node.next_1 = Cell() + start.edge = CCL + start.char_set = set() + start.char_set.add(lexer.current_text) + lexer.next() + return True +``` +* 匹配单个任意字符,创建两个节点A,B,定义A->B的边的类型为CCL,且char_set为所有ASCII字符 +```python +def nfa_any_single_char(nfa_cells): + """ + . 匹配单个任意字符 + """ + if not lexer.match(Token.ANY): + return False + start = nfa_cells.start_node = Cell() + nfa_cells.end_node = nfa_cells.start_node.next_1 = Cell() + start.edge = CCL + start.char_set = set() + for i in range(127): + start.char_set.add(chr(i)) + lexer.next() + return False +``` +* 匹配范围字符集中的单个字符,创建两个节点A,B,定义A->B的边的类型为CCL,且char_set为[" char "-" char "]之间的所有字符 +```python +def nfa_range_single_char(nfa_cells): + """ + [a-z] 匹配范围字符集中的单个字符 + """ + if not lexer.match(Token.SQUARE_START): + return False + lexer.next() + start = nfa_cells.start_node = Cell() + start.next_1 = nfa_cells.end_node = Cell() + start.edge = CCL + + # get range char set + first = '' + while not lexer.match(Token.SQUARE_END): + if not lexer.match(Token.DASH): + first = lexer.current_text + start.char_set.add(first) + else: + lexer.next() + for c in range(ord(first), ord(lexer.current_text) + 1): + start.char_set.add(chr(c)) + lexer.next() + lexer.next() + return True +``` +* 匹配范围字符集中的单个字符,创建两个节点A,B,定义A->B的边的类型为CCL,且char_set为{a,b,c....}中包含的所有字符 +```python +def nfa_set_single_char(nfa_cells): + """ + {a,b,c....} 匹配字符集中的单个字符 相当于(a|b|c...) + """ + if not lexer.match(Token.OPEN_CURLY): + return False + + lexer.next() + start = nfa_cells.start_node = Cell() + start.next_1 = nfa_cells.end_node = Cell() + start.edge = CCL + + while not lexer.match(Token.CLOSE_CURLY): + if lexer.current_text != ',': + start.char_set.add(lexer.current_text) + lexer.next() + lexer.next() + return True +``` +##### 3.2.5.3 根据BNF范式自底向上构建NFA +* group(), 其实就是当检测到"("就递归调用expr(),然后直到检测到”)“停止 +```python +def group(nfa_cells): + """ + group ::= "("expr")" + 递归调用expr(),实现()操作 + """ + if lexer.match(Token.OPEN_PAREN): + lexer.next() + expr(nfa_cells) + if lexer.match(Token.CLOSE_PAREN): + lexer.next() + return True +``` +* term类似与终结符,用于构造nfa的最小节点,这里会调用不同类型元素的构建函数 +```python +def term(nfa_cells): + """ + term ::= char | group | "[" char "-" char "]" | "{" char "," char "}" | "." + 对 . | a (单个字符) | 单个一定范围[a-z]的字符 | {a,b,c} 某些字符集合中的单个字符->相当于(a+b+c) + """ + if lexer.match(Token.L): # char + nfa_single_char(nfa_cells) + elif lexer.match(Token.ANY): # . + nfa_any_single_char(nfa_cells) + elif lexer.match(Token.SQUARE_START): # [" char "-" char "] + nfa_range_single_char(nfa_cells) + elif lexer.match(Token.OPEN_CURLY): # "{" char "," char "}" + nfa_set_single_char(nfa_cells) + elif lexer.match(Token.OPEN_PAREN): # "("expr")" + group(nfa_cells) +``` +* factor用于实现闭包,其原理如下图所示: +
+image +
+ +```python +def factor(nfa_cells): + """ + factor ::= term | term ("*"|"+")* + 实现两种闭包 "*" | "+" + """ + term(nfa_cells) + if lexer.match(Token.CLOSURE) or lexer.match(Token.PLUS_CLOSURE): + nfa_closure(nfa_cells) +def nfa_closure(nfa_cells): + """ + * 闭包操作 以及 + 正闭包操作 + """ + if (not lexer.match(Token.CLOSURE)) and (not lexer.match(Token.PLUS_CLOSURE)): + return False + start = Cell() + end = Cell() + start.next_1 = nfa_cells.start_node + if lexer.match(Token.CLOSURE): # * + start.next_2 = end # 连接start与end形成shortcut,将"+"转化为"*" + nfa_cells.end_node.next_1 = nfa_cells.start_node + nfa_cells.end_node.next_2 = end + nfa_cells.start_node = start + nfa_cells.end_node = end + lexer.next() + return True +``` +* factor_connect用于实现连接操作(即乘操作),其原理如下图所示: +
+image +
+```python +def factor_connect(nfa_cells): + """ + factor_connect ::= factor | factor factor* + 实现乘操作,即连接 + """ + if is_connect_token(lexer.current_token): + factor(nfa_cells) + while is_connect_token(lexer.current_token): + new_cells = PairCell() + factor(new_cells) + nfa_cells.end_node.next_1 = new_cells.start_node + nfa_cells.end_node = new_cells.end_node + return True +``` +* expr用于实现或“|”操作(即加操作),其原理如下图所示: +
+image +
+ +```python +def expr(nfa_cells): + """ + expr ::= ("|" factor_connect)* + 实现OR操作 + """ + factor_connect(nfa_cells) # 第一支路 + new_cells = PairCell() # 其他支路 + while lexer.match(Token.OR): + lexer.next() + factor_connect(new_cells) + start = Cell() + start.next_1 = new_cells.start_node + start.next_2 = nfa_cells.start_node + nfa_cells.start_node = start + end = Cell() + new_cells.end_node.next_1 = end + nfa_cells.end_node.next_2 = end + nfa_cells.end_node = end + return True +``` +#### 3.2.6 判断输入字符串是否被NFA接受(match操作) +详细代码可在```'./nfa/run.py'```中查询
+* match, 整体思路就是依次读入字符串,记录根据跳转条件判断读入字符后NFA所处于的状态(即运行到的节点),然后重复这个过程,直到读取完,最后判断状态集合中是否有接受状态。 +```python +def match(input_string, start_node): + """匹配字符串 + input_string :: 需要配备的字符串 + start_node :: NFA起始节点 + return :: True | False + """ + # 初始化运行状态的状态集合: 起始节点+空转移能到达的节点 + current_state_set = [start_node] + next_state_set = closure(current_state_set) + # 循环读入字符生成状态集合 + for i, ch in enumerate(input_string): + # 读入一个字符后的状态集合+空转移能到达的节点 + current_state_set = move(next_state_set, ch) + next_state_set = closure(current_state_set) + # 状态集合为空,返回False + if next_state_set is None: + return False + # 读入最后一个字符且存在接受状态的返回True + if has_accepted_state(next_state_set) and i == len(input_string) - 1: + return True + return False +``` +* move,根据当前状态集合以及输入的字符输出所能跳转到的状态集合 +```python +def move(state_set, ch): + out_set = [] + for node in state_set: + if node.edge == CCL and ch in node.char_set: + out_set.append(node.next_1) + return out_set +``` +* closure,补充move操作后空转移所能到达的节点,并加入状态集合 +```python +def closure(state_set): + if len(state_set) <= 0: + return None + node_stack = [] + for i in state_set: + node_stack.append(i) + while len(node_stack) > 0: + node = node_stack.pop() + next1 = node.next_1 + next2 = node.next_2 + if next1 is not None and node.edge == EPSILON: + if next1 not in state_set: + state_set.append(next1) + node_stack.append(next1) + if next2 is not None and node.edge == EPSILON: + if next2 not in state_set: + state_set.append(next2) + node_stack.append(next2) + return state_set +``` +* has_accepted_state,判断状态集合中是否有接受状态(即没有next的节点) +```python +def has_accepted_state(state_set): + for state in state_set: + if state.next_1 is None and state.next_2 is None: + return True +``` +* 最后再把NFA的构建和运行的封装在一起通过```./myre.py :: match()```调用 +```python +def match(pattern_string,input_string): + """对input_string使用pattern_string进行正则匹配 + pattern_string :: 正则表达式 + input_string :: 需要匹配的字符串 + return :: True | False + """ + nfa_start_node = builder.create_nfa(pattern_string) + return run.match(input_string, nfa_start_node) +``` +### 3.3 实验测试结果 + +#### 3.3.1 正则表达式引擎基础测试 +在```./example.py```,有5个测试用例,用以测试正则引擎 +```python +# test 1 +#至少含两个连续0的0、1串组成的语言 +pattern = '(0|1)*00(0|1)*' +st1 = '01111011101' +st2 = '011110011101' +print('test 1:',myre.match(pattern, st1),myre.match(pattern, st2)) + +# test 2 +# 开头字符与尾字符相同都是0或1 +pattern = '(1(0|1)*1|0(0|1)*0)' +st1 = '01111011101' +st2 = '011110011100' +print('test 2:',myre.match(pattern, st1),myre.match(pattern, st2)) + +# test 3 +# 仅由'a'或'b'或'c'字符构成 +st1 = 'sakfhsrfefh' +st2 = 'abababababaaccccbaaaaabbababaaaa' +pattern = '[a-c]*' # 或者 (a|b|c) 或者{a,b,c}* +print('test 3:',myre.match(pattern, st1),myre.match(pattern, st2)) + +# test 4 +# 包含'hypo'的字符串 +st1 = 'sakfhypssdesrfefh' +st2 = 'sahypossdesrfefhhypoasd' +pattern = '.*(hypo)+.*' # 或者{a,b}* +print('test 4:',myre.match(pattern, st1),myre.match(pattern, st2)) + +# test 5 +# 极限操作,我也不知道这是啥 +st1 = 'sakfasdasd324228435' +st2 = 'Shenzhen University' +pattern = '((((s.*n|a)+)*)+{a,x}*|.* *.*|[a-z]*)*****' +print('test 5:',myre.match(pattern, st1),myre.match(pattern, st2)) +``` +运行结果如下: +
+image +
+ +#### 3.3.2 测试input.txt +详细代码在```./Question3.py```中 +* 读取input.txt +```python +f = open('./input.txt', 'r', encoding='utf-8') +input_txt = f.read() +f.close() +``` +* 将txt分割为单词 +```python +input_strings = myre.split(input_txt, [' ',',','"','.','(',')']) +``` +* 给定正则表达式并依次匹配,输出接受的串 +```python +pattern = 's{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}*n' +# 或者 pattern = 's[a-z]*n' +for string in input_strings: + if myre.match(pattern, string.lower()): + print(string) +``` +运行结果如下: +
+image +
+ +## Question4 +​ 请设计并编程实现一个简单分类器,要求各类别规则用正则表达式描述,实现输入任意一个字符串,自动给出该串的类别编号。例如定义 类别```1:0*1*```; 类别```2: 1*0*```。则给出串```0011```,程序将输出结果为类别```1```。
+### 4.1 思路 +使用Question3中设计的正则表达式引擎对输入的字符串进行判断即可 +### 4.2 代码实现 +详细代码在```./Question4.py```中 +* 使用字典类型定义类别 +```python +# pattern : class +class_map={ + '0*1*' : 1, + '1*0*' : 2, +} +``` +* 分类器的实现(就是依次对RE进行匹配,哪个匹配成功就是哪一类,都不匹配就输出None) +```python +def classify(string,class_map): + for pattern in class_map: + if myre.match(pattern, string): + return class_map[pattern] + return None +``` +* 获取命令行输入的字符串并进行分类 +```python +print('class_map:',class_map) +print('please input test string.') +while True: + input_string = input('>>') + print('class:',classify(input_string,class_map)) +``` +### 4.3 测试结果 +
+image +
+ +## References +[1] 课程对应的ppt文件以及课本 +[2] 陈安, 【Parser系列】Lexer I, https://zhuanlan.zhihu.com/p/26142441 +[3] 不是Zoe, BNF范式(巴科斯范式)到底是什么?, https://www.zhihu.com/question/27051306 +[4] Gloria, 实现一个四则运算语法解析器, https://zhuanlan.zhihu.com/p/112460676 +[5] dejavudwh, 实现一个正则表达式引擎in Python, https://www.cnblogs.com/secoding/p/11576864.html +[6] dejavudwh, Regex,https://github.com/dejavudwh/Regex +[7] xysun, regex, https://github.com/xysun/regex \ No newline at end of file diff --git a/Automata/example.py b/Automata/example.py index de06e5b7c630f305539743611ef0d22a07fd03ff..ba74a5700a0e47f04887afb41efde8e6442191be 100644 --- a/Automata/example.py +++ b/Automata/example.py @@ -1,13 +1,15 @@ import myre """ -规则: +对应所支持的语法: "." : 用于匹配任意单个字符 +"c" : 匹配除了Token以外的输入的字符 "[a-z]" : 匹配a-z中的任意单个字符 "{a,b,c}" : 匹配a或b或c中的任意单个字符,也可以写作(a|b|c) "*" : 闭包 "+" : 正闭包 "|" : 或(加) "" : 默认进行连接操作(乘) +"("expr")": 支持使用括号提高运算优先级 """ # test 1 diff --git a/Automata/imgs/Question3.png b/Automata/imgs/Question3.png new file mode 100644 index 0000000000000000000000000000000000000000..90a60de3b24beefc204d84bb4634cf53848182ee Binary files /dev/null and b/Automata/imgs/Question3.png differ diff --git a/Automata/imgs/Question4.png b/Automata/imgs/Question4.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf3a91c8e26ab2852ac72ebf53039e2ca74ff55 Binary files /dev/null and b/Automata/imgs/Question4.png differ diff --git a/Automata/imgs/closure.png b/Automata/imgs/closure.png new file mode 100644 index 0000000000000000000000000000000000000000..3dd37cd69ac049aa6c709f42a47129b3b2fb477d Binary files /dev/null and b/Automata/imgs/closure.png differ diff --git a/Automata/imgs/connect.png b/Automata/imgs/connect.png new file mode 100644 index 0000000000000000000000000000000000000000..44ce2c79dbf9d7cbf8c2bbb2e2ede35e51b3c031 Binary files /dev/null and b/Automata/imgs/connect.png differ diff --git a/Automata/imgs/example.png b/Automata/imgs/example.png new file mode 100644 index 0000000000000000000000000000000000000000..1580219f496db9a84de731e76f680814181e0259 Binary files /dev/null and b/Automata/imgs/example.png differ diff --git a/Automata/imgs/or.png b/Automata/imgs/or.png new file mode 100644 index 0000000000000000000000000000000000000000..f4e5465d6fdd5bef1332cfd4291fee61a45a51ce Binary files /dev/null and b/Automata/imgs/or.png differ diff --git a/Automata/imgs/term.png b/Automata/imgs/term.png new file mode 100644 index 0000000000000000000000000000000000000000..90d49bea868c7016ef7f69dc2d2c9498feaa79ea Binary files /dev/null and b/Automata/imgs/term.png differ diff --git a/Automata/myre.py b/Automata/myre.py index 65a4832aca73bb66abd13220acb44e6ba934b025..b3331400aa8c1eb02eee73e4612026d7516a49a5 100644 --- a/Automata/myre.py +++ b/Automata/myre.py @@ -7,15 +7,16 @@ def match(pattern_string,input_string): return :: True | False -------------------------------------------------------- - pattern规则: + 对应所支持的语法: "." : 用于匹配任意单个字符 + "c" : 匹配除了Token以外的输入的字符 "[a-z]" : 匹配a-z中的任意单个字符 "{a,b,c}" : 匹配a或b或c中的任意单个字符,也可以写作(a|b|c) "*" : 闭包 "+" : 正闭包 "|" : 或(加) "" : 默认进行连接操作(乘) - + "("expr")": 支持使用括号提高运算优先级 """ nfa_start_node = builder.create_nfa(pattern_string) return run.match(input_string, nfa_start_node) @@ -34,6 +35,3 @@ def split(string,keys): out_strings.append(string[cnt:i]) cnt = i+1 return out_strings - -def replace(): - pass \ No newline at end of file diff --git a/Automata/nfa/builder.py b/Automata/nfa/builder.py index b9299f1d41a3a78987cd721e224d0aa9342bc077..812a39832e05c67ed97a636d028f787e531ca651 100644 --- a/Automata/nfa/builder.py +++ b/Automata/nfa/builder.py @@ -20,7 +20,7 @@ def create_nfa(pattern_string): 词法分析按照优先级自顶向下 expr ::= ("|" factor_connect)* # "|" 加(或) factor_connect ::= factor | factor factor* # "" 乘(直接连接) -factor ::= term | term ("*"|"+")* # "*"闭包 "+"正闭包 +factor ::= term | term ("*"|"+")* # "*"闭包 "+"正闭包 term ::= char | group | "[" char "-" char "]" | "{" char "," char "}" | "." # 基本单元,终止符 group ::= "("expr")" # 递归解决括号优先级 """ @@ -40,15 +40,15 @@ def term(nfa_cells): """ 对 . | a (单个字符) | 单个一定范围[a-z]的字符 | {a,b,c} 某些字符集合中的单个字符->相当于(a+b+c) """ - if lexer.match(Token.L): # char + if lexer.match(Token.L): # char nfa_single_char(nfa_cells) - elif lexer.match(Token.ANY): # . + elif lexer.match(Token.ANY): # . nfa_any_single_char(nfa_cells) - elif lexer.match(Token.CCL_START): # [" char "-" char "] + elif lexer.match(Token.SQUARE_START): # [" char "-" char "] nfa_range_single_char(nfa_cells) - elif lexer.match(Token.OPEN_CURLY): # "{" char "," char "}" + elif lexer.match(Token.OPEN_CURLY): # "{" char "," char "}" nfa_set_single_char(nfa_cells) - elif lexer.match(Token.OPEN_PAREN): # "("expr")" + elif lexer.match(Token.OPEN_PAREN): # "("expr")" group(nfa_cells) def factor(nfa_cells): @@ -100,7 +100,7 @@ def expr(nfa_cells): def nfa_single_char(nfa_cells): """ - L 匹配单个字符 + L 匹配输入的单个字符 """ if not lexer.match(Token.L): return False @@ -117,7 +117,7 @@ def nfa_single_char(nfa_cells): def nfa_any_single_char(nfa_cells): """ - . 匹配任意单个字符 + . 匹配单个任意字符 """ if not lexer.match(Token.ANY): return False @@ -135,9 +135,9 @@ def nfa_any_single_char(nfa_cells): def nfa_range_single_char(nfa_cells): """ - [a-z] 匹配范围字符集 + [a-z] 匹配范围字符集中的单个字符 """ - if not lexer.match(Token.CCL_START): + if not lexer.match(Token.SQUARE_START): return False lexer.next() start = nfa_cells.start_node = Cell() @@ -147,7 +147,7 @@ def nfa_range_single_char(nfa_cells): # get range char set first = '' - while not lexer.match(Token.CCL_END): + while not lexer.match(Token.SQUARE_END): if not lexer.match(Token.DASH): first = lexer.current_text start.char_set.add(first) @@ -162,7 +162,7 @@ def nfa_range_single_char(nfa_cells): def nfa_set_single_char(nfa_cells): """ - {a,b,c....} 匹配字符集 相当于(a|b|c...) + {a,b,c....} 匹配字符集中的单个字符 相当于(a|b|c...) """ if not lexer.match(Token.OPEN_CURLY): return False @@ -208,7 +208,7 @@ def is_connect_token(token): Token.CLOSURE, Token.PLUS_CLOSURE, Token.CLOSE_CURLY, - Token.CCL_END, + Token.SQUARE_END, Token.OR, ] return token not in no_connect diff --git a/Automata/nfa/cell.py b/Automata/nfa/cell.py index af3c8ae3a7979a5c8a39da68d341a5a7e15ebf10..e4ce790fc89403a076a5edc21da0380806698483 100644 --- a/Automata/nfa/cell.py +++ b/Automata/nfa/cell.py @@ -1,6 +1,6 @@ -#边的类型 +# 根据RE转NFA的规律边的类型分为3种 EPSILON = -1 # edge = EPSILON 对应的节点有两个出去的ε边 -CCL = -2 # edge = CCL 边对应的是字符集(包括单个字符) ,需要结合属性char_set +CCL = -2 # edge = CCL 边对应的是字符集(包括单个字符) ,需要结合属性char_set,自动机运行时只要当前读入的字符在char_set中就满足跳转条件 EMPTY = -3 # edge = EMPTY 一条ε边 class Cell(object): diff --git a/Automata/nfa/lexer.py b/Automata/nfa/lexer.py index 09021531544c3710ae630afaf448866c8eb18f49..b2d014548e7356cd298a162bc3d324daab35c6a3 100644 --- a/Automata/nfa/lexer.py +++ b/Automata/nfa/lexer.py @@ -5,8 +5,8 @@ class Token(Enum): END_OF_INPUT = 1 ANY = 2 L = 3 - CCL_START = 4 - CCL_END = 5 + SQUARE_START = 4 + SQUARE_END = 5 OPEN_CURLY = 6 CLOSE_CURLY = 7 CLOSURE = 8 @@ -20,8 +20,8 @@ Tokens = { '.': Token.ANY, '(': Token.OPEN_PAREN, ')': Token.CLOSE_PAREN, - '[': Token.CCL_START, - ']': Token.CCL_END, + '[': Token.SQUARE_START, + ']': Token.SQUARE_END, '{': Token.OPEN_CURLY, '}': Token.CLOSE_CURLY, '*': Token.CLOSURE, @@ -30,18 +30,35 @@ Tokens = { '|': Token.OR, } +""" +对应所支持的语法: +"." : 用于匹配任意单个字符 +"c" : 匹配除了Token以外的输入的字符 +"[a-z]" : 匹配a-z中的任意单个字符 +"{a,b,c}" : 匹配a或b或c中的任意单个字符,也可以写作(a|b|c) +"*" : 闭包 +"+" : 正闭包 +"|" : 或(加) +"" : 默认进行连接操作(乘) +"("expr")": 支持使用括号提高运算优先级 +""" + class Lexer(object): + """ + 对正则表达式进行解析,校验正则表达式是否合法,得到对应字符的Token,目前不支持转义字符的输入 + """ def __init__(self, pattern): self.pattern = pattern self.pos = 0 self.current_text = '' self.current_token = None + self.check_pattern() # self.del_paren() # print(self.pattern) def next(self): """ - 返回Token并读入下一个字符 + 返回当前对应的Tokens并读入下一个字符 """ pos = self.pos pattern = self.pattern @@ -61,6 +78,23 @@ class Lexer(object): self.pos = self.pos + 1 return Tokens.get(text, Token.L) + def check_pattern(self): + """ + 检查正则表达式括号是否匹配 + """ + brackets = ['(',')','[',']','{','}'] + cnt = [0,0,0,0,0,0] + for i in range(len(self.pattern)): + for j in range(len(brackets)): + if self.pattern[i] == brackets[j]: + cnt[j] += 1 + if cnt[0] < cnt[1] or cnt[2] < cnt[3] or cnt[4] < cnt[5]: + print('Error: Please check the input pattern') + exit(0) + if cnt[0] != cnt[1] or cnt[2] != cnt[3] or cnt[4] != cnt[5]: + print('Error: Please check the input pattern') + exit(0) + def del_paren(self): cnt = 0 for i in range(len(self.pattern)): @@ -70,4 +104,7 @@ class Lexer(object): cnt += 1 def match(self, token): + """ + 将输入的token与当前的token进行比较 + """ return self.current_token == token \ No newline at end of file diff --git a/Automata/solution.md b/Automata/solution.md deleted file mode 100644 index fb309f42ea5be1a10550da3389a69444c9c25203..0000000000000000000000000000000000000000 --- a/Automata/solution.md +++ /dev/null @@ -1,82 +0,0 @@ -[toc] -## Question1 -设计一个DFA,使其接受2进制字符串w,并且所有w的逆都能被5整除。比如,该DFA接受二进制串11110,因为其逆为01111,代表十进制的15,所以可以被5整除。详述该DFA的设计。 -### 1.1 思路 -首先设计一个接受的二进制串能被5整除的的DFA,然后将整个DFA的状态反转使其接受的为符合条件的二进制的逆。 -### 1.2 设计一个接受的二进制串能被5整除的DFA -* 将已扫描到的字符的余数作为当前状态,一共有5种情况,即5个状态:Q0:余数为0;Q1余数为1......根据规律列出状态转移表如下:
-
-image -
-* 根据状态转移表绘制状态转移图:
-
-image -
-### 1.3 反转状态转移图 -* 将1.2中的DFA的状态反转(将箭头反向并将起始状态与接受状态对调)并根据q0的转移函数添加起始状态qs以防止出现未输入任何字符就接受的情况:
-
-image -
- -## Question2 -使用归纳证明法证明对于任意的NFA M=(Q,Σ,δ,q0 ,F),一定存在CFG G=(Q,Σ,P,S),使得L(G)=L(M)。 -### 2.1 思路 -等价转化法:已知NFA与RE等价,故问题转化为对任意的正则表达式R,都可以用CFG G=(Q,Σ,P,S)来表示。即要证明L(R)=L(G)。
-### 2.2 文法设计 -RE由运算符,字符集,以及'()'组成,故根据运算优先级可以得到文法G:
-```shell -S -> A -A -> B|A"|"B # "|" 加(或) -B -> C|BC # "" 乘(直接连接),幂运算可由乘实现 -C -> D|D"*"|D"+"|CD"*"|CD"+" # "*"闭包 "+"正闭包 -D -> E|F -E -> "a","b","c","d","e","f","g"...... #终结符 -F -> "("A")" #括号递归 -``` -### 2.3 归纳法证明 -对输入的正则表达式R的运算符数量进行归纳法证明。
-* 当n=1时:
-R = "a*" | "a|a" | "a+" | "aa" | "(aa)"......
-以R =(aa)为例的派生过程:
-S=>A=>B=>C=>D=>F=>"("A")"=>"("B")"=>"("BC")"=>"("CC")"=>"("CD")"=>"("DD")"=>"("DE")"=>"("EE")"=>"(""a""a"")"即S=>(aa)
-易知其他情况也满足。
-* 假设当运算符数量为n时,正则表达式R也是合法的G
-当运算符数量为n+1时:
-R' = "R*" | "R|a" | "R+" | "Ra" | "(Ra)"...
-由于G可以派生出R,故S=>A=>....R可得A=>R
-以R' ="R|a"的派生过程为例:
-S=>A=>A"|"B=>R"|"B=>R"|"C=>R"|"D=>R"|"E=>R"|""a"即S=>R‘,成立
-对其他情况依次进行归纳,均成立
-故对任意的正则表达式R,都可以由所定义的 G得到,即对于任意的NFA M=(Q,Σ,δ,q0 ,F),一定存在CFG G=(Q,Σ,P,S),使得L(G)=L(M)。 - -## Question3 -正则表达式在信息检索中具有广泛的应用。请设计并编程实现一个基于正则表达式的英文单词检索系统,要求给定任意的正则表达式作为输入,将其转换为等价的自动机,并可以根据该自动机实现从输入文本中检测并输出所有符合正则表达式描述的单词。 -例如,当输入文本文件input.txt包含以下英文文本及正则表达式```s{a,…,z}*n```时: -``` -Shenzhen University (SZU, Chinese: 深圳大学) is a public university established in 1983 located in Nanshan district, Shenzhen, Guangdong, China. It is accredited by the State Council of the People's Republic of China and is funded by the Shenzhen City Government. The university took its first enrollment the same year at what Deng Xiaoping called "Shenzhen Speed". Deng also was named the "father of Shenzhen University." It is regarded as the fastest developing university in China, and also one of the "Top 100 Universities in China" and one of the top university which is listed in the World Top Ranking Universities. -``` -输出: -``` -Shenzhen -Shenzhen -Shenzhen -Shenzhen -Shenzhen -``` -为所有符合该正则表达式描述得单词,注意,此处单词间以空格分隔,并且对大小写字母不敏感。请在提交报告中给出详细的设计方案、关键代码实现、以及实验测试结果。 -### 3.1 思路 -### 3.2 正则表达式引擎的设计 -#### 3.2.1 整体流程 -#### 3.2.2 NFA单个节点的定义 -#### 3.2.3 语义分析 -#### 3.2.4 NFA的构建 - -#### 3.2.3 词法分析即校验 -### 3.3 实验测试结果 -#### 3.3.1 正则表达式引擎基础测试 -#### 3.3.2 测试input.txt - - -## Question4 -请设计并编程实现一个简单分类器,要求各类别规则用正则表达式描述,实现输入任意一个字符串,自动给出该串的类别编号。例如定义 类别1:0*1*; 类别2: 1*0*。则给出串0011,程序将输出结果为类别1。 -