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

Delete objects.py

parent ed41bb02
Loading
Loading
Loading
Loading
Loading

objects.py

deleted100644 → 0
+0 −587
Original line number Diff line number Diff line
from __future__ import annotations
from typing import Set, FrozenSet, List, Dict, Union, Tuple, Deque, Optional, TypeVar
from enum import Enum
import enum
from copy import deepcopy
from collections import deque


class Composition(Enum):
    Union = enum.auto()
    Intersection = enum.auto()
    Subtraction = enum.auto()


class Terminal:
    def __init__(self, name: str):
        self.name = name

    def __eq__(self, obj):
        if isinstance(obj, Terminal):
            return obj.name == self.name
        return False

    def __hash__(self):
        return hash(self.name)


class Character:
    def __init__(self, name: str):
        self.name = name

    def __eq__(self, obj):
        if isinstance(obj, Character):
            return obj.name == self.name
        return False

    def __hash__(self):
        return hash(self.name)


class Eps:
    pass


class Nonterminal:
    def __init__(self, name: str):
        self.name = name

    def __eq__(self, obj):
        if isinstance(obj, Nonterminal):
            return obj.name == self.name
        return False

    def __hash__(self):
        return hash(self.name)


class State:
    def __init__(self, name: str):
        self.name = name

    def __eq__(self, obj):
        if isinstance(obj, State):
            return obj.name == self.name
        return False

    def __hash__(self):
        return hash(self.name)


class REG:
    Rules = Dict[Nonterminal, Set[Union[Terminal, Tuple[Terminal, Nonterminal]]]]
    type_var = TypeVar('type_var')

    # TODO should accept init -> epsilon
    def __init__(self, nonterminals: Set[Nonterminal],
                 terminals: Set[Terminal],
                 rules: Rules,
                 init: Nonterminal):
        self.nonterminals = nonterminals
        self.terminals = terminals
        self.rules = rules
        self.init = init
        assert self.check

    # TODO ask about concept
    def check(self) -> bool:
        # scheme of grammar - empty sets and dicts are implemention only variant
        if len(self.nonterminals) == 0:
            assert len(self.terminals) == 0 and len(self.rules) == 0 and self.init == None, "nonempty scheme of grammar"
            return True

        nonterminal_names = set(map(lambda x: x.name, self.nonterminals))
        terminal_names = set(map(lambda x: x.name, self.terminals))
        assert len(nonterminal_names.intersection(terminal_names)) == 0, "name conflict"

        for nonterminal in self.rules:
            assert nonterminal in self.nonterminals, "unknown nonterminal " + nonterminal.name
            for rule in self.rules[nonterminal]:
                if isinstance(rule, Terminal):
                    assert rule in self.terminals, "unknown terminal " + rule.name
                else:
                    assert rule[0] in self.terminals, "unknown terminal " + rule[0].name
                    assert rule[1] in self.nonterminals, "unknown nonterminal " + rule[1].name

        assert self.init in self.nonterminals, "init not in nonterminals"

        return True

    def reg_to_nfa(self) -> NFA:

        init = State(self.init.name)
        states: Set[State] = set()
        for nonterminal in self.nonterminals:
            state = State(nonterminal.name)
            states.add(state)

        characters: Set[Character] = set()
        for terminal in self.terminals:
            character = Character(terminal.name)
            characters.add(character)

        final = State("new_final")  # TODO assert that name is unique, as for hell in make_total
        states.add(final)
        transition: Dict[Tuple[State, Character], Set[State]] = dict()

        for nonterminal in self.rules:
            state = State(nonterminal.name)
            for rule in self.rules[nonterminal]:

                # rule A -> a becomes d(A, a) = final
                if isinstance(rule, Terminal):  # TODO and a is not \eps
                    character = Character(rule.name)
                    if (state, character) in transition:
                        transition[state, character].add(final)
                    else:
                        transition[state, character] = {final}

                # 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)
                else:
                    character = Character(rule[0].name)
                    move = State(rule[1].name)
                    transition[state, character] = {move}

        # TODO if init -> \eps: nfa.final.add(nfa.init)
        # I need to know how to treat \eps
        nfa = NFA(states, characters, transition, init, {final})
        return nfa


