实现一个正则表达式引擎in Python(三)
项目地址:Regex in Python
前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化
DFA的定义
对于NFA转换为DFA的算法,主要就是将NFA中可以状态节点进行合并,进而让状态节点对于一个输入字符都有唯一的一个跳转节点
所以对于DFA的节点就含有一个nfa状态节点的集合和一个唯一的标识和对是否是接收状态的flag
class Dfa(object):
STATUS_NUM = 0
def __init__(self):
self.nfa_sets = []
self.accepted = False
self.status_num = -1
@classmethod
def nfas_to_dfa(cls, nfas):
dfa = cls()
for n in nfas:
dfa.nfa_sets.append(n)
if n.next_1 is None and n.next_2 is None:
dfa.accepted = True
dfa.status_num = Dfa.STATUS_NUM
Dfa.STATUS_NUM = Dfa.STATUS_NUM + 1
return dfa
NFA转换为DFA
将NFA转换为DFA的最终目标是获得一张跳转表,这个和之前C语言编译的语法分析表有点像
这个函数就是NFA转换为DFA的全部算法了,主要逻辑就是:
- 先利用之前的closure算法,计算出可以合并的NFA节点,然后生成一个DFA的节点
- 然后对这个DFA集合进行遍历
- 之后对于每个输入字符进行move操作,然后对得到的move集合再进行一次closure操作,这样就可以得到下一个DFA状态节点(这里还要进行一个判重的操作,就是可能当前DFA状态节点可能已经生成过了)
- 然后将这两个节点的对应关系放入跳转表中
- 这时候的DFA如果其中含有的NFA存在一个可接收的状态节点,那么当前的DFA的当然也是可接受状态了
def convert_to_dfa(nfa_start_node):
jump_table = list_dict(MAX_DFA_STATUS_NUM)
ns = [nfa_start_node]
n_closure = closure(ns)
dfa = Dfa.nfas_to_dfa(n_closure)
dfa_list.append(dfa)
dfa_index = 0
while dfa_index < len(dfa_list):
dfa = dfa_list[dfa_index]
for i in range(ASCII_COUNT):
c = chr(i)
nfa_move = move(dfa.nfa_sets, c)
if nfa_move is not None:
nfa_closure = closure(nfa_move)
if nfa_closure is None:
continue
new_dfa = convert_completed(dfa_list, nfa_closure)
if new_dfa is None:
new_dfa = Dfa.nfas_to_dfa(nfa_closure)
dfa_list.append(new_dfa)
next_state = new_dfa.status_num
jump_table[dfa.status_num][c] = next_state
if new_dfa.accepted:
jump_table[new_dfa.status_num]['accepted'] = True
dfa_index = dfa_index + 1
return jump_table
DFA最小化
DFA最小化本质上是也是对状态节点的合并,然后分区
- 先根据是否为接收状态进行分区
- 再根据DFA跳转表的跳转关系对分区里的节点进行再次分区,如果当前DFA节点跳转后的状态节点也位于同一个分区中,证明它们可以被归为一个分区
- 重复上面的算法
Dfa分区定义
DfaGroup和之前的定义大同小异,都是有一个唯一的标识和一个放DFA状态节点的list
class DfaGroup(object):
GROUP_COUNT = 0
def __init__(self):
self.set_count()
self.group = []
def set_count(self):
self.group_num = DfaGroup.GROUP_COUNT
DfaGroup.GROUP_COUNT = DfaGroup.GROUP_COUNT + 1
def remove(self, element):
self.group.remove(element)
def add(self, element):
self.group.append(element)
def get(self, count):
if count > len(self.group) - 1:
return None
return self.group[count]
def __len__(self):
return len(self.group)
Minimize DFA
partition是最小化DFA算法最重要的部分
- 会先从跳转表中找出当前DFA对应跳转的下一个状态节点
- first是用来比较的DFA节点
- 如果next节点的下一个状态和first节点的下一状态不在同一分区下的话,说明它们不可以在同一个分区
- 就重新创建一个新分区
所以其实DFA最小化做的就是合并相同的下一个跳转状态的节点
def partition(jump_table, group, first, next, ch):
goto_first = jump_table[first.status_num].get(ch)
goto_next = jump_table[next.status_num].get(ch)
if dfa_in_group(goto_first) != dfa_in_group(goto_next):
new_group = DfaGroup()
group_list.append(new_group)
group.remove(next)
new_group.add(next)
return True
return False
创建跳转表
再分完区之后节点和节点间的跳转就变成了区和区间的跳转了
- 遍历DFA集合
- 从之前的跳转表中找到相应的节点和相应的跳转关系
- 然后找出它们对应的分区,即转换为分区和分区之间的跳转
def create_mindfa_table(jump_table):
trans_table = list_dict(ASCII_COUNT)
for dfa in dfa_list:
from_dfa = dfa.status_num
for i in range(ASCII_COUNT):
ch = chr(i)
to_dfa = jump_table[from_dfa].get(ch)
if to_dfa:
from_group = dfa_in_group(from_dfa)
to_group = dfa_in_group(to_dfa)
trans_table[from_group.group_num][ch] = to_group.group_num
if dfa.accepted:
from_group = dfa_in_group(from_dfa)
trans_table[from_group.group_num]['accepted'] = True
return trans_table
匹配输入字符串
利用跳转表进行对输入字符串的匹配的逻辑非常简单
- 遍历输入的字符串
- 拿到当前状态对应的输入的跳转关系
- 进行跳转或者完成匹配
def dfa_match(input_string, jump_table, minimize=True):
if minimize:
cur_status = dfa_in_group(0).group_num
else:
cur_status = 0
for i, c in enumerate(input_string):
jump_dict = jump_table[cur_status]
if jump_dict:
js = jump_dict.get(c)
if js is None:
return False
else:
cur_status = js
if i == len(input_string) - 1 and jump_dict.get('accepted'):
return True
return jump_table[cur_status].get('accepted') is not None
总结
到此已经完成了一个简单的正则表达式引擎的所有过程
正则表达式 -> NFA -> DFA -> DFA最小化 -> 进行匹配
实现一个正则表达式引擎in Python(三)的更多相关文章
- 实现一个正则表达式引擎in Python(一)
前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...
- 实现一个正则表达式引擎in Python(二)
项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...
- 1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用
简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA ...
- Python的regex模块——更强大的正则表达式引擎
Python自带了正则表达式引擎(内置的re模块),但是不支持一些高级特性,比如下面这几个: 固化分组 Atomic grouping 占有优先量词 Possessive quantifi ...
- 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数
整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...
- 基于ε-NFA的正则表达式引擎
正则表达式几乎每个程序员都会用到,对于这么常见的一个语言,有没有想过怎么去实现一个呢?乍一想,也许觉得困难,实际上实现一个正则表达式的引擎并没有想像中的复杂,<编译原理>一书中有一章专门讲 ...
- 正则表达式—RegEx(RegularExpressio)(三)
今日随笔,继续写一点关于正则表达式的 知识.前两天介绍了正则表达式验证匹配,提取等一些基本的知识,今天继续分享下它的另一个强大的应用:替换(replace). 开始之前,还是要补一下昨天的内容. 在我 ...
- (转)正则表达式—RegEx(RegularExpressio)(三)
原文地址:http://www.cnblogs.com/feng-c-x/archive/2013/09/05/3302465.html 今日随笔,继续写一点关于正则表达式的 知识.前两天介绍了正则表 ...
- 正则表达式引擎:nfa的转换规则。
正则表达式引擎:nfa的转换规则. 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多 ...
随机推荐
- 2018年蓝桥杯b组国赛真题
1.标题:换零钞x星球的钞票的面额只有:100元,5元,2元,1元,共4种.小明去x星旅游,他手里只有2张100元的x星币,太不方便,恰好路过x星银行就去换零钱.小明有点强迫症,他坚持要求200元换出 ...
- JSON格式提取相同属性的某个值
[ {UID:"222",value:"111"}, {UID:"222",value:"103"}, {UID:&qu ...
- Scratch 3下载,最新版Scratch下载,macOS、Windows版
下载地址:https://scratch.mit.edu/download 废话不多说,先上下载地址! 之前小弟学习Scratch,用的2.0发现诸多BUG,到度娘想下最新版却没有发现一篇比较正经的文 ...
- SpringMVC整合Apache Shiro
关于什么是Shiro,可以查看这篇文章http://www.cnblogs.com/Laymen/articles/6117751.html 一.添加maven依赖 <dependency> ...
- 消息中间件——RabbitMQ(九)RabbitMQ整合Spring AMQP实战!(全)
前言 1. AMQP 核心组件 RabbitAdmin SpringAMQP声明 RabbitTemplate SimpleMessageListenerContainer MessageListen ...
- BZOJ-3343教主的魔法+分块(大块排序二分)
传送门:https://www.luogu.org/problemnew/show/P2801 参考:http://hzwer.com/2784.html 感觉思路无比清晰:) ps:我在洛谷A的, ...
- atcoder D - Game on Tree(树形dp+尼姆博弈)
题目链接:http://agc017.contest.atcoder.jp/tasks/agc017_d 题解:简单的树上的尼姆博弈,这个应该看的出来然后就是简单的树形dp然后异或一下就行. #inc ...
- CF981C Useful Decomposition 树 dfs 二十三 *
Useful Decomposition time limit per test 1 second memory limit per test 256 megabytes input standard ...
- 模板类型推导、auto推导
effective modern c++ 果然是神书,干货满满,简单记录下. item1 模板推倒 典型的模板函数 temlate<class T> void fn(ParamType p ...
- go 学习笔记之无心插柳柳成荫的接口和无为而治的空接口
如果你还了解编程概念中的接口概念,那么我建议你最好还是先阅读上一篇文章.详情请点击 go 学习笔记之万万没想到宠物店竟然催生出面向接口编程? ,否则的话,请自动忽略上文,继续探索 Go 语言的接口有什 ...