Loading demo.py +2 −2 Original line number Diff line number Diff line Loading @@ -92,7 +92,7 @@ def dfa_eq(str1, str2): print(parser.dfa_to_str(dfa1)) dfa2 = parser.str_to_dfa(str2) print(parser.dfa_to_str(dfa2)) return dfa1.is_equivalent(dfa2) return DFA.is_equivalent(dfa1, dfa2) except RecognitionException as re: print(re) Loading Loading @@ -229,7 +229,7 @@ def main(): "of DFA through intersections with complements.") print() print("is dfa3 empty?", dfa_3.is_empty()) print("dfa3 and canonical dfa3: ", dfa_3.is_equivalent(canonical), ", should be True") print("dfa3 and canonical dfa3: ", DFA.is_equivalent(dfa_3, canonical), ", should be True") print() print("__________________________________") Loading fja_checker.py +18 −16 Original line number Diff line number Diff line Loading @@ -5,7 +5,8 @@ import sys import signal def parse(string: str, automaton_type: str) -> DFA: # TODO better name def parse_transform(string: str, automaton_type: str) -> DFA: try: parser = Parser() Loading @@ -18,9 +19,10 @@ def parse(string: str, automaton_type: str) -> DFA: except ParsingError as message: print("Parsing error:", message) exit(1) except AttributeError as message: print("Parsing error:", message) exit(1) def check_task(dfa: DFA, task: str) -> bool: Loading @@ -33,22 +35,22 @@ def check_task(dfa: DFA, task: str) -> bool: print("DFA není minimální.") return False not_canonical = not dfa.is_canonical() if task == "CAN" and not_canonical: canonical = dfa.is_canonical() if task == "CAN" and not canonical: print("DFA není kanonický.") return False if task == "TOC" and (not dfa.is_total() or not_canonical): if task == "TOC" and (not dfa.is_total() or not canonical): if not dfa.is_total(): print("DFA není totální.") if not_canonical: if not canonical: print("DFA není kanonický.") return False if task == "MIC" and (not dfa.is_minimal() or not_canonical): if task == "MIC" and (not dfa.is_minimal() or not canonical): if not dfa.is_minimal(): print("DFA není minimální.") if not_canonical: if not canonical: print("DFA není kanonický.") return False Loading @@ -69,15 +71,15 @@ def main(): if argument[:2] == "-o": teacher_solution = argument[2:] try: task_prefix, teacher_solution = teacher_solution.split(":", 1) assert len(task_prefix) >= 7, "Teachers solution could not be parsed correctly." automata_type, task, _ = task_prefix.split("-", 2) assert len(automata_type) == 3 and len(task) == 3, "Teachers solution could not be parsed correctly." student_dfa = parse(student_solution, automata_type) teacher_dfa = parse(teacher_solution, automata_type) student_dfa = parse_transform(student_solution, automata_type) teacher_dfa = parse_transform(teacher_solution, automata_type) try: # if teacher_dfa.is_empty() and student_dfa.is_empty(): print("Správné řešení.") Loading @@ -89,7 +91,7 @@ def main(): print("Nesprávné řešení.") exit(1) result = student_dfa.is_equivalent(teacher_dfa) result = DFA.is_equivalent(student_dfa, teacher_dfa) if check_task(student_dfa, task) and result: print("Správné řešení.") exit(0) Loading parser.py +16 −24 Original line number Diff line number Diff line from typing import List, Dict, Tuple, Optional, Union from typing import List, Dict, Tuple, Optional, Union, Set, TypeVar import re from reg_automata import DFA, NFA, NFAE, State, Character, Eps from reg_grammars import REG, Terminal from reg_grammars import REG, Terminal, Nonterminal import antlr4 # type: ignore from DFA_grammarLexer import DFA_grammarLexer from DFA_grammarParser import DFA_grammarParser Loading @@ -15,17 +15,21 @@ class ParsingError(Exception): self.message = message class Parser: def __init__(self): pass def names_to_str(self, collection: Union[Set[State], Set[Character], Set[Terminal], Set[Nonterminal]]) -> str: return "{" + ','.join(set(map(lambda x: x.name, collection))) + "}" def reg_to_str(self, reg: REG, full: bool = False) -> str: rules = self.rules_to_str(reg.rules) if not full: return "P=" + rules # full - verbose description of DFA - only for development, dismiss later nonterminals = "{" + ','.join(set(map(lambda x: x.name, reg.nonterminals))) + "}" terminals = "{" + ','.join(set(map(lambda x: x.name, reg.terminals))) + "}" nonterminals = self.names_to_str(reg.nonterminals) terminals = self.names_to_str(reg.terminals) out = "REG = (" + nonterminals + ", " + terminals + ", P," + reg.init.name + ")\nP=" out += (self.rules_to_str(reg.rules)) Loading Loading @@ -56,22 +60,18 @@ class Parser: def dfa_to_str(self, dfa: DFA, full : bool = False) -> str: # full - verbose description of DFA - only for development, dismiss later states = "{" + ','.join(set(map(lambda x: x.name, dfa.states))) + "}" characters = "{" + ','.join(set(map(lambda x: x.name, dfa.characters))) + "}" transition = "" for key, state_2 in dfa.transition.items(): state_1, character = key transition += "(" + state_1.name + "," + character.name + ")=" + state_2.name + " " init = "init=" + dfa.init.name final = "final={" + ','.join(set(map(lambda x: x.name, dfa.final))) + "}" final = "final=" + self.names_to_str(dfa.final) # full - verbose description of DFA - only for development, dismiss later if full: return "DFA = (" + states + ", " + characters + ", d, " + \ init + ", " + final + ")\n" + transition return "DFA = (" + self.names_to_str(dfa.states) + ", " + self.names_to_str(dfa.characters) + \ ", d, " + init + ", " + final + ")\n" + transition else: return init + " " + transition + final Loading @@ -79,27 +79,19 @@ class Parser: def nfa_to_str(self, nfa: Union[NFA, NFAE], full : bool = False) -> str: # full - verbose description of DFA - only for development, dismiss later states = "{" + ','.join(set(map(lambda x: x.name, nfa.states))) + "}" characters = "{" + ','.join(set(map(lambda x: x.name, nfa.characters))) + "}" transition = "" for key, set_states in nfa.transition.items(): state, character = key char = character.name if not isinstance(character, Eps) else "ε" if isinstance(character, Eps) and isinstance(nfa, NFAE): dest_states = nfa.transition[state, character] elif isinstance(character, Character) and isinstance(nfa, NFA): dest_states = nfa.transition[state, character] transition += "(" + state.name + "," + char + ")={" + \ ','.join(set(map(lambda x: x.name, dest_states))) + "} " transition += "(" + state.name + "," + char + ")=" + self.names_to_str(dest_states) + " " init = "init=" + nfa.init.name final = "final={" + ','.join(set(map(lambda x: x.name, nfa.final))) + "}" final = "final=" + self.names_to_str(nfa.final) if full: return "NFA = (" + states + ", " + characters + ", d, " + \ init + ", " + final + ")\n" + transition return "DFA = (" + self.names_to_str(nfa.states) + ", " + self.names_to_str(nfa.characters) + \ ", d, " + init + ", " + final + ")\n" + transition else: return init + " " + transition + " " + final Loading reg_automata.py +59 −70 Original line number Diff line number Diff line Loading @@ -42,9 +42,6 @@ class Character: class Eps: def __init__(self): pass def __eq__(self, other): return isinstance(other, Eps) Loading Loading @@ -79,29 +76,27 @@ class DFA: self.transition = transition self.init = init self.final = final assert self.check() self.check() def check(self) -> bool: def check(self): assert len(self.states) > 0, "empty automaton" for (state, character) in self.transition: assert state in self.states, "unknown state " + state.name assert character in self.characters, "unknown character " + character.name assert self.transition[state, character] in self.states, "unknown state " + self.transition[ state, character].name assert self.transition[state, character] in self.states, \ "unknown state " + self.transition[state, character].name assert self.init in self.states, "init not in states" for state in self.final: assert state in self.states, "unknown state " + state.name return True def total(self) -> DFA: if self.is_total(): return self dfa = deepcopy(self) total: Dict[Tuple[State, Character], State] = {} total: DFA.Transition = {} # check: consider import itertools hell_id = 0 Loading Loading @@ -129,8 +124,8 @@ class DFA: return True def accepts(self, word: Union[str, List[Character]]) -> bool: evaluate: List[Character] = [] if isinstance(word, str): evaluate = [] for letter in word: character = Character(letter) evaluate.append(character) Loading @@ -152,27 +147,33 @@ class DFA: dfa.final = dfa.states.difference(dfa.final) return dfa @staticmethod def state_composition(state_1: State, state_2: State) -> State: return State(f"{state_1.name}_{state_2.name}") @staticmethod def composition(dfa_1: DFA, dfa_2: DFA, operation: Composition) -> DFA: """Does not require input automata to have the same alphabet.""" # new characters characters = dfa_1.characters.union(dfa_2.characters) dfa_1.characters = characters dfa_2.characters = characters if not dfa_1.is_total(): dfa_1 = dfa_1.total() if not dfa_2.is_total(): dfa_2 = dfa_2.total() # new characters characters = dfa_1.characters.union(dfa_2.characters) # ATTENTION: terminals may differ -> make total at the end? # new init init = State(f"{dfa_1.init.name}_{dfa_2.init.name}") init = DFA.state_composition(dfa_1.init, dfa_2.init) # new states and transition states = {init} transition: Dict[Tuple[State, Character], State] = dict() for state_1 in dfa_1.states: for state_2 in dfa_2.states: state = State(f"{state_1.name}_{state_2.name}") # TODO avoid conflicts! state = DFA.state_composition(state_1, state_2) # TODO avoid conflicts! states.add(state) # new transition Loading @@ -181,14 +182,14 @@ class DFA: (state_2, character) in dfa_2.transition: dest_state_1 = dfa_1.transition[state_1, character] dest_state_2 = dfa_2.transition[state_2, character] dest_state = State(dest_state_1.name + "_" + dest_state_2.name) dest_state = DFA.state_composition(dest_state_1, dest_state_2) transition[state, character] = dest_state # new final final: Set[State] = set() for state_1 in dfa_1.final: for state_2 in dfa_2.final: state = State(f"{state_1.name}_{state_2.name}") state = DFA.state_composition(state_1, state_2) if operation == Composition.Union: final.add(state) Loading @@ -202,8 +203,7 @@ class DFA: state_2 not in dfa_2.final: final.add(state) dfa = DFA(states, characters, transition, init, final) return dfa return DFA(states, characters, transition, init, final) @staticmethod def union(dfa_1: DFA, dfa_2: DFA) -> DFA: Loading @@ -219,7 +219,6 @@ class DFA: def eliminate_unreachable(self) -> DFA: dfa = deepcopy(self) previous: Set[State] = set() actual = {self.init} Loading @@ -239,25 +238,27 @@ class DFA: return dfa def sort_characters(self) -> List[Character]: def sorted_characters(self) -> List[Character]: return sorted(self.characters, key=lambda x: x.name) def minimize(self) -> DFA: dfa = deepcopy(self) dfa = dfa.eliminate_unreachable() dfa = dfa.total() characters = self.sort_characters() # bc I want to iterate always in same order dfa = self.eliminate_unreachable().total() characters = self.sorted_characters() # bc I want to iterate always in same order previous: Optional[Dict[State, int]] = None actual = dict() previous: Dict[State, int] = dict() actual: Dict[State, int] = dict() for state in dfa.states: if state in dfa.final: actual[state] = 1 else: actual[state] = 0 while previous != actual: classes_prev = 0 classes_new = 1 if len(self.final) > 0 else 0 if len(self.states.difference(self.final)) > 0: classes_new += 1 while classes_prev != classes_new: previous = deepcopy(actual) patterns: Dict[Tuple[int, ...], Set[State]] = dict() actual = dict() Loading @@ -272,13 +273,14 @@ class DFA: else: patterns[tuple(pattern)] = {state} i = 0 classes_prev = classes_new classes_new = 0 for states in patterns.values(): for state in states: actual[state] = i i += 1 actual[state] = classes_new classes_new += 1 new_transition: Dict[Tuple[State, Character], State] = {} new_transition: DFA.Transition = {} dfa.states = set() dfa.final = set() for state in actual: Loading Loading @@ -328,10 +330,10 @@ class DFA: def canonize(self) -> DFA: dfa = deepcopy(self) remain = deepcopy(self.states) characters = dfa.sort_characters() characters = dfa.sorted_characters() i = 0 queue = deque([dfa.init]) # TODO deque queue = deque([dfa.init]) while len(queue) > 0: actual = queue.popleft() if actual in remain: Loading @@ -354,14 +356,11 @@ class DFA: rules: Dict[Nonterminal, Set[Union[Terminal, Tuple[Terminal, Nonterminal]]]] = dict() for state in self.states: nonterminal = Nonterminal(state.name) nonterminals.add(nonterminal) nonterminals.add(Nonterminal(state.name)) for character in self.characters: terminal = Terminal(character.name) terminals.add(terminal) terminals.add(Terminal(character.name)) init = Nonterminal("new_init") # TODO assert that name is unique, as for hell in make_total init = init nonterminals.add(init) for state, character in self.transition: Loading @@ -384,7 +383,7 @@ class DFA: else: rules[nonterminal1] = {(terminal, nonterminal2)} if dest_state in self.final: rules[nonterminal1].add((terminal)) rules[nonterminal1].add(terminal) reg = REG(nonterminals, terminals, rules, init) if self.init in self.final: Loading Loading @@ -427,40 +426,32 @@ class DFA: def is_universal(self): return self.complement().is_empty() # returns: True or (1. what IS in first language (self) and NOT in second (dfa), 2. analogically) def is_equivalent(self, dfa: DFA) -> IsEquivalentResult: left_empty = self.intersection(self, dfa.complement()).is_empty() right_empty = self.intersection(self.complement(), dfa).is_empty() # returns: True or fst_empty: what IS in first language and NOT in second, snd_empty: analogically) @staticmethod def is_equivalent(fst: DFA, snd: DFA) -> IsEquivalentResult: fst_empty = DFA.intersection(fst, snd.complement()).is_empty() snd_empty = DFA.intersection(fst.complement(), snd).is_empty() if left_empty and right_empty: if fst_empty and snd_empty: result = IsEquivalentResult() else: result = IsEquivalentResult(left_empty.counterexample, right_empty.counterexample) result = IsEquivalentResult(fst_empty.counterexample, snd_empty.counterexample) return result def is_part_identical(self, dfa: DFA) -> bool: # print(self.states == dfa.states) # print(self.characters == dfa.characters) # print(len(self.transition), len(dfa.transition)) # print(self.transition == dfa.transition) # print(self.init == dfa.init) # print(self.final == dfa.final) return self.states == dfa.states and self.characters == dfa.characters and \ self.transition == dfa.transition and self.init == dfa.init and \ self.final == dfa.final @staticmethod def is_part_identical(fst: DFA, snd: DFA) -> bool: return fst.states == snd.states and fst.characters == snd.characters and \ fst.transition == snd.transition and fst.init == snd.init and \ fst.final == snd.final def is_minimal(self) -> bool: minimal = self.minimize() return len(self.states) == len(minimal.states) and \ return self.is_total() and len(self.states) == len(minimal.states) and \ len(self.transition) == len(minimal.transition) def is_canonical(self) -> bool: canonic = self.canonize() #import parser as par #parser = par.Parser() #print(parser.dfa_to_str(self)) #print(parser.dfa_to_str(canonic)) return self.is_part_identical(canonic) return DFA.is_part_identical(self, canonic) class NFA: Transition = Dict[Tuple[State, Character], Set[State]] Loading Loading @@ -511,8 +502,7 @@ class NFA: for state_set, character in transition: new_transition[self.unset(state_set), character] = self.unset(transition[state_set, character]) dfa = DFA(new_states, self.characters, new_transition, self.init, new_final) return dfa return DFA(new_states, self.characters, new_transition, self.init, new_final) def unset(self, states: FrozenSet[State]) -> State: return State('_'.join(set(map(lambda x: x.name, sorted(states, key=lambda x: x.name))))) Loading Loading @@ -558,8 +548,7 @@ class NFAE: if surroundings[self.init].intersection(self.final): new_final.add(self.init) nfa = NFA(self.states, self.characters, new_transition, self.init, new_final) return nfa return NFA(self.states, self.characters, new_transition, self.init, new_final) def epsilon_surroundings(self, state: State) -> Set[State]: reached: Deque[State] = deque([state]) Loading reg_grammars.py +4 −8 Original line number Diff line number Diff line Loading @@ -67,13 +67,11 @@ class REG: init = State(self.init.name) states: Set[State] = set() for nonterminal in self.nonterminals: state = State(nonterminal.name) states.add(state) states.add(State(nonterminal.name)) characters: Set[Character] = set() for terminal in self.terminals: character = Character(terminal.name) characters.add(character) characters.add(Character(terminal.name)) final = State("new_final") # TODO assert that name is unique, as for hell in make_total states.add(final) Loading @@ -94,12 +92,10 @@ class REG: # rule A -> aB becomes d(A, a) = B elif (state, rule[0]) in transition: character = Character(rule[0].name) move = State(rule[1].name) transition[state, character].add(move) transition[state, character].add(State(rule[1].name)) else: character = Character(rule[0].name) move = State(rule[1].name) transition[state, character] = {move} transition[state, character] = {State(rule[1].name)} # TODO if init -> \eps: nfa.final.add(nfa.init) # I need to know how to treat \eps Loading Loading
demo.py +2 −2 Original line number Diff line number Diff line Loading @@ -92,7 +92,7 @@ def dfa_eq(str1, str2): print(parser.dfa_to_str(dfa1)) dfa2 = parser.str_to_dfa(str2) print(parser.dfa_to_str(dfa2)) return dfa1.is_equivalent(dfa2) return DFA.is_equivalent(dfa1, dfa2) except RecognitionException as re: print(re) Loading Loading @@ -229,7 +229,7 @@ def main(): "of DFA through intersections with complements.") print() print("is dfa3 empty?", dfa_3.is_empty()) print("dfa3 and canonical dfa3: ", dfa_3.is_equivalent(canonical), ", should be True") print("dfa3 and canonical dfa3: ", DFA.is_equivalent(dfa_3, canonical), ", should be True") print() print("__________________________________") Loading
fja_checker.py +18 −16 Original line number Diff line number Diff line Loading @@ -5,7 +5,8 @@ import sys import signal def parse(string: str, automaton_type: str) -> DFA: # TODO better name def parse_transform(string: str, automaton_type: str) -> DFA: try: parser = Parser() Loading @@ -18,9 +19,10 @@ def parse(string: str, automaton_type: str) -> DFA: except ParsingError as message: print("Parsing error:", message) exit(1) except AttributeError as message: print("Parsing error:", message) exit(1) def check_task(dfa: DFA, task: str) -> bool: Loading @@ -33,22 +35,22 @@ def check_task(dfa: DFA, task: str) -> bool: print("DFA není minimální.") return False not_canonical = not dfa.is_canonical() if task == "CAN" and not_canonical: canonical = dfa.is_canonical() if task == "CAN" and not canonical: print("DFA není kanonický.") return False if task == "TOC" and (not dfa.is_total() or not_canonical): if task == "TOC" and (not dfa.is_total() or not canonical): if not dfa.is_total(): print("DFA není totální.") if not_canonical: if not canonical: print("DFA není kanonický.") return False if task == "MIC" and (not dfa.is_minimal() or not_canonical): if task == "MIC" and (not dfa.is_minimal() or not canonical): if not dfa.is_minimal(): print("DFA není minimální.") if not_canonical: if not canonical: print("DFA není kanonický.") return False Loading @@ -69,15 +71,15 @@ def main(): if argument[:2] == "-o": teacher_solution = argument[2:] try: task_prefix, teacher_solution = teacher_solution.split(":", 1) assert len(task_prefix) >= 7, "Teachers solution could not be parsed correctly." automata_type, task, _ = task_prefix.split("-", 2) assert len(automata_type) == 3 and len(task) == 3, "Teachers solution could not be parsed correctly." student_dfa = parse(student_solution, automata_type) teacher_dfa = parse(teacher_solution, automata_type) student_dfa = parse_transform(student_solution, automata_type) teacher_dfa = parse_transform(teacher_solution, automata_type) try: # if teacher_dfa.is_empty() and student_dfa.is_empty(): print("Správné řešení.") Loading @@ -89,7 +91,7 @@ def main(): print("Nesprávné řešení.") exit(1) result = student_dfa.is_equivalent(teacher_dfa) result = DFA.is_equivalent(student_dfa, teacher_dfa) if check_task(student_dfa, task) and result: print("Správné řešení.") exit(0) Loading
parser.py +16 −24 Original line number Diff line number Diff line from typing import List, Dict, Tuple, Optional, Union from typing import List, Dict, Tuple, Optional, Union, Set, TypeVar import re from reg_automata import DFA, NFA, NFAE, State, Character, Eps from reg_grammars import REG, Terminal from reg_grammars import REG, Terminal, Nonterminal import antlr4 # type: ignore from DFA_grammarLexer import DFA_grammarLexer from DFA_grammarParser import DFA_grammarParser Loading @@ -15,17 +15,21 @@ class ParsingError(Exception): self.message = message class Parser: def __init__(self): pass def names_to_str(self, collection: Union[Set[State], Set[Character], Set[Terminal], Set[Nonterminal]]) -> str: return "{" + ','.join(set(map(lambda x: x.name, collection))) + "}" def reg_to_str(self, reg: REG, full: bool = False) -> str: rules = self.rules_to_str(reg.rules) if not full: return "P=" + rules # full - verbose description of DFA - only for development, dismiss later nonterminals = "{" + ','.join(set(map(lambda x: x.name, reg.nonterminals))) + "}" terminals = "{" + ','.join(set(map(lambda x: x.name, reg.terminals))) + "}" nonterminals = self.names_to_str(reg.nonterminals) terminals = self.names_to_str(reg.terminals) out = "REG = (" + nonterminals + ", " + terminals + ", P," + reg.init.name + ")\nP=" out += (self.rules_to_str(reg.rules)) Loading Loading @@ -56,22 +60,18 @@ class Parser: def dfa_to_str(self, dfa: DFA, full : bool = False) -> str: # full - verbose description of DFA - only for development, dismiss later states = "{" + ','.join(set(map(lambda x: x.name, dfa.states))) + "}" characters = "{" + ','.join(set(map(lambda x: x.name, dfa.characters))) + "}" transition = "" for key, state_2 in dfa.transition.items(): state_1, character = key transition += "(" + state_1.name + "," + character.name + ")=" + state_2.name + " " init = "init=" + dfa.init.name final = "final={" + ','.join(set(map(lambda x: x.name, dfa.final))) + "}" final = "final=" + self.names_to_str(dfa.final) # full - verbose description of DFA - only for development, dismiss later if full: return "DFA = (" + states + ", " + characters + ", d, " + \ init + ", " + final + ")\n" + transition return "DFA = (" + self.names_to_str(dfa.states) + ", " + self.names_to_str(dfa.characters) + \ ", d, " + init + ", " + final + ")\n" + transition else: return init + " " + transition + final Loading @@ -79,27 +79,19 @@ class Parser: def nfa_to_str(self, nfa: Union[NFA, NFAE], full : bool = False) -> str: # full - verbose description of DFA - only for development, dismiss later states = "{" + ','.join(set(map(lambda x: x.name, nfa.states))) + "}" characters = "{" + ','.join(set(map(lambda x: x.name, nfa.characters))) + "}" transition = "" for key, set_states in nfa.transition.items(): state, character = key char = character.name if not isinstance(character, Eps) else "ε" if isinstance(character, Eps) and isinstance(nfa, NFAE): dest_states = nfa.transition[state, character] elif isinstance(character, Character) and isinstance(nfa, NFA): dest_states = nfa.transition[state, character] transition += "(" + state.name + "," + char + ")={" + \ ','.join(set(map(lambda x: x.name, dest_states))) + "} " transition += "(" + state.name + "," + char + ")=" + self.names_to_str(dest_states) + " " init = "init=" + nfa.init.name final = "final={" + ','.join(set(map(lambda x: x.name, nfa.final))) + "}" final = "final=" + self.names_to_str(nfa.final) if full: return "NFA = (" + states + ", " + characters + ", d, " + \ init + ", " + final + ")\n" + transition return "DFA = (" + self.names_to_str(nfa.states) + ", " + self.names_to_str(nfa.characters) + \ ", d, " + init + ", " + final + ")\n" + transition else: return init + " " + transition + " " + final Loading
reg_automata.py +59 −70 Original line number Diff line number Diff line Loading @@ -42,9 +42,6 @@ class Character: class Eps: def __init__(self): pass def __eq__(self, other): return isinstance(other, Eps) Loading Loading @@ -79,29 +76,27 @@ class DFA: self.transition = transition self.init = init self.final = final assert self.check() self.check() def check(self) -> bool: def check(self): assert len(self.states) > 0, "empty automaton" for (state, character) in self.transition: assert state in self.states, "unknown state " + state.name assert character in self.characters, "unknown character " + character.name assert self.transition[state, character] in self.states, "unknown state " + self.transition[ state, character].name assert self.transition[state, character] in self.states, \ "unknown state " + self.transition[state, character].name assert self.init in self.states, "init not in states" for state in self.final: assert state in self.states, "unknown state " + state.name return True def total(self) -> DFA: if self.is_total(): return self dfa = deepcopy(self) total: Dict[Tuple[State, Character], State] = {} total: DFA.Transition = {} # check: consider import itertools hell_id = 0 Loading Loading @@ -129,8 +124,8 @@ class DFA: return True def accepts(self, word: Union[str, List[Character]]) -> bool: evaluate: List[Character] = [] if isinstance(word, str): evaluate = [] for letter in word: character = Character(letter) evaluate.append(character) Loading @@ -152,27 +147,33 @@ class DFA: dfa.final = dfa.states.difference(dfa.final) return dfa @staticmethod def state_composition(state_1: State, state_2: State) -> State: return State(f"{state_1.name}_{state_2.name}") @staticmethod def composition(dfa_1: DFA, dfa_2: DFA, operation: Composition) -> DFA: """Does not require input automata to have the same alphabet.""" # new characters characters = dfa_1.characters.union(dfa_2.characters) dfa_1.characters = characters dfa_2.characters = characters if not dfa_1.is_total(): dfa_1 = dfa_1.total() if not dfa_2.is_total(): dfa_2 = dfa_2.total() # new characters characters = dfa_1.characters.union(dfa_2.characters) # ATTENTION: terminals may differ -> make total at the end? # new init init = State(f"{dfa_1.init.name}_{dfa_2.init.name}") init = DFA.state_composition(dfa_1.init, dfa_2.init) # new states and transition states = {init} transition: Dict[Tuple[State, Character], State] = dict() for state_1 in dfa_1.states: for state_2 in dfa_2.states: state = State(f"{state_1.name}_{state_2.name}") # TODO avoid conflicts! state = DFA.state_composition(state_1, state_2) # TODO avoid conflicts! states.add(state) # new transition Loading @@ -181,14 +182,14 @@ class DFA: (state_2, character) in dfa_2.transition: dest_state_1 = dfa_1.transition[state_1, character] dest_state_2 = dfa_2.transition[state_2, character] dest_state = State(dest_state_1.name + "_" + dest_state_2.name) dest_state = DFA.state_composition(dest_state_1, dest_state_2) transition[state, character] = dest_state # new final final: Set[State] = set() for state_1 in dfa_1.final: for state_2 in dfa_2.final: state = State(f"{state_1.name}_{state_2.name}") state = DFA.state_composition(state_1, state_2) if operation == Composition.Union: final.add(state) Loading @@ -202,8 +203,7 @@ class DFA: state_2 not in dfa_2.final: final.add(state) dfa = DFA(states, characters, transition, init, final) return dfa return DFA(states, characters, transition, init, final) @staticmethod def union(dfa_1: DFA, dfa_2: DFA) -> DFA: Loading @@ -219,7 +219,6 @@ class DFA: def eliminate_unreachable(self) -> DFA: dfa = deepcopy(self) previous: Set[State] = set() actual = {self.init} Loading @@ -239,25 +238,27 @@ class DFA: return dfa def sort_characters(self) -> List[Character]: def sorted_characters(self) -> List[Character]: return sorted(self.characters, key=lambda x: x.name) def minimize(self) -> DFA: dfa = deepcopy(self) dfa = dfa.eliminate_unreachable() dfa = dfa.total() characters = self.sort_characters() # bc I want to iterate always in same order dfa = self.eliminate_unreachable().total() characters = self.sorted_characters() # bc I want to iterate always in same order previous: Optional[Dict[State, int]] = None actual = dict() previous: Dict[State, int] = dict() actual: Dict[State, int] = dict() for state in dfa.states: if state in dfa.final: actual[state] = 1 else: actual[state] = 0 while previous != actual: classes_prev = 0 classes_new = 1 if len(self.final) > 0 else 0 if len(self.states.difference(self.final)) > 0: classes_new += 1 while classes_prev != classes_new: previous = deepcopy(actual) patterns: Dict[Tuple[int, ...], Set[State]] = dict() actual = dict() Loading @@ -272,13 +273,14 @@ class DFA: else: patterns[tuple(pattern)] = {state} i = 0 classes_prev = classes_new classes_new = 0 for states in patterns.values(): for state in states: actual[state] = i i += 1 actual[state] = classes_new classes_new += 1 new_transition: Dict[Tuple[State, Character], State] = {} new_transition: DFA.Transition = {} dfa.states = set() dfa.final = set() for state in actual: Loading Loading @@ -328,10 +330,10 @@ class DFA: def canonize(self) -> DFA: dfa = deepcopy(self) remain = deepcopy(self.states) characters = dfa.sort_characters() characters = dfa.sorted_characters() i = 0 queue = deque([dfa.init]) # TODO deque queue = deque([dfa.init]) while len(queue) > 0: actual = queue.popleft() if actual in remain: Loading @@ -354,14 +356,11 @@ class DFA: rules: Dict[Nonterminal, Set[Union[Terminal, Tuple[Terminal, Nonterminal]]]] = dict() for state in self.states: nonterminal = Nonterminal(state.name) nonterminals.add(nonterminal) nonterminals.add(Nonterminal(state.name)) for character in self.characters: terminal = Terminal(character.name) terminals.add(terminal) terminals.add(Terminal(character.name)) init = Nonterminal("new_init") # TODO assert that name is unique, as for hell in make_total init = init nonterminals.add(init) for state, character in self.transition: Loading @@ -384,7 +383,7 @@ class DFA: else: rules[nonterminal1] = {(terminal, nonterminal2)} if dest_state in self.final: rules[nonterminal1].add((terminal)) rules[nonterminal1].add(terminal) reg = REG(nonterminals, terminals, rules, init) if self.init in self.final: Loading Loading @@ -427,40 +426,32 @@ class DFA: def is_universal(self): return self.complement().is_empty() # returns: True or (1. what IS in first language (self) and NOT in second (dfa), 2. analogically) def is_equivalent(self, dfa: DFA) -> IsEquivalentResult: left_empty = self.intersection(self, dfa.complement()).is_empty() right_empty = self.intersection(self.complement(), dfa).is_empty() # returns: True or fst_empty: what IS in first language and NOT in second, snd_empty: analogically) @staticmethod def is_equivalent(fst: DFA, snd: DFA) -> IsEquivalentResult: fst_empty = DFA.intersection(fst, snd.complement()).is_empty() snd_empty = DFA.intersection(fst.complement(), snd).is_empty() if left_empty and right_empty: if fst_empty and snd_empty: result = IsEquivalentResult() else: result = IsEquivalentResult(left_empty.counterexample, right_empty.counterexample) result = IsEquivalentResult(fst_empty.counterexample, snd_empty.counterexample) return result def is_part_identical(self, dfa: DFA) -> bool: # print(self.states == dfa.states) # print(self.characters == dfa.characters) # print(len(self.transition), len(dfa.transition)) # print(self.transition == dfa.transition) # print(self.init == dfa.init) # print(self.final == dfa.final) return self.states == dfa.states and self.characters == dfa.characters and \ self.transition == dfa.transition and self.init == dfa.init and \ self.final == dfa.final @staticmethod def is_part_identical(fst: DFA, snd: DFA) -> bool: return fst.states == snd.states and fst.characters == snd.characters and \ fst.transition == snd.transition and fst.init == snd.init and \ fst.final == snd.final def is_minimal(self) -> bool: minimal = self.minimize() return len(self.states) == len(minimal.states) and \ return self.is_total() and len(self.states) == len(minimal.states) and \ len(self.transition) == len(minimal.transition) def is_canonical(self) -> bool: canonic = self.canonize() #import parser as par #parser = par.Parser() #print(parser.dfa_to_str(self)) #print(parser.dfa_to_str(canonic)) return self.is_part_identical(canonic) return DFA.is_part_identical(self, canonic) class NFA: Transition = Dict[Tuple[State, Character], Set[State]] Loading Loading @@ -511,8 +502,7 @@ class NFA: for state_set, character in transition: new_transition[self.unset(state_set), character] = self.unset(transition[state_set, character]) dfa = DFA(new_states, self.characters, new_transition, self.init, new_final) return dfa return DFA(new_states, self.characters, new_transition, self.init, new_final) def unset(self, states: FrozenSet[State]) -> State: return State('_'.join(set(map(lambda x: x.name, sorted(states, key=lambda x: x.name))))) Loading Loading @@ -558,8 +548,7 @@ class NFAE: if surroundings[self.init].intersection(self.final): new_final.add(self.init) nfa = NFA(self.states, self.characters, new_transition, self.init, new_final) return nfa return NFA(self.states, self.characters, new_transition, self.init, new_final) def epsilon_surroundings(self, state: State) -> Set[State]: reached: Deque[State] = deque([state]) Loading
reg_grammars.py +4 −8 Original line number Diff line number Diff line Loading @@ -67,13 +67,11 @@ class REG: init = State(self.init.name) states: Set[State] = set() for nonterminal in self.nonterminals: state = State(nonterminal.name) states.add(state) states.add(State(nonterminal.name)) characters: Set[Character] = set() for terminal in self.terminals: character = Character(terminal.name) characters.add(character) characters.add(Character(terminal.name)) final = State("new_final") # TODO assert that name is unique, as for hell in make_total states.add(final) Loading @@ -94,12 +92,10 @@ class REG: # rule A -> aB becomes d(A, a) = B elif (state, rule[0]) in transition: character = Character(rule[0].name) move = State(rule[1].name) transition[state, character].add(move) transition[state, character].add(State(rule[1].name)) else: character = Character(rule[0].name) move = State(rule[1].name) transition[state, character] = {move} transition[state, character] = {State(rule[1].name)} # TODO if init -> \eps: nfa.final.add(nfa.init) # I need to know how to treat \eps Loading