diff --git a/python-lab/Finite-Automatons-Computation/.gitignore b/python-lab/Finite-Automatons-Computation/.gitignore new file mode 100644 index 0000000..79d1a35 --- /dev/null +++ b/python-lab/Finite-Automatons-Computation/.gitignore @@ -0,0 +1,122 @@ + +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python + +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode + +### VisualStudioCode ### +.vscode/* + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.toptal.com/developers/gitignore/api \ No newline at end of file diff --git a/python-lab/Finite-Automatons-Computation/README.md b/python-lab/Finite-Automatons-Computation/README.md new file mode 100644 index 0000000..44e12fc --- /dev/null +++ b/python-lab/Finite-Automatons-Computation/README.md @@ -0,0 +1,22 @@ +# FINITE AUTOMATONS COMPUTATION + +A program that runs finite automatons and operate with them (reading, determinizing and minimizing). +I'm working on automaton algebra operations such as concatenation, union and intersection. ^^ + +To work with the program you need to create a json file with the automaton settings. + +The json must contains the following attributes: + + + states -> A map whose keys are the label of the state + and whose values are a list with each type + corresponding to the state. + The types are: 'I' for initial states, + 'F' for final states, + 'N' for non-initial and non-final states. + + alphabet -> A list with each symbol accepted by the automaton. + + transitions -> A map whose keys are the label of the state + and whose values are another map that represents + each transition for each symbol for the corresponding state. + + The transition map must have as key a valid symbol on the alphabet + and as value a list of the states the corresponding state goes with that symbol. diff --git a/python-lab/Finite-Automatons-Computation/data/example_dfa.json b/python-lab/Finite-Automatons-Computation/data/example_dfa.json new file mode 100644 index 0000000..1b8e321 --- /dev/null +++ b/python-lab/Finite-Automatons-Computation/data/example_dfa.json @@ -0,0 +1,25 @@ +{ + "states": { + "0": ["I", "F"], + "1": ["N"], + "2": ["F"] + }, + "alphabet": [ + "a", + "b" + ], + "transitions": { + "0": { + "b" : ["0"], + "a" : ["1"] + }, + "1": { + "a" : ["2"], + "b" : ["1"] + }, + "2": { + "a" : ["2"], + "b" : ["2"] + } + } +} \ No newline at end of file diff --git a/python-lab/Finite-Automatons-Computation/data/example_nfa.json b/python-lab/Finite-Automatons-Computation/data/example_nfa.json new file mode 100644 index 0000000..f424060 --- /dev/null +++ b/python-lab/Finite-Automatons-Computation/data/example_nfa.json @@ -0,0 +1,25 @@ +{ + "states": { + "0": ["I"], + "1": ["I"], + "2": ["F"] + }, + "alphabet": [ + "a", + "b" + ], + "transitions": { + "0": { + "b" : [], + "a" : ["1"] + }, + "1": { + "a" : ["2"], + "b" : [] + }, + "2": { + "a" : ["2"], + "b" : ["2", "0"] + } + } +} \ No newline at end of file diff --git a/python-lab/Finite-Automatons-Computation/src/automaton.py b/python-lab/Finite-Automatons-Computation/src/automaton.py new file mode 100644 index 0000000..6a27016 --- /dev/null +++ b/python-lab/Finite-Automatons-Computation/src/automaton.py @@ -0,0 +1,247 @@ +from collections.abc import Iterable +from queue import Queue +import random + +empty_set = u"\u00F8" +lmbda = u"\u03BB" + + +class FSM(): + def __init__(self, json: dict): + try: + states = json["states"] + alphabet = json["alphabet"] + transitions = json["transitions"] + except Exception: + raise ValueError( + "The json file must have the keys 'states', 'alphabet' and 'transitions'") + self.__assert_states(states) + self.__assert_alphabet(alphabet) + self.__assert_transitions(transitions, alphabet, states) + # Create all the Automaton Attributes + self.__states = set(states.keys()) + self.__initial_states = set( + filter(lambda state: 'I' in states[state], states)) + self.__final_states = set( + filter(lambda state: 'F' in states[state], states)) + self.__alphabet = set(alphabet) + self.__transitions = transitions + + # ----------------------------------------------------- + # ----------------- PUBLIC METHODS -------------------- + # ----------------------------------------------------- + + def determinize(self): + """ This method determinize the automata + using BFS algorithm with a Queue + """ + queue = Queue() + transitions = dict() + initial_state = tuple(self.__initial_states) + queue.put(initial_state) + print("Determinizing automaton... ") + while not queue.empty(): + curr_state = queue.get() + if curr_state not in transitions: + transitions[curr_state] = dict() + for symbol in self.__alphabet: + next_state = self.__get_next_state(curr_state, symbol) + if next_state == empty_set: + if empty_set not in transitions: + self.__add_empty_set(transitions) + transitions[curr_state][symbol] = [next_state] + queue.put(next_state) + self.__states = set(transitions.keys()) + self.__initial_states = set([initial_state]) + self.__final_states = set( + filter(lambda state: self.__is_final_state(state), transitions.keys())) + self.__transitions = transitions + print("Automaton determinized! Enter 'show' to see the changes\n") + + def read(self, word): + """ Represents word reading + """ + if not self.is_deterministic(): + print("\nThis automaton is non-deterministic") + self.determinize() + init_state = list(self.__initial_states)[0] + print("Reading word...") + self.__read(word, init_state) + + def minimize(self): + if not self.is_deterministic(): + print("\nThis automaton is non-deterministic") + self.determinize() + eq_classes = [self.__states - self.__final_states, self.__final_states] + self.__minimize(eq_classes, []) + + def show(self): + print(f" States => {self.__states}") + print(f" Initial states => {self.__initial_states}") + print(f" Final states => {self.__final_states}") + print("\n Transitions map => ") + for state, transition in self.__transitions.items(): + print(f" {state} =>") + for symbol, dest in transition.items(): + print(f" with {symbol} => {dest}") + + def is_deterministic(self): + if len(self.__initial_states) > 1: + return False + for transition in self.__transitions.values(): + for states in transition.values(): + if len(states) != 1: + return False + return True + + # ----------------------------------------------------- + # ----------------- PRIVATE METHODS ------------------- + # ----------------------------------------------------- + + # -------------------- READ METHODS ------------------- + + def __read(self, word, state): + if len(word) < 1 or word == lmbda: + if state in self.__final_states: + print("WORD ACCEPTED") + else: + print("WORD NOT ACCEPTED") + return + symbol = word[0] + if symbol not in self.__alphabet: + print(f"SYMBOL {symbol} NOT RECOGNIZED => WORD NOT ACCEPTED") + return + next_state = self.__transitions[state][symbol][0] + next_word = lmbda if len(word) <= 1 else word[1:] + print( + f"\t({state}, {word}) => ({next_state}, {next_word})") + self.__read(next_word, next_state) + + # ---------------- MINIMIZE METHODS ------------------ + + def __minimize(self, current, previous): + if current == previous: + print(f"Final equivalence classes: {current}") + self.__update_states(current) + print("Minimization completed!") + return + equivalence = [] + for s in current: + eq_class = s.copy() + while eq_class: + new_set = set() + r_state = eq_class.pop() + new_set.add(r_state) + for state in eq_class: + if self.__are_equivalent(r_state, state, current): + new_set.add(state) + eq_class -= new_set + equivalence.append(new_set) + self.__minimize(equivalence, current) + + def __update_states(self, eq_classes): + for c in eq_classes: + if len(c) > 1: + new_state = c.pop() + print(f"States {c} minimized to => {new_state}") + self.__states -= c + self.__final_states -= c + for state in c: + del self.__transitions[state] + for transition in self.__transitions.values(): + for dest in transition.values(): + if dest[0] in c: + dest[0] = new_state + + def __are_equivalent(self, state1, state2, groups): + """ Checks equivalence between two states + """ + for symbol in self.__alphabet: + s1 = self.__transitions[state1][symbol][0] + s2 = self.__transitions[state2][symbol][0] + for eq_class in groups: + if s1 in eq_class and s2 not in eq_class or s2 in eq_class and s1 not in eq_class: + return False + return True + + # --------------- DETERMINIZE METHODS ---------------- + + def __get_next_state(self, curr_state, symbol): + new_state = set() + for state in curr_state: + new_state.update(set(self.__transitions[state][symbol])) + if len(new_state) == 0: + return empty_set + return tuple(new_state) + + def __add_empty_set(self, transitions): + transitions[empty_set] = dict() + for letter in self.__alphabet: + transitions[empty_set][letter] = [empty_set] + + def __is_final_state(self, t): + for elem in t: + if elem in self.__final_states: + return True + return False + + # ------------ ASSERTS -------------- + def __assert_states(self, states): + if not isinstance(states, dict): + raise TypeError("Bad states type, has to be a map") + has_initial = False + has_final = False + for value in states.values(): + if not isinstance(value, list): + raise TypeError("Bad states value, has to be a list") + if 'I' in value: + has_initial = True + if 'F' in value: + has_final = True + if not has_initial: + raise ValueError("States map doesn't has an initial state") + if not has_final: + raise ValueError("States map does not has a final state") + + def __assert_alphabet(self, alphabet): + if not isinstance(alphabet, list): + raise TypeError("Bad alphabet type, has to be a list") + + def __assert_transitions(self, transitions, alphabet, states): + if not isinstance(transitions, dict): + raise TypeError("Bad transitions format, has to be a map") + # Check if each state is defined in transitions map + for s in states: + if s not in transitions: + raise ValueError(f"State {s} not defined on transitions table") + # Check the transitions map format + for state in transitions: + # Check if each state is in the states map + if state not in states: + raise ValueError( + f"State {state} in transitions map not defined in states map") + # Check the transition object type + transition = transitions[state] + if not isinstance(transition, dict): + raise TypeError( + f"Transaction value {transition} has to be a symbol-states map") + # Check if each state has defined a behaviour for each symbol in the alphabet + for symbol in alphabet: + if symbol not in transition.keys(): + raise ValueError( + f"Symbol {symbol} behaviour not defined in state {state}, each symbol has to be defined even if its value is an empty list.") + # Check the transition map content + for symbol, dest in transition.items(): + # Check if each symbol is in the alphabet + if symbol not in alphabet: + raise ValueError( + f"Symbol {symbol} in state {state} transition is not in the alphabet") + # Check the destination states object type + if not isinstance(dest, Iterable): + raise TypeError( + f"Value {dest} in transitions table has to be an Iterable") + # Check if each state in destination list is in the states map + for s in dest: + if s not in states: + raise ValueError( + f"State {s} in {dest} not in states map") diff --git a/python-lab/Finite-Automatons-Computation/src/main.py b/python-lab/Finite-Automatons-Computation/src/main.py new file mode 100644 index 0000000..8f4c5c1 --- /dev/null +++ b/python-lab/Finite-Automatons-Computation/src/main.py @@ -0,0 +1,60 @@ +from automaton import FSM +from os import system, name +import json + + +def clear(): + # for windows + if name == 'nt': + system('cls') + # for mac and linux(here, os.name is 'posix') + else: + system('clear') + + +def show_options(): + print("D => Determinize automat") + print("M => Minimize automat") + print("read => Read a word") + print("show => Show automat") + print("clear => Clear screen") + print("help => Show commands") + print("quit => Close program") + + +if __name__ == '__main__': + try: + filename = input("Enter the automaton json filepath: ") + fp = open(filename, 'r') + automaton_json = json.load(fp) + automat = FSM(automaton_json) + show_options() + while True: + option = input(">> ").lower() + if option == 'd': + if automat.is_deterministic(): + print("This automaton is already determinized") + continue + automat.determinize() + elif option == 'm': + automat.minimize() + elif option == 'read': + word = input("Enter the word you want to read: ") + automat.read(word) + elif option == 'show': + automat.show() + elif option == 'clear': + clear() + elif option == 'help': + show_options() + elif option == 'quit': + print("Bye!") + break + else: + print("Command not recognized") + except OSError as err: + print("OSError:", err) + except ValueError as err: + print("ValueError:", err) + except TypeError as err: + print("TypeError:", err)