def test_set_new_transtable(): alphabet = ["a", "b"] states = ["1", "2"] table = TransitionTable(alphabet, states) table.add_transition("a", "2", "2") table.add_transition("b", "2", "2") table.add_transition("a", "1", "2") table.add_transition("b", "1", "1") dfa = DFA(table) # invalid table try: dfa.trans_table = {} except ValueError: pass # new table new_table = TransitionTable(alphabet, states) new_table.add_transition("b", "2", "2") new_table.add_transition("a", "1", "2") new_table.add_transition("b", "1", "1") dfa.trans_table = new_table
def test_consume(): alphabet = ["a", "b"] states = ["1", "2"] table = TransitionTable(alphabet, states) table.add_transition("b", "2", "2") table.add_transition("a", "1", "2") table.add_transition("b", "1", "1") dfa = DFA(table) # accepted string accepted = dfa.consume("ba") assert accepted, "'ba' is expected to be accepted." accepted = dfa.consume("babbb") assert accepted, "'babbb' is expected to be accepted." # not acceptable string accepted = dfa.consume("b") assert not accepted, "'b' is expected not to be accepted." accepted = dfa.consume("bbb") assert not accepted, "'bbb' is expected not to be accepted." # dead state accepted = dfa.consume("bbaba") assert not accepted, "'bbaba' is expected not to be accepted." # invalid transition accepted = dfa.consume("babbbc") assert not accepted, "'bac' is expected not to be accepted."
def nfa_to_dfa(self): try: self.alphabet.remove('ε') except: pass self.new_ss = state({s.id for s in self.start_states}) temp_op = {key: set() for key in self.alphabet + ['ε']} for ss in self.start_states: for k, v in ss.outpaths.items(): set_v = set(v) temp_op[k] = temp_op[k].union(set_v) self.new_ss.outpaths = {k: list(v) for k, v in temp_op.items()} self.new_ss.made_of = set(self.start_states) self.states.append(self.new_ss) self.e_transitions() if 'ε' in self.new_ss.outpaths: del self.new_ss.outpaths['ε'] existing_states = [self.new_ss.made_of] new_states = [self.new_ss] dfa_states = [self.new_ss] while len(new_states) > 0: next_round = [] for es in new_states: new_dict = {key: set() for key in self.alphabet} for sub_state in es.made_of: for char, states in sub_state.ep_outpaths.items(): new_dict[char] = new_dict[char].union(states) for char, states in new_dict.items(): if states not in existing_states: id = set([s.id for s in states]) if id == set(): id = '∅' new_state = state(id) new_state.made_of = states es.outpaths[char] = new_state dfa_states.append(new_state) existing_states.append(states) next_round.append(new_state) else: for x in dfa_states: if x.made_of == states: es.outpaths[char] = x break new_states = next_round self.my_dfa = DFA(dfa_states) self.my_dfa.alphabet = self.alphabet self.my_dfa.start_state = self.new_ss acc_ids = {st.id for st in self.accept_states} for st in self.my_dfa.states: for s in st.made_of: if s.id in acc_ids: self.my_dfa.accept_states.append(st) break
def test_dfa_match_bad(self): tr = {(0, 15): 1, (0, 22): 2, (1, 17): 3, (2, 17): 3, (2, 22): 4, (3, 22): 4, (4, 233): 4 } fs = {1: {'regex1'}, 4: {'regex2'} } dfa = DFA(5, tr, 0, fs) seq = [2] self.assertEqual(dfa.match(seq), set(), "Matching the empty sequence should stay in the start state which is not final in this example") tr = {(0, 15): 1, (0, 22): 2, (1, 17): 3, (2, 17): 3, (2, 22): 4, (3, 22): 4, (4, 233): 4 } fs = {1: {'regex1'}, 4: {'regex2'} } dfa = DFA(5, tr, 0, fs) seq = [22, 22, 233, 2] self.assertEqual(dfa.match(seq), set(), "If a transition is undefined, it must return set()")
def test_dfa_match_good1(self): tr = {(0, 15): 1, (0, 22): 2, (1, 17): 3, (2, 17): 3, (2, 22): 4, (3, 22): 4, (4, 233): 4 } fs = {1: {'regex1'}, 4: {'regex2'} } dfa = DFA(5, tr, 0, fs) seq = [15] self.assertEqual(dfa.match(seq), {'regex1'}, "In this example, the sequence [15] leads into final state 1 that should return regex1 as label") tr = {(0, 15): 1, (0, 22): 2, (1, 17): 3, (2, 17): 3, (2, 22): 4, (3, 22): 4, (4, 233): 4 } fs = {1: {'regex1'}, 4: {'regex2'} } dfa = DFA(5, tr, 0, fs) seq = [15, 17, 22, 233] self.assertEqual(dfa.match(seq), {'regex2'}, "In this example, the sequence [15, 17, 22, 233] leads into final state 4 that should return regex2 as label")
def setup(self): # DFA which matches all binary strings ending in an odd number of '1's self.dfa = DFA(**{ 'states': {'q0', 'q1', 'q2'}, 'symbols': {'0', '1'}, 'transitions': { 'q0': {'0': 'q0', '1': 'q1'}, 'q1': {'0': 'q0', '1': 'q2'}, 'q2': {'0': 'q2', '1': 'q1'} }, 'initial_state': 'q0', 'final_states': {'q1'} })
def test2(): #Example 13.1 from Automata & Computability print('Test 2') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s0.outpaths = {'a': s1, 'b': s3} s1.outpaths = {'a': s2, 'b': s2} s2.outpaths = {'a': s2, 'b': s2} s3.outpaths = {'a': s2, 'b': s2} d = DFA([s0,s1,s2,s3]) d.start_state = s0 d.accept_states = [s1, s3] d.alphabet = ['a', 'b'] d.minimize() d.print_dfa(True) """
def subset_construction(self): # based on: # Subset Construction presented by Ullman (Automata course) # extended with closure operation to work with epsilon transitions states = 1 numeric_state = dict() closure = self.epsilon_closure() start = frozenset(closure[self.start]) numeric_state[start] = states - 1 finals = dict() # empty transitions dictionary tr = dict() queue = collections.deque([start]) # if one of the subset states in the DFA start state is already final fin = start & self.final.keys() if fin: # start state is always id 0 finals[0] = {label for q in fin for label in self.final[q]} while queue: # print(len(queue)) curr_state = queue.popleft() for (a, state_set) in self.successors(curr_state).items(): # for a in range(256): # next_state = self.next(curr_state, a) next_state = frozenset(state_set) if next_state: # the state exists if next_state not in numeric_state.keys(): states += 1 numeric_state[next_state] = states - 1 # add into queue so it gets resolved in next round queue.append(next_state) tr[numeric_state[curr_state], a] = numeric_state[next_state] fin = next_state & self.final.keys() if fin: finals[numeric_state[next_state]] = { label for q in fin for label in self.final[q] } # print("State Subsets", numeric_state) return DFA(states, tr, 0, finals)
def test1(): #from https://www.gatevidyalay.com/minimization-of-dfa-minimize-dfa-example/ print('Test 1') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s4 = state(4) s0.outpaths = {'a': s1, 'b': s2} s1.outpaths = {'a': s1, 'b': s3} s2.outpaths = {'a': s1, 'b': s2} s3.outpaths = {'a': s1, 'b': s4} s4.outpaths = {'a': s1, 'b': s2} d = DFA([s0,s1,s2,s3,s4]) d.start_state = s1 d.accept_states = [s4] d.alphabet = ['a', 'b'] d.minimize() d.print_dfa(True) """
def test1(): #1a in HW3 in Automata & Computability print('Test 1') s0 = state(0) s1 = state(1) s0.outpaths = {'a': s1, 'b': s0} s1.outpaths = {'a': s0, 'b': s1} d = DFA([s0, s1]) d.start_state = s0 d.accept_states = [s0] d.alphabet = ['a', 'b'] print(d.all_regex(False) + '\n')
def test4(): #Example 13.2 from Automata & Computability print('Test 4') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s4 = state(4) s5 = state(5) s0.outpaths = {'a': s1, 'b': s2} s1.outpaths = {'a': s3, 'b': s4} s2.outpaths = {'a': s4, 'b': s3} s3.outpaths = {'a': s5, 'b': s5} s4.outpaths = {'a': s5, 'b': s5} s5.outpaths = {'a': s5, 'b': s5} d = DFA([s0,s1,s2,s3,s4,s5]) d.start_state = s0 d.accept_states = [s1, s2, s5] d.alphabet = ['a', 'b'] d.minimize() d.print_dfa(True) """
def test3(): #from https://www.geeksforgeeks.org/minimization-of-dfa/ print('Test 3') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s4 = state(4) s5 = state(5) s0.outpaths = {'0': s3, '1': s1} s1.outpaths = {'0': s2, '1': s5} s2.outpaths = {'0': s2, '1': s5} s3.outpaths = {'0': s0, '1': s4} s4.outpaths = {'0': s2, '1': s5} s5.outpaths = {'0': s5, '1': s5} d = DFA([s0,s1,s2,s3,s4,s5]) d.start_state = s0 d.accept_states = [s1, s2, s4] d.alphabet = ['0', '1'] d.minimize() d.print_dfa(True) """
def test5(): #accepts any variant of a(ba)*a print('Test 5') s0 = state(0) s1 = state(1) s2 = state(2) s0.outpaths = {'a': s1} s1.outpaths = {'a': s2, 'b': s0} d = DFA([s0, s1, s2]) d.start_state = s0 d.accept_states = [s2] d.alphabet = ['a', 'b'] print(d.all_regex(False))
def test3(): #1d in HW3 in Automata & Computability print('Test 3') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s0.outpaths = {'a': s2, 'b': s1} s1.outpaths = {'a': s3, 'b': s0} s2.outpaths = {'a': s0, 'b': s3} s3.outpaths = {'a': s1, 'b': s2} d = DFA([s0, s1, s2, s3]) d.start_state = s0 d.accept_states = [s1] d.alphabet = ['a', 'b'] print(d.all_regex() + '\n')
def test4(): #pg 17 Example 3.2 from Automata & Computability #accepts strings w/ 3 consecutive a's print('Test 4') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s0.outpaths = {'a': s1, 'b': s0} s1.outpaths = {'a': s2, 'b': s0} s2.outpaths = {'a': s3, 'b': s0} s3.outpaths = {'a': s3, 'b': s3} d = DFA([s0, s1, s2, s3]) d.start_state = s0 d.accept_states = [s3] d.alphabet = ['a', 'b'] print(d.all_regex() + '\n')
def test_from_nfa_simple(self): """should properly convert a simple NFA to a DFA""" nfa = NFA(**{ 'states': {'q0', 'q1', 'q2'}, 'symbols': {'0', '1'}, 'transitions': { 'q0': {'0': {'q0', 'q1'}}, 'q1': {'1': {'q2'}}, 'q2': {} }, 'initial_state': 'q0', 'final_states': {'q2'} }) dfa = DFA.from_nfa(nfa) nose.assert_equal(dfa.states, {'{}', '{q0}', '{q0q1}', '{q2}'}) nose.assert_equal(dfa.symbols, {'0', '1'}) nose.assert_equal(dfa.transitions, { '{}': {'0': '{}', '1': '{}'}, '{q0}': {'0': '{q0q1}', '1': '{}'}, '{q0q1}': {'0': '{q0q1}', '1': '{q2}'}, '{q2}': {'0': '{}', '1': '{}'} }) nose.assert_equal(dfa.initial_state, '{q0}') nose.assert_equal(dfa.final_states, {'{q2}'})
def test_dfa_match_empty(self): tr = {(0, 15): 1, (0, 22): 2, (1, 17): 3, (2, 17): 3, (2, 22): 4, (3, 22): 4, (4, 233): 4 } fs = {1: {'regex1'}, 4: {'regex2'} } dfa = DFA(5, tr, 0, fs) seq = [] self.assertEqual(dfa.match(seq), set(), "Matching the empty sequence should stay in the start state which is not final") tr = {(0, 15): 0} fs = {0: {'regex1'}} dfa = DFA(1, tr, 0, fs) self.assertEqual(dfa.match([]), {"regex1"}, "In this example, the start state is accepting, so the empty sequence should return the correct label")
def test6(): #Example 13.2 from Automata & Computability #accepts {a,b} and any strings with 3+ characters print('Test 6') s0 = state(0) s1 = state(1) s2 = state(2) s3 = state(3) s4 = state(4) s5 = state(5) s0.outpaths = {'a': s1, 'b': s2} s1.outpaths = {'a': s3, 'b': s4} s2.outpaths = {'a': s4, 'b': s3} s3.outpaths = {'a': s5, 'b': s5} s4.outpaths = {'a': s5, 'b': s5} s5.outpaths = {'a': s5, 'b': s5} d = DFA([s0, s1, s2, s3, s4, s5]) d.start_state = s0 d.accept_states = [s1, s2, s5] d.alphabet = ['a', 'b'] print(d.all_regex())
def create_fa(self, expression, alphabet): new_FA = DFA() new_FA.set_name(str(expression)) self.FA_list.append(new_FA) self.add_tab(expression, ['FA', alphabet])
class TestDFA(): def setup(self): # DFA which matches all binary strings ending in an odd number of '1's self.dfa = DFA(**{ 'states': {'q0', 'q1', 'q2'}, 'symbols': {'0', '1'}, 'transitions': { 'q0': {'0': 'q0', '1': 'q1'}, 'q1': {'0': 'q0', '1': 'q2'}, 'q2': {'0': 'q2', '1': 'q1'} }, 'initial_state': 'q0', 'final_states': {'q1'} }) def test_init_json(self): """should copy given JSON object into new DFA""" with open('tests/files/dfa.json', 'r') as dfa_file: dfa_json = json.load(dfa_file) new_dfa = DFA(**dfa_json) nose.assert_equal(new_dfa.states, set(dfa_json['states'])) nose.assert_is_not(new_dfa.states, dfa_json['states']) nose.assert_equal(new_dfa.symbols, set(dfa_json['symbols'])) nose.assert_is_not(new_dfa.symbols, dfa_json['symbols']) nose.assert_equal(new_dfa.transitions, dict(dfa_json['transitions'])) nose.assert_is_not(new_dfa.transitions, dfa_json['transitions']) nose.assert_equal(new_dfa.initial_state, dfa_json['initial_state']) nose.assert_equal(new_dfa.final_states, set(dfa_json['final_states'])) nose.assert_is_not(new_dfa.final_states, dfa_json['final_states']) def test_validate_automaton_missing_state(self): """should raise error if a state has no transitions defined""" with nose.assert_raises(automaton.MissingStateError): del self.dfa.transitions['q1'] self.dfa.validate_automaton() def test_validate_automaton_missing_symbol(self): """should raise error if a symbol transition is missing""" with nose.assert_raises(automaton.MissingSymbolError): del self.dfa.transitions['q1']['1'] self.dfa.validate_automaton() def test_validate_automaton_invalid_symbol(self): """should raise error if a transition references an invalid symbol""" with nose.assert_raises(automaton.InvalidSymbolError): self.dfa.transitions['q1']['2'] = 'q2' self.dfa.validate_automaton() def test_validate_automaton_invalid_state(self): """should raise error if a transition references an invalid state""" with nose.assert_raises(automaton.InvalidStateError): self.dfa.transitions['q1']['1'] = 'q3' self.dfa.validate_automaton() def test_validate_automaton_invalid_initial_state(self): """should raise error if the initial state is invalid""" with nose.assert_raises(automaton.InvalidStateError): self.dfa.initial_state = 'q3' self.dfa.validate_automaton() def test_validate_automaton_invalid_final_state(self): """should raise error if the final state is invalid""" with nose.assert_raises(automaton.InvalidStateError): self.dfa.final_states = {'q3'} self.dfa.validate_automaton() def test_validate_input_valid(self): """should return correct stop state when valid DFA input is given""" nose.assert_equal(self.dfa.validate_input('0111'), 'q1') def test_validate_input_invalid_symbol(self): """should raise error if an invalid symbol is read""" with nose.assert_raises(automaton.InvalidSymbolError): self.dfa.validate_input('01112') def test_validate_input_nonfinal_state(self): """should raise error if the stop state is not a final state""" with nose.assert_raises(automaton.FinalStateError): self.dfa.validate_input('011') def test_from_nfa_simple(self): """should properly convert a simple NFA to a DFA""" nfa = NFA(**{ 'states': {'q0', 'q1', 'q2'}, 'symbols': {'0', '1'}, 'transitions': { 'q0': {'0': {'q0', 'q1'}}, 'q1': {'1': {'q2'}}, 'q2': {} }, 'initial_state': 'q0', 'final_states': {'q2'} }) dfa = DFA.from_nfa(nfa) nose.assert_equal(dfa.states, {'{}', '{q0}', '{q0q1}', '{q2}'}) nose.assert_equal(dfa.symbols, {'0', '1'}) nose.assert_equal(dfa.transitions, { '{}': {'0': '{}', '1': '{}'}, '{q0}': {'0': '{q0q1}', '1': '{}'}, '{q0q1}': {'0': '{q0q1}', '1': '{q2}'}, '{q2}': {'0': '{}', '1': '{}'} }) nose.assert_equal(dfa.initial_state, '{q0}') nose.assert_equal(dfa.final_states, {'{q2}'})
def create_fa(self, expression, alphabet): automata = DFA() automata.set_name(str(expression)) self.FA_list.append(automata) self.add_tab(expression, ['FA', alphabet])