项目地址: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最小化本质上是也是对状态节点的合并,然后分区

  1. 先根据是否为接收状态进行分区
  2. 再根据DFA跳转表的跳转关系对分区里的节点进行再次分区,如果当前DFA节点跳转后的状态节点也位于同一个分区中,证明它们可以被归为一个分区
  3. 重复上面的算法

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(三)的更多相关文章

  1. 实现一个正则表达式引擎in Python(一)

    前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...

  2. 实现一个正则表达式引擎in Python(二)

    项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...

  3. 1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用

    简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA ...

  4. Python的regex模块——更强大的正则表达式引擎

    Python自带了正则表达式引擎(内置的re模块),但是不支持一些高级特性,比如下面这几个: 固化分组    Atomic grouping 占有优先量词    Possessive quantifi ...

  5. 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数

    整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...

  6. 基于ε-NFA的正则表达式引擎

    正则表达式几乎每个程序员都会用到,对于这么常见的一个语言,有没有想过怎么去实现一个呢?乍一想,也许觉得困难,实际上实现一个正则表达式的引擎并没有想像中的复杂,<编译原理>一书中有一章专门讲 ...

  7. 正则表达式—RegEx(RegularExpressio)(三)

    今日随笔,继续写一点关于正则表达式的 知识.前两天介绍了正则表达式验证匹配,提取等一些基本的知识,今天继续分享下它的另一个强大的应用:替换(replace). 开始之前,还是要补一下昨天的内容. 在我 ...

  8. (转)正则表达式—RegEx(RegularExpressio)(三)

    原文地址:http://www.cnblogs.com/feng-c-x/archive/2013/09/05/3302465.html 今日随笔,继续写一点关于正则表达式的 知识.前两天介绍了正则表 ...

  9. 正则表达式引擎:nfa的转换规则。

    正则表达式引擎:nfa的转换规则. 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多 ...

随机推荐

  1. 小白学Python(2)——常用Python编程工具,Python IDE

    下载好Python,但是如何开始编程呢? 有几种方法, 1.第一个就是command lind 即为命令行的方式,也就是我们常说的cmd. 输入 win+ cmd 在命令行中再输入 python,即可 ...

  2. Python笔记_初级语法

    1.标识符与变量 1.1 标识符 规范 只能由数字,字母,_(下划线)组成 不能以数字开头 不能是关键字 区分大小写 命名约束 下划线分隔法(推荐): 多个单词组成的名称,使用全小写字母书写,中间使用 ...

  3. Python数据分析之pandas基本数据结构:Series、DataFrame

    1引言 本文总结Pandas中两种常用的数据类型: (1)Series是一种一维的带标签数组对象. (2)DataFrame,二维,Series容器 2 Series数组 2.1 Series数组构成 ...

  4. 【原创】Linux cpuidle framework

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  5. JWT原理 使用(入门篇)

    1.JWT简介 JWT:Json Web Token,是基于Json的一个公开规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,他的两大使用场景是:认证和数据交换 使用起来就是,由 ...

  6. JUC包Lock机制的支持--AQS

    在上一次总结中,提到了JUC包下使用Lock接口实现同步的方法,以及和Synchronized关键字的一些比较,那么使用Lock完成锁机制的底层支持又是什么呢?总结如下: 1 AQS是什么 AQS是一 ...

  7. 在IIS下部署PHP

    没有.net ramework 4.0 的要先安装 dotNetFx40_Full_x86_x64.exe PHP压缩包 推荐用5.6.29版 IIS下PHP压缩包下载地址:"http:// ...

  8. FZU oj Problem 2082 过路费

                                                                                    Problem 2082 过路费 Pro ...

  9. 入门MySQL——备份与恢复

    前言:  前面几篇文章为大家介绍了 MySQL 各种语句语法的用法及用户权限相关知识.本篇文章将主要讲解 MySQL 数据库数据备份与恢复相关知识,主要聚焦于逻辑备份,介绍mysqldump工具的使用 ...

  10. eql框架。

    在刚进入公司的时候,在service层的框架用的是eql,是公司内的大佬封装的,作为一个小白,真的是折磨.公司内没有任何的文档,只能靠着自己一步一步的摸索. 后来用习惯了,发现这个框架确实有自己的独到 ...