Python实现DFA算法详解:从原理到代码

季宁
季宁
发布时间:2026-10-14 | 6人浏览过
原创

本文深入解析Python中DFA(确定有限状态自动机)算法的实现原理与应用。通过构建嵌套字典结构,文章展示了如何高效处理关键词匹配,包括处理包含关系和添加通配符支持。相比传统循环遍历,DFA在大数据量匹配场景下具有显著性能优势,适合敏感词过滤等实际应用。

一、概述

在计算机操作系统中,进程的状态及其切换机制可以作为理解DFA(确定有限状态自动机)算法的一种直观类比。如参考图示,图中椭圆代表不同的状态,连接椭圆的线条代表触发状态转换的事件。无论是进程的状态还是引发状态变化的事件,它们都是确定的,并且所有可能的情况都可以被穷举出来。

DFA算法在实际开发中有多种应用场景,本文首先聚焦于其在关键词匹配领域的应用。

二、匹配关键词

我们可以将文本中的每个字符或片段视为一个状态节点。以“匹配关键词”为例,该短语可以拆解为“匹”、“匹配”、“匹配关”、“匹配关键”和“匹配关键词”这五个逐步递进的文本片段,每个片段对应DFA中的一个状态。

具体匹配过程如下:

  • 初始状态下,系统处于空状态。当输入字符“匹”作为事件触发时,系统转换至状态“匹”;
  • 随后若触发事件“配”,系统进一步转换至状态“匹配”;
  • 依此类推,持续进行状态转换,直至到达最后一个状态“匹配关键词”,此时匹配完成。

进一步考虑包含多个关键词的情况,例如同时存在“匹配算法”、“匹配关键词”以及“信息抽取”。

观察状态转换图可以发现,其结构类似于树形结构。正是由于这种树形结构的存在,DFA算法在关键词匹配效率上通常优于简单的关键词迭代方法(如for循环)。熟悉LeetCode的读者应当了解,树形结构遍历的时间复杂度通常低于线性遍历的时间复杂度。

以下是传统for循环匹配方式的示例:


keyword_list = []

for keyword in ["匹配算法", "匹配关键词", "信息抽取"]:
    if keyword in "DFA 算法匹配关键词":
        keyword_list.append(keyword)   

使用for循环需要遍历整个关键词列表。随着关键词库的不断扩充,遍历所需的时间成本也会随之增加,导致性能下降。

相比之下,DFA算法在遇到“匹”字时,只会沿着特定的路径走向后续序列(如“匹配关键词”),而不会错误地走向“匹配算法”的路径。因此,DFA的遍历次数远少于for循环。具体的代码实现将在后文详述。

【问】:那么,如何构建上述状态转换图的结构呢?

【答】:在Python中,我们可以利用dict(字典)数据结构来实现这一结构。


state_event_dict = {
    "匹": {
        "配": {
            "算": {
                "法": {
                    "is_end": True
                },
                "is_end": False
            },
            "关": {
                "键": {
                    "词": {
                        "is_end": True
                    },
                    "is_end": False
                },
                "is_end": False
            },
            "is_end": False
        },
        "is_end": False
    },
    "信": {
        "息": {
            "抽": {
                "取": {
                    "is_end": True
                },
                "is_end": False
            },
            "is_end": False
        },
        "is_end": False
    }
}

具体而言,使用嵌套字典来模拟树形结构,其中字典的key代表事件(即字符)。通过检查is_end字段,可以判断当前状态是否为结束状态。如果是最后一个状态,则停止状态转换,并提取匹配的关键词。

【问】:如果关键词之间存在包含关系,例如“匹配关键词”和“匹配”,该如何处理?

【答】:我们依然可以使用is_end字段来标记关键词的结尾。同时,为了支持继续匹配,可以添加一个新的字段,例如is_continue,以表明当前状态后仍可继续匹配。此外,也可以通过检查除is_end字段外是否还存在其他子节点来判断是否继续匹配。例如在下面的代码中,对于“配”这个状态,除了is_end字段外还包含“关”节点,因此匹配过程需要继续进行。


state_event_dict = {
    "匹": {
        "配": {
            "关": {
                "键": {
                    "词": {
                        "is_end": True
                    },
                    "is_end": False
                },
                "is_end": False
            },
            "is_end": True
        },
        "is_end": False
    }
}

接下来,我们将具体实现这一算法。

三、算法实现

本文使用Python 3.6版本进行实现,当然其他Python 3.X版本也能正常运行。

3.1、构建存储结构


def _generate_state_event_dict(keyword_list: list) -> dict:
    state_event_dict = {}

    # 遍历每一个关键词
    for keyword in keyword_list:
        current_dict = state_event_dict
        length = len(keyword)

        for index, char in enumerate(keyword):
            if char not in current_dict:
                next_dict = {"is_end": False}
                current_dict[char] = next_dict
                current_dict = next_dict
            else:
                next_dict = current_dict[char]
                current_dict = next_dict

            if index == length - 1:
                current_dict["is_end"] = True

    return state_event_dict

上述代码仍有不少可迭代优化的空间。例如,可以先对关键词列表按字典序进行排序,这样具有相同前缀的关键词会集中在一起,从而在构建存储结构时减少遍历次数。

