|
| 1 | +from sudkampPython.transitions import Machine |
| 2 | + |
| 3 | +""" |
| 4 | +Algorithm 5.7.2 |
| 5 | +Determination of Equivalent States of DFA |
| 6 | +
|
| 7 | +input: DFA M = (Q, Alphabet, TransitionFunctoin, q<sub>0</sub>, F) |
| 8 | +
|
| 9 | +1. (Initialization) |
| 10 | + for every pair of states q<sub>i</sub> and q<sub>j</sub>, i < j, do |
| 11 | + 1.1 D[i,j] := 0 |
| 12 | + 1.2 S[i,j] := null set |
| 13 | + end for |
| 14 | +2. for every pair i, j, i < j, if one of q<sub>i</sub> or q<sub>j</sub> is |
| 15 | + and accepting state and the other is not an accepting state, then set D[i,j] := 1 |
| 16 | +3. for every pair i, j, i < j, with D[i,j] = 0, do |
| 17 | + 3.1 if there exists an a which is element of Alphabet such that delta(q<sub>i</sub>, a) = q<sub>m</sub>, |
| 18 | + delta(q<sub>j</sub>, a) = q<sub>n</sub> and D[m,n] = 1 or D[n,m] = 1, then DIST(i,j) |
| 19 | +
|
| 20 | + 3.2 else for each a that is element of Alphabet, do: Let delta(q<sub>i</sub>, a) = q<sub>m</sub>, |
| 21 | + delta(q<sub>j</sub>m, a) = q<sub>n</sub> |
| 22 | + if m < n and [i,j] != [m,n], then add [i,j] to S[m,n] |
| 23 | + else if m > n and [i,j] != [n,m], then add [i,j] to S[n,m] |
| 24 | + end for |
| 25 | +
|
| 26 | +DIST(i,j); |
| 27 | +begin |
| 28 | + D[i,j] := 1 |
| 29 | + for all [m,n] that are elements of S[i,j], DIST(m,n) |
| 30 | +end |
| 31 | +""" |
| 32 | +def determineEquivelantStatesOfDFA( dfa ): # TODO: Improve algorithmic efficiency |
| 33 | + states = list(dfa.states.items()) |
| 34 | + equivStates = set() |
| 35 | + D, S = [], [] |
| 36 | + # initialization |
| 37 | + for i in range(len(states)): |
| 38 | + D.append([]) |
| 39 | + S.append([]) |
| 40 | + for j in range(len(states)): |
| 41 | + D[-1].append(False) if i < j else D[-1].append(None) |
| 42 | + S[-1].append(set()) if i < j else S[-1].append(None) |
| 43 | + # Step 2 |
| 44 | + for i in range(len(states)): |
| 45 | + for j in range(len(states)): |
| 46 | + if (i < j and any(s.is_final() for s in states[i:j+1]) |
| 47 | + and not all(s.is_final() for s in states[i:j+1])): |
| 48 | + # one is final and other isn't |
| 49 | + D[i][j] = 1 |
| 50 | + # Step 3 |
| 51 | + for i in range(len(states)): |
| 52 | + for j in range(len(states)): |
| 53 | + if i < j and D[i][j] == 0: # Do |
| 54 | + # if there is an "a" for which both qi and qj have a transition and the |
| 55 | + # resulting state m,n are not equal (m!=n) and D[m][n] or D[n][m] == 1 then |
| 56 | + # DIST(i,j) |
| 57 | + # 3.1 |
| 58 | + for symbol in dfa.alphabet.keys(): |
| 59 | + qi_transition = getTransitionWithSymbol( symbol, states[i].name ) |
| 60 | + qj_transition = getTransitionWithSymbol( symbol, states[j].name ) |
| 61 | + m_state_name, n_state_name = qi_transition.dest, qj_transition.dest |
| 62 | + # get indexes |
| 63 | + m = [s.name for s in states].index(m_state_name) |
| 64 | + n = [s.name for s in states].index(n_state_name) |
| 65 | + if D[m][n] == 1 or D[n][m] == 1: # if n = m, D[n,m] will never equal 1 |
| 66 | + DIST(i, j, D, S) |
| 67 | + # 3.2 |
| 68 | + for symbol in dfa.alphabet.key(): |
| 69 | + qi_transition = getTransitionWithSymbol( symbol, states[i].name ) |
| 70 | + qj_transition = getTransitionWithSymbol( symbol, states[j].name ) |
| 71 | + m_state_name, n_state_name = qi_transition.dest, qj_transition.dest |
| 72 | + # get indexes |
| 73 | + m = [s.name for s in states].index(m_state_name) |
| 74 | + n = [s.name for s in states].index(n_state_name) |
| 75 | + if m < n and (i,j) != (m,n): |
| 76 | + S[m][n].add((i,j)) |
| 77 | + elif m > n and (i,j) != (n,m): |
| 78 | + S[n][m].add((i,j)) |
| 79 | + |
| 80 | + # Now determine the set of equiv states. If D[i][j] = 0 then i and j are equiv |
| 81 | + for i in range(len(states)): |
| 82 | + for j in range(len(states)): |
| 83 | + if D[i][j] == 0: |
| 84 | + equivStates.add(states[i]) |
| 85 | + equivStates.add(states[j]) |
| 86 | + return equivStates |
| 87 | + |
| 88 | +def DIST(i, j, D, S): |
| 89 | + """ Helper function for our main algorithm """ |
| 90 | + D[i][j] = 1 |
| 91 | + for m,n in S[i][j]: |
| 92 | + DIST(m, n, D, S) |
| 93 | + |
| 94 | +def getTransitionWithSymbol( dfa, symbol, source ): |
| 95 | + """ |
| 96 | + Find a transition, if one exists, that involves the named |
| 97 | + symbol and source |
| 98 | + """ |
| 99 | + for event in dfa.events.keys(): |
| 100 | + for transition in event.transitions.values(): |
| 101 | + if event == symbol and transition.source == source: |
| 102 | + return transition |
| 103 | + |
| 104 | +""" |
| 105 | +input: an NFA-null M = (Q, Alphabet, TransitionFunction, q<sub>0</sub>,F) |
| 106 | + input transition function t of M |
| 107 | +
|
| 108 | +1. initialize Q' to null-closure(q<sub>0</sub>) |
| 109 | +2. repeat |
| 110 | + 2.1 if there is a node X, X is element of Q' and a symbol a that's element of Alphabet |
| 111 | + with no arc leaving X labeled "a", then |
| 112 | + 2.1.1 let Y = UNION over q<sub>i</sub>, where q<sub>i</sub> is element of X of |
| 113 | + t(q<sub>i</sub>,a) |
| 114 | + 2.1.2 if Y /= Q', then set Q' := Q UNION {Y} |
| 115 | + 2.1.3 add an arc from X to Y labeled a |
| 116 | + else done:= true |
| 117 | + until done |
| 118 | +3. the set of accepting states of DM is F' = {X, where X is element of Q' | X contains |
| 119 | + an element q<sub>i</sub> that is element of F} |
| 120 | +""" |
| 121 | +def constructEquivelantDFAFromNFA( nfa ): |
| 122 | + dfa = Machine( model=None, states=nfa.initial.name, initial=nfa.initial.name, transitions=None) |
| 123 | + Q_ = nullClosure(nfa, nfa.initial) # state q0 |
| 124 | + while True: |
| 125 | + for X in Q_: |
| 126 | + validSymbols = [sym for sym in nfa.alphabet if not nfa.events[sym].transitions[X.name]] |
| 127 | + if validSymbols: |
| 128 | + a = validSymbols[0] |
| 129 | + destinations = [t.dest for qi in X for t in nfa.events[a].transitions[qi.name]] |
| 130 | + Y = set([nfa.states[x] for x in destinations]) |
| 131 | + if Y != Q_: |
| 132 | + Q_ |= Y |
| 133 | + dfa.add_transition(a, str(X), str(Y)) |
| 134 | + else: # done = true |
| 135 | + break |
| 136 | + # the set of final states in F' |
| 137 | + F_ = [X for X in Q_ if any(q in X for q in nfa.final_states)] |
| 138 | + |
| 139 | + |
| 140 | +def nullClosure( machine, state ): |
| 141 | + """ Calculate the set of states that can be reached without processing a symbol """ |
| 142 | + closureSet = set() |
| 143 | + closureSet.add(state) |
| 144 | + # TODO: If "*" trigger doesn't exist then we can just return the set with just the named state? |
| 145 | + while True: # do |
| 146 | + prev = set(closureSet) # update previous states set |
| 147 | + # add all states that can be reached by null |
| 148 | + for state in closureSet: |
| 149 | + reachableWithNull = [t.dest for t in machine.events['*'].transitions[state.name]] |
| 150 | + closureSet |= set(reachableWithNull) |
| 151 | + if prev == closureSet: # has closureSet changed? |
| 152 | + break |
| 153 | + return closureSet |
0 commit comments