|
| 1 | +""" |
| 2 | +ε-Closure Computation |
| 3 | +
|
| 4 | +Implements ε-closure computation for ε-NFA support. |
| 5 | +The ε-closure of a state is the set of states reachable from that state |
| 6 | +using only ε-transitions (including the state itself). |
| 7 | +""" |
| 8 | + |
| 9 | +from typing import Set, Dict, List |
| 10 | +from ..schemas import FSA, Transition |
| 11 | + |
| 12 | + |
| 13 | +def epsilon_closure(state: str, epsilon_transitions: Dict[str, Set[str]]) -> Set[str]: |
| 14 | + """ |
| 15 | + Compute the ε-closure of a single state. |
| 16 | + |
| 17 | + The ε-closure of a state q is the set of all states reachable from q |
| 18 | + using only ε-transitions, including q itself. |
| 19 | + |
| 20 | + Algorithm: |
| 21 | + 1. Initialize closure with the state itself |
| 22 | + 2. Use DFS/BFS to follow all ε-transitions |
| 23 | + 3. Return the complete set of reachable states |
| 24 | + |
| 25 | + Args: |
| 26 | + state: The state to compute ε-closure for |
| 27 | + epsilon_transitions: Dict mapping state -> set of states reachable via ε |
| 28 | + |
| 29 | + Returns: |
| 30 | + Set of states in the ε-closure |
| 31 | + |
| 32 | + Example: |
| 33 | + >>> eps_trans = {"q0": {"q1", "q2"}, "q1": {"q3"}} |
| 34 | + >>> epsilon_closure("q0", eps_trans) |
| 35 | + {"q0", "q1", "q2", "q3"} |
| 36 | + """ |
| 37 | + closure = {state} |
| 38 | + stack = [state] |
| 39 | + |
| 40 | + while stack: |
| 41 | + current = stack.pop() |
| 42 | + |
| 43 | + # Get all states reachable via ε from current |
| 44 | + if current in epsilon_transitions: |
| 45 | + for next_state in epsilon_transitions[current]: |
| 46 | + if next_state not in closure: |
| 47 | + closure.add(next_state) |
| 48 | + stack.append(next_state) |
| 49 | + |
| 50 | + return closure |
| 51 | + |
| 52 | + |
| 53 | +def epsilon_closure_set(states: Set[str], epsilon_transitions: Dict[str, Set[str]]) -> Set[str]: |
| 54 | + """ |
| 55 | + Compute the ε-closure of a set of states. |
| 56 | + |
| 57 | + This is the union of ε-closures of all states in the set. |
| 58 | + |
| 59 | + Args: |
| 60 | + states: Set of states to compute ε-closure for |
| 61 | + epsilon_transitions: Dict mapping state -> set of states reachable via ε |
| 62 | + |
| 63 | + Returns: |
| 64 | + Set of all states in the combined ε-closure |
| 65 | + |
| 66 | + Example: |
| 67 | + >>> eps_trans = {"q0": {"q1"}, "q2": {"q3"}} |
| 68 | + >>> epsilon_closure_set({"q0", "q2"}, eps_trans) |
| 69 | + {"q0", "q1", "q2", "q3"} |
| 70 | + """ |
| 71 | + closure = set() |
| 72 | + |
| 73 | + for state in states: |
| 74 | + closure |= epsilon_closure(state, epsilon_transitions) |
| 75 | + |
| 76 | + return closure |
| 77 | + |
| 78 | + |
| 79 | +def build_epsilon_transition_map(transitions: List[Transition]) -> Dict[str, Set[str]]: |
| 80 | + """ |
| 81 | + Build a mapping of ε-transitions from a list of transitions. |
| 82 | + |
| 83 | + Args: |
| 84 | + transitions: List of all transitions in the FSA |
| 85 | + |
| 86 | + Returns: |
| 87 | + Dict mapping each state to the set of states reachable via ε-transitions |
| 88 | + |
| 89 | + Example: |
| 90 | + >>> transitions = [ |
| 91 | + ... Transition(from_state="q0", to_state="q1", symbol="ε"), |
| 92 | + ... Transition(from_state="q0", to_state="q2", symbol="ε"), |
| 93 | + ... Transition(from_state="q1", to_state="q3", symbol="a") |
| 94 | + ... ] |
| 95 | + >>> build_epsilon_transition_map(transitions) |
| 96 | + {"q0": {"q1", "q2"}} |
| 97 | + """ |
| 98 | + epsilon_map: Dict[str, Set[str]] = {} |
| 99 | + |
| 100 | + for trans in transitions: |
| 101 | + # Check for ε-transitions (various representations) |
| 102 | + if trans.symbol in ("ε", "epsilon", ""): |
| 103 | + if trans.from_state not in epsilon_map: |
| 104 | + epsilon_map[trans.from_state] = set() |
| 105 | + epsilon_map[trans.from_state].add(trans.to_state) |
| 106 | + |
| 107 | + return epsilon_map |
| 108 | + |
| 109 | + |
| 110 | +def compute_all_epsilon_closures(fsa: FSA) -> Dict[str, Set[str]]: |
| 111 | + """ |
| 112 | + Compute ε-closures for all states in an FSA. |
| 113 | + |
| 114 | + This is useful for preprocessing before NFA to DFA conversion. |
| 115 | + |
| 116 | + Args: |
| 117 | + fsa: The finite state automaton |
| 118 | + |
| 119 | + Returns: |
| 120 | + Dict mapping each state to its ε-closure |
| 121 | + |
| 122 | + Example: |
| 123 | + >>> fsa = FSA( |
| 124 | + ... states=["q0", "q1", "q2"], |
| 125 | + ... alphabet=["a"], |
| 126 | + ... transitions=[ |
| 127 | + ... Transition(from_state="q0", to_state="q1", symbol="ε"), |
| 128 | + ... Transition(from_state="q1", to_state="q2", symbol="a") |
| 129 | + ... ], |
| 130 | + ... initial_state="q0", |
| 131 | + ... accept_states=["q2"] |
| 132 | + ... ) |
| 133 | + >>> compute_all_epsilon_closures(fsa) |
| 134 | + {"q0": {"q0", "q1"}, "q1": {"q1"}, "q2": {"q2"}} |
| 135 | + """ |
| 136 | + epsilon_trans = build_epsilon_transition_map(fsa.transitions) |
| 137 | + closures = {} |
| 138 | + |
| 139 | + for state in fsa.states: |
| 140 | + closures[state] = epsilon_closure(state, epsilon_trans) |
| 141 | + |
| 142 | + return closures |
0 commit comments