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

tweaks according to a code review

parent cd0fb1f1
Loading
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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("__________________________________")
+18 −16
Original line number Diff line number Diff line
@@ -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:]

    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)
+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
@@ -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))) + "} "
            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
+59 −70
Original line number Diff line number Diff line
@@ -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()

        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()
@@ -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])
+4 −8
Original line number Diff line number Diff line
@@ -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