项目地址:Regex in Python

前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化

DFA的定义

对于NFA转换为DFA的算法,主要就是将NFA中可以状态节点进行合并,进而让状态节点对于一个输入字符都有唯一的一个跳转节点

所以对于DFA的节点就含有一个nfa状态节点的集合和一个唯一的标识和对是否是接收状态的flag

  1. class Dfa(object):
  2. STATUS_NUM = 0
  3. def __init__(self):
  4. self.nfa_sets = []
  5. self.accepted = False
  6. self.status_num = -1
  7. @classmethod
  8. def nfas_to_dfa(cls, nfas):
  9. dfa = cls()
  10. for n in nfas:
  11. dfa.nfa_sets.append(n)
  12. if n.next_1 is None and n.next_2 is None:
  13. dfa.accepted = True
  14. dfa.status_num = Dfa.STATUS_NUM
  15. Dfa.STATUS_NUM = Dfa.STATUS_NUM + 1
  16. return dfa

NFA转换为DFA

将NFA转换为DFA的最终目标是获得一张跳转表,这个和之前C语言编译的语法分析表有点像

这个函数就是NFA转换为DFA的全部算法了,主要逻辑就是:

  • 先利用之前的closure算法,计算出可以合并的NFA节点,然后生成一个DFA的节点
  • 然后对这个DFA集合进行遍历
  • 之后对于每个输入字符进行move操作,然后对得到的move集合再进行一次closure操作,这样就可以得到下一个DFA状态节点(这里还要进行一个判重的操作,就是可能当前DFA状态节点可能已经生成过了)
  • 然后将这两个节点的对应关系放入跳转表中
  • 这时候的DFA如果其中含有的NFA存在一个可接收的状态节点,那么当前的DFA的当然也是可接受状态了
  1. def convert_to_dfa(nfa_start_node):
  2. jump_table = list_dict(MAX_DFA_STATUS_NUM)
  3. ns = [nfa_start_node]
  4. n_closure = closure(ns)
  5. dfa = Dfa.nfas_to_dfa(n_closure)
  6. dfa_list.append(dfa)
  7. dfa_index = 0
  8. while dfa_index < len(dfa_list):
  9. dfa = dfa_list[dfa_index]
  10. for i in range(ASCII_COUNT):
  11. c = chr(i)
  12. nfa_move = move(dfa.nfa_sets, c)
  13. if nfa_move is not None:
  14. nfa_closure = closure(nfa_move)
  15. if nfa_closure is None:
  16. continue
  17. new_dfa = convert_completed(dfa_list, nfa_closure)
  18. if new_dfa is None:
  19. new_dfa = Dfa.nfas_to_dfa(nfa_closure)
  20. dfa_list.append(new_dfa)
  21. next_state = new_dfa.status_num
  22. jump_table[dfa.status_num][c] = next_state
  23. if new_dfa.accepted:
  24. jump_table[new_dfa.status_num]['accepted'] = True
  25. dfa_index = dfa_index + 1
  26. return jump_table

DFA最小化

DFA最小化本质上是也是对状态节点的合并,然后分区

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

Dfa分区定义

DfaGroup和之前的定义大同小异,都是有一个唯一的标识和一个放DFA状态节点的list

  1. class DfaGroup(object):
  2. GROUP_COUNT = 0
  3. def __init__(self):
  4. self.set_count()
  5. self.group = []
  6. def set_count(self):
  7. self.group_num = DfaGroup.GROUP_COUNT
  8. DfaGroup.GROUP_COUNT = DfaGroup.GROUP_COUNT + 1
  9. def remove(self, element):
  10. self.group.remove(element)
  11. def add(self, element):
  12. self.group.append(element)
  13. def get(self, count):
  14. if count > len(self.group) - 1:
  15. return None
  16. return self.group[count]
  17. def __len__(self):
  18. return len(self.group)

Minimize DFA

partition是最小化DFA算法最重要的部分

  • 会先从跳转表中找出当前DFA对应跳转的下一个状态节点
  • first是用来比较的DFA节点
  • 如果next节点的下一个状态和first节点的下一状态不在同一分区下的话,说明它们不可以在同一个分区
  • 就重新创建一个新分区

所以其实DFA最小化做的就是合并相同的下一个跳转状态的节点

  1. def partition(jump_table, group, first, next, ch):
  2. goto_first = jump_table[first.status_num].get(ch)
  3. goto_next = jump_table[next.status_num].get(ch)
  4. if dfa_in_group(goto_first) != dfa_in_group(goto_next):
  5. new_group = DfaGroup()
  6. group_list.append(new_group)
  7. group.remove(next)
  8. new_group.add(next)
  9. return True
  10. return False