class DFA:
    Transition = Dict[Tuple[State, Character], State]
    type_var = TypeVar('type_var')

    def set_or_none(self, out: Optional[Set[type_var]]) -> Set[type_var]:
        return set() if out is None else out

    def dict_or_none(self, out: Optional[Transition]) -> Transition:
        return dict() if out is None else out

    def __init__(self, states: Set[State],
                 characters: Set[Character],
                 transition: Transition,
                 init: State,
                 final: Set[State]):

        self.states = self.set_or_none(states)
        self.characters = self.set_or_none(characters)
        self.transition = self.dict_or_none(transition)
        self.init = init
        self.final = self.set_or_none(final)
        assert self.check

    def check(self) -> bool:
        # # scheme of automaton - empty sets and dicts are implemention only variant
        # if len(self.states) == 0:
        #     assert len(self.characters) == 0 and len(self.transition) == 0 and self.init == None \
        #        and len(self.final) == 0, "nonempty scheme of automaton"
        #     return True
        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.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] = {}

        # check: consider import itertools
        hell_id = 0
        hell = State(str(hell_id))
        while hell in dfa.states:
            hell_id += 1
            hell.name = str(hell_id)
        dfa.states.add(hell)

        for state in dfa.states:
            for character in dfa.characters:
                if (state, character) in dfa.transition:
                    total[state, character] = dfa.transition[state, character]
                else:
                    total[state, character] = hell

        dfa.transition = total
        return dfa

    def is_total(self) -> bool:
        for state in self.states:
            for character in self.characters:
                if (state, character) not in self.transition:
                    return False
        return True

    def accepts(self, word: str) -> bool:
        state = self.init
        for letter in word:
            character = Character(letter)
            if (state, character) not in self.transition:
                return False
            state = self.transition[state, character]
        return state in self.final

    def complement(self) -> DFA:
        dfa = deepcopy(self)
        if not dfa.is_total():
            dfa = dfa.total()

        dfa.final = dfa.states.difference(dfa.final)
        return dfa

    @staticmethod
    def composition(dfa_1: DFA, dfa_2: DFA, operation: Composition) -> DFA:

        # 2019 IB102 slides want both total
        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}")

        # 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!
                states.add(state)

                # new transition
                for character in characters:
                    if (state_1, character) in dfa_1.transition and \
                            (state_2, character) in dfa_2.transition:
                        move_1 = dfa_1.transition[state_1, character]
                        move_2 = dfa_2.transition[state_2, character]
                        move = State(move_1.name + "_" + move_2.name)
                        transition[state, character] = move

        # 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}")

                if operation == Composition.Union:
                    final.add(state)

                elif operation == Composition.Intersection and \
                        state_1 in dfa_1.final and \
                        state_2 in dfa_2.final:
                    final.add(state)

                elif operation == Composition.Subtraction and \
                        state_2 not in dfa_2.final:
                    final.add(state)

        dfa = DFA(states, characters, transition, init, final)
        return dfa

    @staticmethod
    def union(dfa_1: DFA, dfa_2: DFA) -> DFA:
        return DFA.composition(dfa_1, dfa_2, Composition.Union)

    @staticmethod
    def intersection(dfa_1: DFA, dfa_2: DFA) -> DFA:
        return DFA.composition(dfa_1, dfa_2, Composition.Intersection)

    @staticmethod
    def subtraction(dfa_1: DFA, dfa_2: DFA) -> DFA:
        return DFA.composition(dfa_1, dfa_2, Composition.Subtraction)

    def eliminate_unreachable(self) -> DFA:
        dfa = deepcopy(self)

        previous: Set[State] = set()
        actual = {self.init}

        while previous != actual:
            previous = deepcopy(actual)

            for state in previous:
                for character in dfa.characters:
                    if (state, character) in self.transition:
                        actual.add(self.transition[state, character])

        dfa.states = actual
        dfa.final = dfa.final.intersection(actual)
        for (state, character) in self.transition:
            if state not in actual:
                del dfa.transition[state, character]

        return dfa

    def sort_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

        previous: Optional[Dict[State, int]] = None
        actual = dict()
        for state in dfa.states:
            if state in dfa.final:
                actual[state] = 1
            else:
                actual[state] = 0

        while previous != actual:
            previous = deepcopy(actual)
            patterns: Dict[Tuple[int, ...], Set[State]] = dict()
            actual = dict()

            for state in dfa.states:
                pattern = [previous[state]]
                for character in characters:
                    result = dfa.transition[state, character]
                    pattern.append(previous[result])

                if tuple(pattern) in patterns:
                    patterns[tuple(pattern)].add(state)
                else:
                    patterns[tuple(pattern)] = {state}

            i = 0
            for states in patterns.values():
                for state in states:
                    actual[state] = i
                i += 1

        new_transition: Dict[Tuple[State, Character], State] = {}
        dfa.states = set()
        dfa.final = set()
        for state in actual:
            new_state = State("new_" + str(actual[state]))
            dfa.states.add(new_state)

            for character in characters:
                if not (new_state, character) in new_transition:
                    originally = dfa.transition[state, character]
                    now = State("new_" + str(actual[originally]))
                    new_transition[new_state, character] = now

            if state == self.init:
                dfa.init = new_state
            if state in self.final:
                dfa.final.add(new_state)

        dfa.transition = new_transition
        return dfa

    def rename_state(self, rename: State, name: str):
        new_state = State(name)
        self.states.add(new_state)
        self.states.remove(rename)
        transition = deepcopy(self.transition)

        if rename == self.init:
            self.init = new_state
        if rename in self.final:
            self.final.add(new_state)
            self.final.remove(rename)

        for character in self.characters:
            if (rename, character) in self.transition:
                transition[new_state, character] = self.transition[rename, character]
                del transition[rename, character]

        for state, character in transition:
            if transition[state, character] == rename:
                del transition[state, character]
                transition[state, character] = new_state

        self.transition = transition

    def canonize(self) -> DFA:
        # TODO total?

        dfa = deepcopy(self)
        remain = deepcopy(self.states)
        characters = dfa.sort_characters()

        i = 0
        queue = [dfa.init]  # TODO deque
        while len(queue) > 0:
            actual = queue.pop(0)

            for character in characters:
                state = dfa.transition[actual, character]
                if state in remain:
                    queue.append(state)
                    remain.remove(state)

            dfa.rename_state(actual, str(i))
            i += 1
        return dfa

    def dfa_to_reg(self) -> REG:
        nonterminals: Set[Nonterminal] = set()
        terminals: Set[Terminal] = set()
        rules: Dict[Nonterminal, Set[Union[Terminal, Tuple[Terminal, Nonterminal]]]] = dict()

        for state in self.states:
            nonterminal = Nonterminal(state.name)
            nonterminals.add(nonterminal)

        for character in self.characters:
            terminal = Terminal(character.name)
            terminals.add(terminal)
        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:
            move = self.transition[state, character]
            if state == self.init:
                nonterminal2 = Nonterminal(move.name)
                terminal = Terminal(character.name)
                if init in rules:
                    rules[init].add((terminal, nonterminal2))
                else:
                    rules[init] = {(terminal, nonterminal2)}
                if move in self.final:
                    rules[init].add(terminal)
            else:
                nonterminal1 = Nonterminal(state.name)
                nonterminal2 = Nonterminal(move.name)
                terminal = Terminal(character.name)
                if nonterminal1 in rules:
                    rules[nonterminal1].add((terminal, nonterminal2))
                else:
                    rules[nonterminal1] = {(terminal, nonterminal2)}
                if move in self.final:
                    rules[nonterminal1].add((terminal))

        reg = REG(nonterminals, terminals, rules, init)
        if self.init in self.final:
            pass  # TODO eg.rules[init].add((\eps))

        return reg

    def is_empty(self) -> bool:
        reached: Deque[State] = deque()
        reached.append(self.init)  # TODO ask: I need to do it like this bc State is not iterable - shall it be?
        reachable: Set[State] = set()
        reachable.add(self.init)
        while len(reached) > 0:
            actual = reached.popleft()
            for character in self.characters:
                if self.transition[actual, character] is not None and \
                        self.transition[actual, character] not in reachable:
                    reached.append(self.transition[actual, character])
                    reachable.add(self.transition[actual, character])

        return len(reachable.intersection(self.final)) > 0

    # Ha!
    def is_universal(self):
        return self.complement().is_empty()

    def is_equivalent(self, dfa: DFA) -> bool:
        self.complement()
        dfa.complement()
        self.complement().is_empty()
        dfa.complement().is_empty()

        return self.intersection(self, dfa.complement()).is_empty() and \
               self.intersection(self.complement(), dfa).is_empty()


