zoukankan      html  css  js  c++  java
  • 实现一个正则表达式引擎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最小化本质上是也是对状态节点的合并,然后分区

    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最小化 -> 进行匹配

  • 相关阅读:
    PL/SQL 自动补全[转]
    关闭开发环境的开发者模式
    删除带外键的表【foreign key constraint fails】报错
    CSSS选择器总结
    XSS漏洞初窥(通过dvwa平台进测试)
    Oracle 闪回 找回数据
    Oracle 函数 Function
    Oracle 存储过程 PROCEDURE
    Oracle 备份与恢复
    Web应用程序架构的比较
  • 原文地址:https://www.cnblogs.com/secoding/p/11582310.html
Copyright © 2011-2022 走看看