创建跳转表

再分完区之后节点和节点间的跳转就变成了区和区间的跳转了

  • 遍历DFA集合
  • 从之前的跳转表中找到相应的节点和相应的跳转关系
  • 然后找出它们对应的分区,即转换为分区和分区之间的跳转
  1. def create_mindfa_table(jump_table):
  2. trans_table = list_dict(ASCII_COUNT)
  3. for dfa in dfa_list:
  4. from_dfa = dfa.status_num
  5. for i in range(ASCII_COUNT):
  6. ch = chr(i)
  7. to_dfa = jump_table[from_dfa].get(ch)
  8. if to_dfa:
  9. from_group = dfa_in_group(from_dfa)
  10. to_group = dfa_in_group(to_dfa)
  11. trans_table[from_group.group_num][ch] = to_group.group_num
  12. if dfa.accepted:
  13. from_group = dfa_in_group(from_dfa)
  14. trans_table[from_group.group_num]['accepted'] = True
  15. return trans_table

匹配输入字符串

利用跳转表进行对输入字符串的匹配的逻辑非常简单

  • 遍历输入的字符串
  • 拿到当前状态对应的输入的跳转关系
  • 进行跳转或者完成匹配
  1. def dfa_match(input_string, jump_table, minimize=True):
  2. if minimize:
  3. cur_status = dfa_in_group(0).group_num
  4. else:
  5. cur_status = 0
  6. for i, c in enumerate(input_string):
  7. jump_dict = jump_table[cur_status]
  8. if jump_dict:
  9. js = jump_dict.get(c)
  10. if js is None:
  11. return False
  12. else:
  13. cur_status = js
  14. if i == len(input_string) - 1 and jump_dict.get('accepted'):
  15. return True
  16. 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. C# ModBus 读取数据

    简单介绍: 项目上需要与多家公司做接口对接.我们提供接口的有,其他公司提供的接口也有.所有的接口全部对接完了,遇到一个非常棘手的问题,需要获取甲方船厂设备上的状态,就给了一个文档,文档上写了IP.端口 ...

  2. Selenium + python 测试环境搭建扩展-HTMLUNIT的使用

    尝试给公司的网站写每日例行检查的脚本时,不需要去打开浏览器,这是就用到HTMLUNIT的使用 HTMLUNIT是基于Selenium服务端的,所以需要selenium-server-standalon ...

  3. Code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 11.0.1'

    Code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 11.0.1' 进入 projects and lis ...

  4. AI芯片:高性能卷积计算中的数据复用

    随着深度学习的飞速发展,对处理器的性能要求也变得越来越高,随之涌现出了很多针对神经网络加速设计的AI芯片.卷积计算是神经网络中最重要的一类计算,本文分析了高性能卷积计算中的数据复用,这是AI芯片设计中 ...

  5. Java中时间格式处理,指定N天/小时等之后的时间

    1)根据当前时间,获取具体的时刻的时间 N天前 M小时之前 可用 new Date().getTime() - 24 * 60 * 60 * 1000*N[N天之前]的方法来获取处理时间之后的具体的值 ...

  6. Django--路由层、伪静态页面、虚拟环境、视图层

    路由层: 在路由匹配的时候,第一个参数是一个正则表达式,这也就意味着在路由匹配的时候按照正则匹配的规则去匹配,路由匹配的顺序是从上往下依次匹配的,只要匹配到一个,就会执行对应的函数,就不会执行下面的函 ...

  7. 体验SpringCloud Gateway

    Spring Cloud Gateway是Spring Cloud技术栈中的网关服务,本文实战构建一个SpringCloud环境,并开发一个SpringCloud Gateway应用,快速体验网关服务 ...

  8. Keras(三)backend 兼容 Regressor 回归 Classifier 分类 原理及实例

    backend 兼容 backend,即基于什么来做运算 Keras 可以基于两个Backend,一个是 Theano,一个是 Tensorflow 查看当前backend import keras ...

  9. E-Find the median_2019牛客暑期多校训练营(第七场)

    题意 N次操作,每次塞入区间\([L,R]\)的每个数,并输出此时的中位数. 题解 如果题目不是每次塞入一整个区间,而是只塞入一个数,可以简单的建权值线段树查询区间第K大,由于每次都是查询整个区间就不 ...

  10. poj 2240 Arbitrage(Bellman_ford变形)

    题目链接:http://poj.org/problem?id=2240 题目就是要通过还钱涨自己的本钱最后还能换回到自己原来的钱种. 就是判一下有没有负环那么就直接用bellman_ford来判断有没 ...