class NFA:
    Transition = Dict[Tuple[State, Character], Set[State]]
    type_var = TypeVar('type_var')

    def set_or_none(self, out: Optional[Set[type_var]]) -> Set[type_var]:
        return set() if out is None else out

    def dict_or_none(self, out: Optional[Transition]) -> Transition:
        return dict() if out is None else out

    def __init__(self, states: Set[State],
                 characters: Set[Character],
                 transition: Transition,
                 init: State,
                 final: Set[State]):

        self.states = self.set_or_none(states)
        self.characters = self.set_or_none(characters)
        self.transition = self.dict_or_none(transition)
        self.init = init
        self.final = self.set_or_none(final)

    def determinize(self) -> DFA:
        states: Set[FrozenSet[State]] = set()
        states.add(frozenset({self.init}))
        transition = {}
        final = set()
        done: Set[FrozenSet[State]] = set()

        while len(states.difference(done)) > 0:
            subset = (states.difference(done)).pop()  # arbitrary element from set
            if len(subset.intersection(self.final)) > 0:
                final.add(subset)
            for character in self.characters:
                new_subset: Set[State] = set()
                for state in subset:
                    if (state, character) in self.transition:
                        new_subset = new_subset.union(self.transition[state, character])

                states.add(frozenset(new_subset))
                transition[subset, character] = frozenset(new_subset)

            done.add(subset)

        new_states: Set[State] = set()
        new_transition: Dict[Tuple[State, Character], State] = dict()
        new_final: Set[State] = set()

        for state_set in states:
            new_states.add(self.unset(state_set))
        for state_set in final:
            new_final.add(self.unset(state_set))
        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

    def unset(self, states: FrozenSet[State]) -> State:
        return State('_'.join(set(map(lambda x: x.name, sorted(states, key=lambda x: x.name)))))

    # would be function for in epsilon NFA
    def eliminate_epsilon(self) -> NFA:
        nfa = NFA(self.states, self.characters, {}, self.init, set())

        # rozsirena prechodova funkce: D_eps(p) je podmnozina stavů X tz p tam je
        # a pokud q je v X a d(q, eps) = r, pak r je v X
        # a jde to pro mnoziny stavu (sjednoceni vsech)

        return nfa

    def epsilon_surroundings(self, state: State) -> Set[State]:
        pass