Commit ef4e5e17 authored by Kateřina Sloupová's avatar Kateřina Sloupová
Browse files

tweaks according to a code review

parent cd0fb1f1
Pipeline #55726 failed with stage
in 20 seconds
......@@ -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)
......@@ -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("__________________________________")
......
......@@ -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()
......@@ -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:
......@@ -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
......@@ -69,15 +71,15 @@ def main():
if argument[:2] == "-o":
teacher_solution = argument[2:]
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."
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í.")
......@@ -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)
......
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
......@@ -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))
......@@ -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
......@@ -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))) + "} "
dest_states = nfa.transition[state, character]
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
......
......@@ -42,9 +42,6 @@ class Character:
class Eps:
def __init__(self):
pass
def __eq__(self, other):
return isinstance(other, Eps)
......@@ -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
......@@ -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)
......@@ -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
......@@ -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)
......@@ -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:
......@@ -219,7 +219,6 @@ class DFA:
def eliminate_unreachable(self) -> DFA:
dfa = deepcopy(self)
previous: Set[State] = set()
actual = {self.init}
......@@ -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()
dfa = self.eliminate_unreachable().total()
characters = self.sorted_characters() # bc I want to iterate always in same order
characters = self.sort_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()
......@@ -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:
......@@ -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:
......@@ -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:
......@@ -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:
......@@ -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]]
......@@ -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)))))
......@@ -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])
......
......@@ -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)
......@@ -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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment