📌  相关文章
📜  正则表达式到有限自动机的转换(1)

📅  最后修改于: 2023-12-03 15:40:38.499000             🧑  作者: Mango

正则表达式到有限自动机的转换

在计算机科学中,正则表达式(regular expression)是一种用于描述特定模式的表达式。正则表达式通常用于字符串匹配、文本搜索和替换等操作。而有限自动机(finite automaton)是一种数学模型,它能够模拟一个特定的有限状态机(finite state machine)。

在计算机科学中,正则表达式经常用于描述一组模式,而有限自动机常用于实现这些模式。因此,将正则表达式转换为有限自动机是一项重要的任务。

正则表达式

正则表达式是由普通字符和元字符(metacharacters)组成的字符串,它们代表了特殊的含义。正则表达式的基本元素包括:

  • 字符集:用方括号([])表示。字符集用于匹配一个字符,字符集内部可以包含多个字符。例如,[abcd]表示匹配字符a、b、c或d。

  • 特殊字符:包括转义字符、点号(.)和星号(*)等。这些字符有特殊的含义,可以匹配多个字符。

  • 量词:用于表示一个模式的重复次数。常用的量词有问号(?)、星号(*)和加号(+)。

有限自动机

有限自动机是一种形式化模型,用于描述有限状态机。有限自动机包含一个有限的状态集合、一个输入字符集和一个状态转换函数。当有限自动机接收到一个输入字符时,它会根据当前状态和输入字符进行转换,然后进入新的状态。

在有限自动机中,每个状态都有一个开始状态和一个终止状态。当有限自动机到达终止状态时,它会停止对输入字符的处理,并判断输入字符串是否符合模式。

正则表达式到有限自动机的转换

将正则表达式转换为有限自动机的过程涉及多个步骤,包括正则表达式解析、NFA(非确定有限自动机)构造、DFA(确定有限自动机)构造和最小化等。

以下是将正则表达式转换为NFA的简单算法:

  1. 对正则表达式进行解析,将其转换为语法树。

  2. 从根节点出发,递归遍历整个语法树,并将每个节点的类型和子节点转换为NFA。

  3. 如果节点是连接符(即叶子节点不是或节点),则将其左右子节点的NFA进行连接操作,生成一个新的NFA。

  4. 如果节点是或符号(|),则将其左右子节点的NFA进行或操作,生成一个新的NFA。

  5. 如果节点是星号(*),则对其子节点的NFA进行克林闭包(Kleene Closure)操作,生成一个新的NFA。

  6. 如果节点是加号(+),则对其子节点的NFA进行正闭包(Positive Closure)操作,生成一个新的NFA。

  7. 如果节点是问号(?),则对其子节点的NFA进行可选操作,生成一个新的NFA。

  8. 最终,整个语法树最终生成的NFA就是正则表达式对应的NFA。

以下是将NFA转换为DFA的简单算法:

  1. 使用NFA的起始状态创建一个新的DFA状态。

  2. 对于每个DFA状态和每个输入字符,计算其对应的NFA状态集合。

  3. 如果NFA状态集合对应的DFA状态不存在,则创建一个新的DFA状态,用该集合进行初始化。

  4. 在DFA状态中,按输入字符和对应的NFA状态计算下一个状态。

  5. 对于每个新的DFA状态,对其包含的NFA状态集合进行转换,计算对应的新的DFA状态。

  6. 最终得到的DFA包含一个起始状态和多个结束状态,其中每个状态对应一个NFA状态集合。

以下是最小化DFA的算法:

  1. 对于每个状态对,计算其可达性及等价性。

  2. 设置初始分组为非结束状态和结束状态,然后对于每个分组中的状态对,计算其是否是等价状态。

  3. 如果两个状态是等价状态,则将它们分到相同的分组中。

  4. 如果分组不再改变,则停止迭代。

  5. 最终得到的分组包含了所有的等价状态,每个分组对应一个最小化的DFA状态。

代码片段

NFA的Python实现

以下是使用Python实现将正则表达式转换为NFA的代码片段:

class NFA:
    def __init__(self, symbol=None, edges=None, accept=False):
        self.symbol = symbol
        self.edges = edges or []
        self.accept = accept

    def set_accept(self):
        self.accept = True

    def add_edge(self, symbol, to):
        self.edges.append((symbol, to))

    def epsilon_closure(self):
        states = set()

        def traverse(state):
            if state in states:
                return
            states.add(state)
            for symbol, to in self.edges:
                if symbol is None and to == state:
                    traverse(to)

        traverse(self)
        return states

    @staticmethod
    def concatenate(first, second):
        for state in first.accept_states():
            state.add_edge(None, second)
        first.clear_accept_states()
        return first

    def accept_states(self):
        return [state for state in self if state.accept]

    def clear_accept_states(self):
        for state in self:
            state.accept = False

    def __iter__(self):
        todo = [self]
        done = set()
        while todo:
            state = todo.pop()
            if state in done:
                continue
            done.add(state)
            yield state
            for symbol, to in state.edges:
                if to not in done and to not in todo:
                    todo.append(to)

    @staticmethod
    def compile(pattern):
        stack = []
        for symbol in pattern:
            if symbol == "|":
                y, x = stack.pop(), stack.pop()
                state = NFA(None, [(None, x), (None, y)], accept=False)
                stack.append(state)
            elif symbol == ".":
                y, x = stack.pop(), stack.pop()
                x = NFA.concatenate(x, y)
                stack.append(x)
            elif symbol == "*":
                x = stack.pop()
                state = NFA(None, [(None, x)], accept=False)
                x.add_edge(None, state)
                state.add_edge(None, x)
                state.set_accept()
                stack.append(state)
            elif symbol == "+":
                x = stack.pop()
                state = NFA(None, [(None, x)], accept=False)
                x.add_edge(None, state)
                state.set_accept()
                stack.append(x)
            elif symbol == "?":
                x = stack.pop()
                state = NFA(None, [(None, x)], accept=True)
                stack.append(state)
            else:
                state = NFA(symbol, accept=False)
                state.add_edge(symbol, NFA(accept=True))
                stack.append(state)
        nfa = NFA.concatenate(*stack)
        return nfa