3.2、匹配关键词


def match(state_event_dict: dict, content: str):
    match_list = []
    state_list = []
    temp_match_list = []

    for char_pos, char in enumerate(content):
        # 首先找到匹配项的起点
        if char in state_event_dict:
            state_list.append(state_event_dict)
            temp_match_list.append({
                "start": char_pos,
                "match": ""
            })

        # 可能会同时满足多个匹配项,因此遍历这些匹配项
        for index, state in enumerate(state_list):
            if char in state:
                state_list[index] = state[char]
                temp_match_list[index]["match"] += char

                # 如果抵达匹配项的结尾,表明匹配关键词完成
                if state[char]["is_end"]:
                    match_list.append(copy.deepcopy(temp_match_list[index]))

                    # 如果还能继续,则继续进行匹配
                    if len(state[char].keys()) == 1:
                        state_list.pop(index)
                        temp_match_list.pop(index)
            # 如果不满足匹配项的要求,则将其移除
            else:
                state_list.pop(index)
                temp_match_list.pop(index)

    return match_list

3.3、完整代码


import re
import copy


class DFA:

    def __init__(self, keyword_list: list):
        self.state_event_dict = self._generate_state_event_dict(keyword_list)

    def match(self, content: str):
        match_list = []
        state_list = []
        temp_match_list = []

        for char_pos, char in enumerate(content):
            if char in self.state_event_dict:
                state_list.append(self.state_event_dict)
                temp_match_list.append({
                    "start": char_pos,
                    "match": ""
                })

            for index, state in enumerate(state_list):
                if char in state:
                    state_list[index] = state[char]
                    temp_match_list[index]["match"] += char

                    if state[char]["is_end"]:
                        match_list.append(copy.deepcopy(temp_match_list[index]))

                        if len(state[char].keys()) == 1:
                            state_list.pop(index)
                            temp_match_list.pop(index)
                else:
                    state_list.pop(index)
                    temp_match_list.pop(index)

        return match_list

    @staticmethod
    def _generate_state_event_dict(keyword_list: list) -> dict:
        state_event_dict = {}

        for keyword in keyword_list:
            current_dict = state_event_dict
            length = len(keyword)

            for index, char in enumerate(keyword):
                if char not in current_dict:
                    next_dict = {"is_end": False}
                    current_dict[char] = next_dict
                    current_dict = next_dict
                else:
                    next_dict = current_dict[char]
                    current_dict = next_dict

                if index == length - 1:
                    current_dict["is_end"] = True

        return state_event_dict


if __name__ == "__main__":
    dfa = DFA(["匹配关键词", "匹配算法", "信息抽取", "匹配"])
    print(dfa.match("信息抽取之 DFA 算法匹配关键词,匹配算法"))

运行输出结果如下:

[

    {

'start': 0,

'match': '信息抽取'

    }, {

'start': 12,

'match': '匹配'

    }, {

'start': 12,

'match': '匹配关键词'

    }, {

'start': 18,

'match': '匹配'

    }, {

'start': 18,

'match': '匹配算法'

    }

]

四、其他用法

4.1、添加通配符

在敏感词识别场景中,常遇到同一类型的句式,例如“你这个傻X”,其中X可以是多种字符。如果逐一添加到关键词表中显然不现实。更好的做法是引入类似正则表达式的通配符机制。一个简单的实现是使用“*”来匹配任意内容。

添加通配符只需修改匹配关键词的过程:


def match(self, content: str):
    match_list = []
    state_list = []
    temp_match_list = []

    for char_pos, char in enumerate(content):
        if char in self.state_event_dict:
            state_list.append(self.state_event_dict)
            temp_match_list.append({
                "start": char_pos,
                "match": ""
            })

        for index, state in enumerate(state_list):
            is_find = False
            state_char = None

            # 如果是 * 则匹配所有内容
            if "*" in state:
                state_list[index] = state["*"]
                state_char = state["*"]
                is_find = True

            if char in state:
                state_list[index] = state[char]
                state_char = state[char]
                is_find = True

            if is_find:
                temp_match_list[index]["match"] += char

                if state_char["is_end"]:
                    match_list.append(copy.deepcopy(temp_match_list[index]))

                    if len(state_char.keys()) == 1:
                        state_list.pop(index)
                        temp_match_list.pop(index)
            else:
                state_list.pop(index)
                temp_match_list.pop(index)

    return match_list

main()函数调用示例。


if __name__ == "__main__":
    dfa = DFA(["匹配关键词", "匹配算法", "信息*取", "匹配"])
    print(dfa.match("信息抽取之 DFA 算法匹配关键词,匹配算法,信息抓取"))

运行输出结果如下:

[

    {

'start': 0,

'match': '信息抽取'

    }, {

'start': 12,

'match': '匹配'

    }, {

'start': 12,

'match': '匹配关键词'

    }, {

'start': 18,

'match': '匹配'

    }, {

'start': 18,

'match': '匹配算法'

    }, {

'start': 23,

'match': '信息抓取'

    }

]

以上是对Python实现DFA算法的详细解析,更多关于Python DFA算法的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表