DFA的Python实现

以下是使用Python实现将NFA转换为DFA的代码片段:

class DFA:
    def __init__(self, start_state, accepted_states):
        self.start_state = start_state
        self.accepted_states = set(accepted_states)
        self.transitions = {}
        self.current_state = start_state

    def add_transition(self, from_state, symbol, to_state):
        self.transitions.setdefault((from_state, symbol), set()).add(to_state)

    def set_start_state(self, state):
        self.current_state = self.start_state = state

    def feed(self, symbol):
        to_states = self.transitions.get((self.current_state, symbol))
        if not to_states:
            raise Exception("Invalid input: " + symbol)
        self.current_state = next(iter(to_states))

    def is_accepted(self):
        return self.current_state in self.accepted_states

    @staticmethod
    def compile(nfa):
        start_state = frozenset(nfa.epsilon_closure())
        transitions = {}
        accepted_states = set()

        def traverse(state):
            state_id = frozenset(state)
            if state_id in transitions:
                return
            transitions[state_id] = {}
            for symbol in range(256):
                to_states = set()
                for from_state in state:
                    for symbol2, to_state in from_state.edges:
                        if symbol == symbol2:
                            to_states.add(to_state)
                if to_states:
                    to_state = frozenset.union(*(to.epsilon_closure() for to in to_states))
                    transitions[state_id][symbol] = to_state
                    if any(to.accept for to in to_states):
                        accepted_states.add(to_state)
                    traverse(to_state)

        traverse(start_state)
        dfa = DFA(start_state, accepted_states)
        for from_state_id, transitions in transitions.items():
            for symbol, to_state_id in transitions.items():
                dfa.add_transition(from_state_id, symbol, to_state_id)
        return dfa

最小化DFA的Python实现

以下是使用Python实现将DFA最小化的代码片段:

class MinDFA:
    def __init__(self, dfa):
        self.nfa = dfa
        self.groups = [dfa.accepted_states, set(dfa) - dfa.accepted_states]
        self.group_states = [set(), set()]
        for state in dfa:
            self.group_states[dfa.is_accepted()].add(state)
        self.transitions = {}
        self.accepted_states = set()

    def create_group(self, states, second_group=False):
        new_group = set()
        for state in states:
            if state in self.group_states[second_group]:
                new_group.add(state)
                self.group_states[second_group].remove(state)
                assert state not in self.group_states[second_group]
        self.groups.append(new_group)
        self.group_states.append(new_group)
        return len(self.groups) - 1

    def minimize(self):
        todo = [(0, 1)]
        done = set()
        new_transitions = {}

        def find_group(state):
            idx = bisect_left([frozenset(g) for g in self.groups], state)
            return idx if idx < len(self.groups) and state == self.groups[idx] else None

        while todo:
            idx1, idx2 = todo.pop()
            if idx2 < idx1:
                idx1, idx2 = idx2, idx1
            if (idx1, idx2) in done:
                continue
            done.add((idx1, idx2))

            for symbol in range(256):
                transitions1 = {state: to_state for (state, to_state) in self.nfa.transitions.items()
                                if state[0] in self.groups[idx1] and state[1] == symbol and to_state in self.groups[
                                    idx2]}
                transitions2 = {state: to_state for (state, to_state) in self.nfa.transitions.items()
                                if state[0] in self.groups[idx2] and state[1] == symbol and to_state in self.groups[
                                    idx1]}

                if not transitions1 and not transitions2:
                    continue

                g1, g2 = None, None
                if transitions1:
                    g1 = find_group(transitions1.keys())
                    if g1 is None:
                        g1 = self.create_group(transitions1.keys())
                        todo.append((idx1, g1))
                        todo.append((g1, idx2))

                if transitions2:
                    g2 = find_group(transitions2.keys())
                    if g2 is None:
                        g2 = self.create_group(transitions2.keys())
                        todo.append((idx1, g2))
                        todo.append((g2, idx2))

                if not transitions1 or not transitions2:
                    new_transitions[(idx1, idx2, symbol)] = (g1 or idx1, g2 or idx2)
                elif g1 == g2:
                    new_transitions[(idx1, idx2, symbol)] = (g1, g2)
                elif g1 is None or g2 is None:
                    new_transitions[(idx1, idx2, symbol)] = (g1 or g2, g1 or g2)
                else:
                    new_transitions[(idx1, idx2, symbol)] = (g1, g2)

        for i in range(len(self.groups)):
            if any(state.accept for state in self.groups[i]):
                self.accepted_states.add(i)

        self.transitions = {}
        for (group1, group2, symbol), (to_group1, to_group2) in new_transitions.items():
            self.transitions.setdefault((to_group1, to_group2), {})[symbol] = (group1, group2)

    def simulate(self, s):
        s = s.encode('ascii')
        current_state = 0
        for c in s:
            current_state, _ = self.transitions.get((current_state, None), {}).get(c, (None, None))
            if current_state is None:
                return False
        return current_state in self.accepted_states