Скачиваний:
0
Добавлен:
15.04.2026
Размер:
46.68 Кб
Скачать
import re
import sys
import collections
from typing import Any, Union, Dict, Tuple, List, Set


def to_flatten_tuple(any_arg: Any) -> Tuple[str]:
    """
    Преобразует аргумент any_arg в кортеж (для использования в качестве ключа сортировки состояний и пр.)
    42 -> ('42',)    |    (42, 24) -> ('42', '24')    |    [4, ['2', '4'], 2] -> ('4', '2', '4', '2')
    ([6, [[5]]], ([4], 3), 2, ['one', ['0']]) -> ('6', '5', '4', '3', '2', 'one', '0')
    :param any_arg: число / строка / кортеж / список / множество
    :return: кортеж
    """
    return tuple(re.findall(pattern=r'\w+', string=str(any_arg)))


def fsm_to_deterministic(alphabet: Union[str, Tuple[str], List[str], Set[str]],
                         fsm: Dict[Union[str, Tuple[str]],
                                   Dict[str, Union[str, Tuple[str], Set[Union[str, Tuple[str]]]]]],
                         initial_states: Union[str, Tuple[str],
                                               List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
                         final_states: Union[str, Tuple[str],
                                             List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
                         is_gen_new_states: bool = True) -> (Dict[Union[str, Tuple[str]],
                                                                  Dict[str, Union[str, Tuple[str]]]],
                                                             Set[Union[str, Tuple[str]]],
                                                             Set[Union[str, Tuple[str]]]):
    """
    Преобразует входной конечный автомат fsm в детерминированный (алгоритм Томпсона)
    Множество {,}:: 'a' : {1, 2, 3} -- это переходы в состояния 1, 2, 3 с меткой 'a' (недетерминированность)
    Кортеж (,):: 'a' : (1, 2, 3) -- это переход в одно состояние (1, 2, 3) с меткой 'a' (детерминированность)
    Примечание: эта функция не выясняет, насколько корректны входные параметры
    :param alphabet: алфавит языка
    :param fsm: конечный автомат, например, {'1': {'a': {'1', '2'}}, '2': {'a': '1'}}
    :param initial_states: множество начальных состояний КА (элементы множества -- строки / кортежи)
                           либо одно начальное состояние (строка / кортеж)
    :param final_states: множество заключительных состояний КА (элементы множества -- строки / кортежи)
                         либо одно заключительное состояние (строка / кортеж)
    :param is_gen_new_states: выдавать (1, 2) и т.п. (False) либо создавать новые состояния (True): (1, 2) -> 3
    :return: (ДКА, множество начальных состояний, множество заключительных состояний)
    """
    # если FSM пуст или множество начальных состояний пусто
    if not fsm or not initial_states and not isinstance(initial_states, (str, tuple)):
        return dict(), set(), set()
    
    # преобразуем в множества
    initial_states = set(initial_states) if isinstance(initial_states, (set, list)) else {initial_states}
    final_states = set(final_states) if isinstance(final_states, (set, list)) else {final_states}
    new_fsm = dict()  # новый конечный автомат
    new_initial_states = initial_states.copy()  # множество новых начальных состояний КА
    new_final_states = final_states.copy()  # множество новых заключительных состояний КА
    seen = set()  # множество уже обработанных состояний
    q = collections.deque(initial_states)  # очередь состояний
    while q:  # пока очередь не пуста
        state = q.popleft()  # достаем первый элемент из очереди
        if state in seen:  # если состояние уже было обработано
            continue  # переходим к следующему состоянию в очереди
        seen.add(state)  # или добавляем текущее состояние в список обработанных и обрабатываем его
        # если состояние -- это строка или единичный кортеж
        if isinstance(state, str) or isinstance(state, tuple) and len(state) <= 1:
            # преобразуем в строку
            if len(state) == 1:
                if state in new_final_states:
                    new_final_states.add(state[0])
                state = state[0]
            new_fsm.setdefault(state, dict())  # добавляем соответствие
            # просматриваем все переходы из текущего состояния
            for alpha, alpha_states in fsm.get(state, dict()).items():
                # если есть несколько переходов с одной меткой (недетерминированность)
                if isinstance(alpha_states, set) and len(alpha_states) > 1:
                    # удаляем из множества отсутствующие в FSM состояния
                    alpha_states = alpha_states.intersection(fsm)
                    if not alpha_states:  # если все состояния отсутствовали
                        continue  # пропускаем переход по метке alpha
                    # формируем состояние в виде отсортированного кортежа состояний
                    sorted_alpha_states = tuple(sorted(alpha_states, key=to_flatten_tuple))
                    # добавляем переход по метке alpha из state в новое состояние
                    new_fsm[state][alpha] = sorted_alpha_states
                    # добавляем в очередь новое состояние
                    q.append(sorted_alpha_states)
                else:  # если есть ровно один переход или их нет совсем (детерминированность)
                    if isinstance(alpha_states, (set, tuple)):
                        if not alpha_states:  # если нет переходов
                            continue  # пропускаем переход по метке alpha
                        # если ровно один переход
                        if isinstance(alpha_states, set) or alpha_states not in fsm and len(alpha_states) == 1:
                            alpha_states = next(iter(alpha_states))  # преобразуем в сам элемент
                    # если состояние или его составляющие отсутствуют
                    if alpha_states not in fsm and (isinstance(alpha_states, str)
                                                    or all(x not in fsm for x in alpha_states)):
                        continue  # пропускаем переход по метке alpha
                    # добавляем переход по метке alpha из state в новое состояние
                    new_fsm[state][alpha] = alpha_states
                    # добавляем в очередь новое состояние
                    q.append(alpha_states)
        elif isinstance(state, tuple):  # если состояние -- это кортеж из двух или более элементов
            sorted_alpha_states = tuple(sorted(state, key=to_flatten_tuple))
            # если хотя бы одно состояние в кортеже является заключительным
            if not new_final_states.isdisjoint(sorted_alpha_states):
                # добавляем состояние sorted_alpha_states в множество новых заключительных состояний
                new_final_states.add(sorted_alpha_states)
            d = dict()  # словарь переходов для нового состояния
            for alpha in alphabet:  # просматриваем все возможные переходы по меткам
                states_for_alpha = set()  # множество состояний для текущего перехода с меткой alpha
                # для каждого состояния в кортеже, включая сам кортеж
                for alpha_state in [*sorted_alpha_states, sorted_alpha_states]:
                    if alpha_state in fsm and alpha in fsm[alpha_state]:  # определяем, есть ли состояние в КА
                        elem = fsm[alpha_state][alpha]
                        if isinstance(elem, str):  # в случае строки
                            if elem in fsm:  # если есть целевое состояние в FSM
                                states_for_alpha.add(elem)  # добавляем как элемент
                        elif isinstance(elem, tuple):  # в случае кортежа
                            if elem in fsm:  # если есть целевое состояние в FSM
                                states_for_alpha.add(elem)  # добавляем как элемент
                            else:  # если есть составляющие
                                states_for_alpha.update(x for x in elem if x in fsm)  # дополняем
                        else:  # в случае множества
                            states_for_alpha.update(elem.intersection(fsm))  # дополняем
                # сортируем множество состояний для текущего перехода с меткой alpha
                sorted_states_for_alpha = tuple(sorted(states_for_alpha, key=to_flatten_tuple))
                if sorted_states_for_alpha:  # если множество не пусто
                    # добавляем переход по метке alpha
                    d[alpha] = sorted_states_for_alpha if len(sorted_states_for_alpha) > 1 \
                        else next(iter(sorted_states_for_alpha))
            new_fsm[sorted_alpha_states] = d  # добавляем переходы
            # каждый добавленный переход нужно будет обработать отдельно (добавляем переходы в очередь q)
            for state in d.values():
                q.append(state)
    if is_gen_new_states:  # если нужно создавать новые состояния: (1, 2) -> 4, (1, 2, 3) -> 5...
        # сортируем множество переходов конечного автомата
        sorted_new_states = sorted(new_fsm.items(), key=to_flatten_tuple)
        new_fsm = dict()  # новый КА
        transformation_dict = dict()  # словарь преобразований: (1, 2) -> 5 и т. п.
        # предварительно обрабатываем те состояния, которые представлены в виде строк
        for state, alphas in sorted_new_states:
            # если состояния ещё не было в словаре и оно является строкой
            if state not in transformation_dict and isinstance(state, str):
                transformation_dict[state] = state  # добавляем
        # обрабатываем те состояния, которые представлены в виде кортежей
        for state, alphas in sorted_new_states:
            if state not in transformation_dict:  # если состояния ещё не было в словаре
                # находим незанятый строковый номер состояния (лексикографически минимальное состояние)
                min_key = min(fsm.keys(), key=to_flatten_tuple) if fsm else 1
                i = int(min_key) if isinstance(min_key, str) and min_key.isdigit() else 1
                while str(i) in transformation_dict:
                    i += 1
                si = str(i)
                transformation_dict[si] = si  # добавляем номер в словарь, чтобы снова на него не выйти
                transformation_dict[state] = si  # добавляем соответствие состояния номеру в словарь
                if si in new_final_states:  # если номер нового состояния совпал со старым в
                    new_final_states.remove(si)  # множестве заключительных состояний, то убираем его оттуда
                if state in new_final_states:  # заменяем старое заключительное состояние на новое
                    new_final_states.remove(state)
                    new_final_states.add(si)
        # обрабатываем переходы по состояниям
        for state, alphas in sorted_new_states:
            d = dict()  # словарь переходов
            for alpha, alpha_states in alphas.items():  # просматриваем все переходы из состояния state
                # если найдено соответствие, выполняем замену
                d[alpha] = transformation_dict.get(alpha_states, alpha_states)
            new_fsm[transformation_dict[state]] = d  # добавляем словарь переходов по номеру состояния
    # удаление отсутствующих состояний в множествах начальных и заключительных состояний
    for state in new_initial_states.union(new_final_states):
        if state not in new_fsm:
            if state in new_initial_states:
                new_initial_states.remove(state)
            if state in new_final_states:
                new_final_states.remove(state)
    return new_fsm, new_initial_states, new_final_states


def determine_alphabet(fsm: Dict[Any, Dict[Union[str, Tuple[str]], Any]]) -> Set[str]:
    """
    Возвращает алфавит языка, распознаваемого конечным автоматом fsm
    :param fsm: конечный автомат, например, {'1': {'a': {'1', '2'}}, '2': {'a': '1'}}
    :return: алфавит языка
    """
    alphabet = set()
    for alphas in fsm.values():
        alphabet.update(alphas.keys())
    return alphabet


def fsm_eliminate_epsilon_transitions(fsm: Dict[Union[str, Tuple[str]],
                                                Dict[str, Union[str, Tuple[str], Set[Union[str, Tuple[str]]]]]],
                                      initial_states: Union[str, Tuple[str],
                                                            List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
                                      final_states: Union[str, Tuple[str],
                                                          List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
                                      epsilon: str = 'εϵΕ'
                                      ) -> (Dict[Union[str, Tuple[str]],
                                                 Dict[str, Union[str, Tuple[str], Set[Union[str, Tuple[str]]]]]],
                                            Union[str, Tuple[str],
                                                  List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
                                            Union[str, Tuple[str],
                                                  List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]]):
    """
    Устраняет epsilon переходы конечного автомата
    :param fsm: конечный автомат, например, {'1': {'a': {'1', '2'}}, '2': {'a': '1'}}
    :param initial_states: множество начальных состояний КА (элементы множества -- строки / кортежи)
                           либо одно начальное состояние (строка / кортеж)
    :param final_states: множество заключительных состояний КА (элементы множества -- строки / кортежи)
                         либо одно заключительное состояние (строка / кортеж)
    :param epsilon: символы epsilon переходов ('', 'ε', 'ϵ', 'Ε')
    :return: (КА без epsilon переходов, множество начальных состояний, множество заключительных состояний)
    """
    # если FSM пуст или множество начальных состояний пусто
    if not fsm or not initial_states and not isinstance(initial_states, (str, tuple)):
        return dict(), set(), set()
    
    # преобразуем в множества
    new_initial_states = set(initial_states) if isinstance(initial_states, (set, list)) else {initial_states}
    new_final_states = set(final_states) if isinstance(final_states, (set, list)) else {final_states}
    # составляем новый конечный автомат
    new_fsm = dict()
    # состояние -> множество состояний, в которые можно попасть из исходного по epsilon переходу
    from_state_to_state_by_epsilon = dict()
    for state, alphas in fsm.items():  # для каждого состояния
        new_fsm.setdefault(state, dict())  # добавляем в новый КА
        from_state_to_state_by_epsilon.setdefault(state, set())  # добавляем в словарь соответствий
        for alpha, alpha_states in alphas.items():  # для каждой метки
            if alpha in epsilon:  # если метка epsilon, то добавляем новое соответствие
                all_states = {state}
                if isinstance(alpha_states, set):
                    all_states.update(x for x in alpha_states if x in fsm)
                elif alpha_states in fsm:
                    all_states.add(alpha_states)
                from_state_to_state_by_epsilon[state].update(all_states)
            else:  # если метка не epsilon, то добавляем то, что было
                new_fsm[state].setdefault(alpha, set())
                if isinstance(alpha_states, set):
                    new_fsm[state][alpha].update(x for x in alpha_states if x in fsm)
                elif alpha_states in fsm:
                    new_fsm[state][alpha].add(alpha_states)
    b = True
    while b:  # пока есть изменения
        b = False
        # для каждого состояния, из которого есть переход по epsilon
        for state, next_states in from_state_to_state_by_epsilon.items():
            states_by_epsilon = from_state_to_state_by_epsilon.get(state, set())
            for next_state in next_states.copy():  # дополняем множество состояний по каждому элементу этого множества
                previous_length = len(states_by_epsilon)
                states_by_epsilon.update(from_state_to_state_by_epsilon.get(next_state, set()))
                b |= (previous_length != len(states_by_epsilon))  # было ли изменение
    for state, next_states in from_state_to_state_by_epsilon.items():  # для каждого состояния в словаре
        for next_state in next_states:  # добавляем переходы из state во входящие из next_state
            for alpha, alpha_states in new_fsm[next_state].items():  # alpha != epsilon
                new_fsm[state].setdefault(alpha, set()).update(alpha_states)
        if state in new_initial_states:  # дополняем множество начальных состояний
            new_initial_states.update(next_states)
        if not new_final_states.isdisjoint(next_states):  # дополняем множество заключительных состояний
            new_final_states.add(state)
    return new_fsm, new_initial_states, new_final_states


def fsm_convert_and_minimize(fsm: Dict[Union[str, Tuple[str]],
                                       Dict[str, Union[str, Tuple[str], Set[Union[str, Tuple[str]]]]]],
                             initial_states: Union[str, Tuple[str],
                                                   List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
                             final_states: Union[str, Tuple[str],
                                                 List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]]
                             ) -> (Dict[str, Dict[str, str]], Set[str], Set[str]):
    """
    Преобразует входной конечный автомат fsm в минимальный детерминированный
    (алгоритм Томпсона преобразования КА в ДКА и алгоритм Хопкрофта минимизации ДКА)
    :param fsm: конечный автомат, например, {'1': {'a': {'1', '2'}}, '2': {'a': '1'}}
    :param initial_states: множество начальных состояний КА (элементы множества -- строки / кортежи)
                           либо одно начальное состояние (строка / кортеж)
    :param final_states: множество заключительных состояний КА (элементы множества -- строки / кортежи)
                         либо одно заключительное состояние (строка / кортеж)
    :return: (минимальный ДКА, множество начальных состояний, множество заключительных состояний)
    """
    # если FSM пуст или множество начальных состояний пусто
    if not fsm or not initial_states and not isinstance(initial_states, (str, tuple)):
        return dict(), set(), set()

    # устраняем epsilon переходы
    fsm, initial_states, final_states = fsm_eliminate_epsilon_transitions(fsm, initial_states, final_states)
    # определяем алфавит языка
    alphabet = determine_alphabet(fsm)

    # добавляем фиктивное состояние
    fictive_state = 'fictive_state'
    while fictive_state in fsm:
        fictive_state += "'"
    # добавляем фиктивные переходы из фиктивного состояния в начальные
    fictive_alpha = 'fictive_alpha'
    while fictive_alpha in alphabet:
        fictive_alpha += "'"
    fsm[fictive_state] = {fictive_alpha: initial_states}
    initial_states = {fictive_state}

    # преобразуем в ДКА
    fsm, initial_states, final_states = fsm_to_deterministic(alphabet, fsm, initial_states, final_states, True)
    # строим обратный конечный автомат
    inversed_fsm = dict()
    for state, alphas in fsm.items():
        inversed_fsm.setdefault(state, dict())
        for alpha, alpha_states in alphas.items():
            if isinstance(alpha_states, (str, tuple)):
                alpha_states = {alpha_states}
            for alpha_state in alpha_states:
                inversed_fsm.setdefault(alpha_state, dict()).setdefault(alpha, set()).add(state)
    
    # множество из одного фиктивного состояния
    fictive_state_class = frozenset({fictive_state})
    # множество заключительных состояний
    only_final_states = frozenset(final_states)
    # множество состояний, исключая заключительные и фиктивное
    without_final_states = frozenset(fsm).difference(final_states).difference(fictive_state_class)
    classes = {fictive_state_class, only_final_states, without_final_states}  # классы эквивалентности
    q = classes.copy()  # очередь из классов
    while q:  # пока очередь не пуста
        some_class = q.pop()  # достаем первый элемент из очереди
        for alpha in alphabet:  # для каждой буквы языка
            path_sources = set()  # состояния, из которых можно попасть в состояния из some_class
            for state in some_class:
                if state in inversed_fsm and alpha in inversed_fsm[state]:
                    path_sources.update(inversed_fsm[state][alpha])  # inversed_fsm[state][alpha] -- set
            for class_to_split in classes.copy():  # для каждого класса
                # находим общие состояния path_sources и класса
                p_with_class = class_to_split.intersection(path_sources)
                if p_with_class:  # если есть общее
                    # находим состояния в классе, отсутствующие в path_sources
                    p_without_class = class_to_split.difference(path_sources)
                    if p_without_class:  # если есть разница
                        classes.remove(class_to_split)  # разбиваем класс на два новых
                        classes.add(p_with_class)
                        classes.add(p_without_class)
                        if class_to_split in q:  # если класс есть в очереди
                            q.remove(class_to_split)  # также разбиваем его
                            q.add(p_with_class)
                            q.add(p_without_class)
                        else:  # иначе добавляем наименьший
                            if len(p_with_class) <= len(p_without_class):
                                q.add(p_with_class)
                            else:
                                q.add(p_without_class)
    # формируем new_states, new_initial_states и new_final_states
    new_states = dict()  # словарь соответствий старых состояний новым
    new_initial_states = set()  # множество новых начальных состояний
    new_final_states = set()  # множество новых заключительных состояний
    n_class = -1  # номер класса (не изменять)
    classes = {some_class for some_class in classes if some_class}  # рассматриваем не пустые классы
    # сначала рассматриваем классы с начальными состояниями
    new_non_initial_classes = set()
    for some_class in classes:  # для каждого класса
        if not initial_states.isdisjoint(some_class):  # если есть хотя бы одно начальное состояние
            n_class += 1
            n_class_str = str(n_class)
            new_initial_states.add(n_class_str)
            for state in some_class:
                new_states[state] = n_class_str
            # начальное состояние может быть заключительным -- нужно учесть
            if not final_states.isdisjoint(some_class):  # если есть хотя бы одно заключительное состояние
                new_final_states.add(n_class_str)
        else:
            new_non_initial_classes.add(some_class)
    # затем рассматриваем классы с не заключительными состояниями
    new_final_classes = set()
    for some_class in new_non_initial_classes:
        if final_states.isdisjoint(some_class):  # если заключительные состояния отсутствуют
            n_class += 1
            n_class_str = str(n_class)
            for state in some_class:
                new_states[state] = n_class_str
        else:
            new_final_classes.add(some_class)
    # далее рассматриваем классы с заключительными (и не начальными) состояниями
    for some_class in new_final_classes:
        n_class += 1
        n_class_str = str(n_class)
        new_final_states.add(n_class_str)
        for state in some_class:
            new_states[state] = n_class_str
    # формируем минимальный ДКА по имеющимся классам эквивалентности
    new_fsm = dict()
    for some_class in classes:
        new_state = dict()
        for state in some_class:
            for alpha, alpha_states in fsm.get(state, dict()).items():
                if alpha_states in new_states:
                    new_state[alpha] = new_states[alpha_states]  # alpha_states -- str
        any_state = next(iter(some_class))  # пустые классы ранее были удалены
        new_fsm[new_states[any_state]] = new_state
    # удаляем фиктивное состояние и фиктивные переходы из него
    if '0' in new_fsm:
        new_initial_states = new_fsm['0'].pop(fictive_alpha)
        new_initial_states = set(new_initial_states) if isinstance(new_initial_states, (set, list)) else {new_initial_states}
        new_fsm.pop('0')
    if '0' in new_initial_states:
        new_initial_states.remove('0')
    return new_fsm, new_initial_states, new_final_states


def fsm_plot(filename: str,
             fsm: Dict[Union[str, Tuple[str]],
                       Dict[str, Union[str, Tuple[str], Set[Union[str, Tuple[str]]]]]],
             initial_states: Union[str, Tuple[str],
                                   List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]],
             final_states: Union[str, Tuple[str],
                                 List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]]) -> None:
    """
    Генерирует диаграмму конечного автомата и сохраняет результат в файл <filename>.png
    :param filename: имя файла изображения без расширения
    :param fsm: конечный автомат, например, {'1': {'a': {'1', '2'}}, '2': {'a': '1'}}
    :param initial_states: множество начальных состояний КА (элементы множества -- строки / кортежи)
                           либо одно начальное состояние (строка / кортеж)
    :param final_states: множество заключительных состояний КА (элементы множества -- строки / кортежи)
                         либо одно заключительное состояние (строка / кортеж)
    :return: None
    """
    import graphviz
    G = graphviz.Digraph()
    G.attr('graph', rankdir="TB")  # направление -- сверху вниз
    G.node('start', None, {'shape': 'point'})  # начальная точка, из которой идут дуги в начальные состояния
    initial_states = set(initial_states) if isinstance(initial_states, (set, list)) else {initial_states}
    final_states = set(final_states) if isinstance(final_states, (set, list)) else {final_states}
    for state in fsm:
        # если состояние является заключительным, то двойной круг, иначе обычный круг
        G.attr('node', shape='doublecircle' if state in final_states else 'circle')
        G.node(','.join(state) if isinstance(state, tuple) else state)
    G.attr('node', shape='point')
    for state, alphas in fsm.items():
        if state in initial_states:
            G.edge(tail_name='start', head_name=state, label='')  # из начальной точки в начальное состояние
        # формируем словарь для соответствий "целевое состояние" -> "метки дуг, идущих в целевое состояние"
        arcs = dict()
        for alpha, alpha_states in alphas.items():  # просматриваем все метки переходов из state
            if isinstance(alpha_states, (str, tuple)):  # если alpha_states -- это одно состояние
                alpha_states = {alpha_states}  # заводим множество с одним состоянием
            for alpha_state in alpha_states:  # просматриваем множество
                if state != alpha_state:  # петли обрабатываем в конце
                    arcs.setdefault(alpha_state, set())  # по умолчанию -- пустое множество
                    arcs[alpha_state].add(alpha)  # добавляем метку в словарь
        # обрабатываем добавленные в arcs состояния
        for alpha_state, alpha_state_alphas in arcs.items():
            G.edge(tail_name=','.join(state) if isinstance(state, tuple) else state,
                   head_name=','.join(alpha_state) if isinstance(alpha_state, tuple) else alpha_state,
                   label=','.join(sorted(alpha_state_alphas, key=to_flatten_tuple)))  # рисуем дугу
        # обрабатываем петли (чтобы не было много дуг: делаем одну с нужными метками через запятую)
        state_alpha_state = set()  # множество всех меток петель
        for alpha, alpha_states in alphas.items():
            if isinstance(alpha_states, (str, tuple)):  # если alpha_states -- это одно состояние
                alpha_states = {alpha_states}  # заводим множество с одним состоянием
            for alpha_state in alpha_states:  # просматриваем множество
                if state == alpha_state:  # если петля
                    state_alpha_state.add(alpha)  # добавляем метку
                    break  # искать дальше по той же метке смысла нет -- метку уже добавили
        if state_alpha_state:  # если петли есть
            G.edge(tail_name=','.join(state) if isinstance(state, tuple) else state,
                   head_name=','.join(state) if isinstance(state, tuple) else state,
                   label=','.join(sorted(state_alpha_state, key=to_flatten_tuple)))  # рисуем петлю
    G.render(filename, format='png', cleanup=True)


def fsm_int_states_to_str(fsm: Dict[Any, Dict[Any, Any]]) -> Dict[str, Dict[str, Union[str, Set[str]]]]:
    """
    Преобразует числовые состояния КА в строковые: 1 -> '1', 2 -> '2' и т. д.
    :param fsm: конечный автомат
    :return: конечный автомат со строковыми состояниями
    """
    return {str(state): {str(alpha): set((str(x) for x in sorted(alpha_states)))
                                     if isinstance(alpha_states, (set, tuple, list)) and len(alpha_states) != 1
                                     else str(next(iter(alpha_states)))
                                     if isinstance(alpha_states, (set, tuple, list)) and len(alpha_states) == 1
                                     else str(alpha_states)
                         for alpha, alpha_states in alphas.items()}
            for state, alphas in fsm.items()}


def fsm_startswith(alphabet: str, prefix: str) -> (Dict[str, Dict[str, str]], Set[str], Set[str]):
    """
    Формирует конечный автомат для проверки принадлежности слова языку над алфавитом alphabet,
    такому что все слова языка начинаются на prefix
    :param alphabet: алфавит языка
    :param prefix: первые буквы всех слов языка
    :return: (минимальный детерминированный конечный автомат, множество начальных состояний,
              множество заключительных состояний)
    """
    if not set(prefix).issubset(alphabet):  # если в prefix есть символ не из алфавита alphabet
        raise Exception('input prefix contains invalid symbol')
    initial_state = 1  # начальное состояние
    last_state = initial_state + len(prefix)  # конечное состояние
    fsm = dict()  # конечный автомат
    fsm[last_state] = {x: {last_state, } for x in alphabet}  # для последнего -- все переходы указывают на него
    for i, x in enumerate(prefix, initial_state):  # обработка остальных состояний
        fsm[i] = {x: {i + 1, }}  # переход в следующее состояние из текущего
    fsm = fsm_int_states_to_str(fsm)  # преобразование числовых состояний в строковые
    # КА получается детерминированным и минимальным
    return fsm, {str(initial_state)}, {str(last_state)}


def fsm_endswith(alphabet: str, suffix: str) -> (Dict[str, Dict[str, str]], Set[str], Set[str]):
    """
    Формирует конечный автомат для проверки принадлежности слова языку над алфавитом alphabet,
    такому что все слова языка заканчиваются на suffix
    :param alphabet: алфавит языка
    :param suffix: последние буквы всех слов языка
    :return: (минимальный детерминированный конечный автомат, множество начальных состояний,
              множество заключительных состояний)
    """
    if not set(suffix).issubset(alphabet):  # если в prefix есть символ не из алфавита alphabet
        raise Exception('input suffix contains invalid symbol')
    initial_state = 1  # начальное состояние
    last_state = initial_state + len(suffix)  # конечное состояние
    fsm = dict()  # конечный автомат
    fsm[initial_state] = {x: {initial_state, } for x in
                          alphabet}  # для первого -- все переходы указывают на него самого
    if suffix:  # если имеется суффикс
        fsm[initial_state][suffix[0]].add(initial_state + 1)  # добавляем переход из нач. состояния во второе
        for i, x in enumerate(suffix[1:], initial_state + 1):  # обработка остальных состояний
            fsm[i] = {x: {i + 1, }}  # переход в следующее состояние из текущего
        fsm[last_state] = {}  # из последнего состояния нет перехода
    fsm = fsm_int_states_to_str(fsm)  # преобразование числовых состояний в строковые
    # в этой функции автомат получается недетерминированным в случае len(suffix) > 0,
    # поэтому его нужно преобразовать в детерминированный
    return fsm_convert_and_minimize(fsm, str(initial_state), str(last_state))


def fsm_check(string: str,
              fsm: Dict[str, Dict[str, str]],
              initial_state: Union[str, Tuple[str]],
              final_states: Union[str, Tuple[str],
                                  List[Union[str, Tuple[str]]], Set[Union[str, Tuple[str]]]]) -> bool:
    """
    Определяет, принадлежит ли слово string языку, который распознает конечный автомат fsm
        с начальным состоянием initial_state и заключительными состояниями final_states
    :param string: слово
    :param fsm: детерминированный конечный автомат
    :param initial_state: начальное состояние
    :param final_states: множество заключительных состояний КА (элементы множества -- строки / кортежи)
                         либо одно заключительное состояние (строка / кортеж)
    :return: True, если слово string принадлежит языку, и False, если не принадлежит
    """
    final_states = set(final_states) if isinstance(final_states, (set, list)) else {final_states}
    state = initial_state  # начинаем с начального состояния
    for x in string:  # для каждой буквы слова
        transition = fsm.get(state)  # находим возможные переходы из текущего состояния
        if transition is None:  # если переходов у состояния нет
            return False  # значит не принадлежит
        state = transition.get(x)  # пробуем перейти по метке x в следующее состояние
        if state is None:  # если метка перехода не найдена
            return False  # значит не принадлежит
    return state in final_states  # слово принадлежит языку, если последнее состояние -- заключительное


def main():
    def print_fsm(fsm: Any, final_states: Any) -> None:
        import pprint
        print('Детерминированный конечный автомат:')
        pprint.pprint(fsm)
        print(f'Заключительные состояния: {final_states}')
    
    def print_examples(fsm: Dict[str, Dict[str, str]],
                       initial_state: Union[str, Tuple[str]],
                       final_states: Union[str, Tuple[str],
                                           List[Union[str, Tuple[str]]],
                                           Set[Union[str, Tuple[str]]]]) -> None:
        examples = ['', '0', '1', '00', '01', '10', '11', '0001', '1000', '0110', '1100', '0011', '001100', '110011']
        for example in examples:
            result = fsm_check(string=example, fsm=fsm, initial_state=initial_state, final_states=final_states)
            print(f"'{example}': {result}" + (', ' if example != examples[-1] else ''), end='')
        print(end='\n\n')
    
    def print_fsm_and_examples(alphabet: str, ix: str, task_type: str) -> None:
        if task_type == 'startswith':
            print(f'Слова языка начинаются на {ix}')
            fsm, initial_states, final_states = fsm_startswith(alphabet=alphabet, prefix=ix)
        elif task_type == 'endswith':
            print(f'Слова языка заканчиваются на {ix}')
            fsm, initial_states, final_states = fsm_endswith(alphabet=alphabet, suffix=ix)
        else:
            print('Task is missing')
            return None
        print_fsm(fsm, final_states)
        print_examples(fsm, next(iter(initial_states)), final_states)
        try:
            fsm_plot(f'{task_type}_{ix}', fsm, initial_states, final_states)
        except Exception as e:
            print(e, file=sys.stderr)
            return None
    
    alphabet = '01'
    print(f'Алфавит: {alphabet}')
    # Вариант 1
    print_fsm_and_examples(alphabet, '00', 'endswith')
    # Вариант 2
    print_fsm_and_examples(alphabet, '00', 'startswith')
    # Вариант 3
    print_fsm_and_examples(alphabet, '11', 'endswith')
    # Вариант 4
    print_fsm_and_examples(alphabet, '11', 'startswith')
    # Вариант 5
    print_fsm_and_examples(alphabet, '10', 'endswith')
    # Вариант 6
    print_fsm_and_examples(alphabet, '10', 'startswith')
    # Дополнительный вариант 7
    print_fsm_and_examples(alphabet, '110011', 'endswith')
    # Дополнительный вариант 8
    print_fsm_and_examples(alphabet, '110011', 'startswith')
    # Дополнительный вариант 9
    print_fsm_and_examples(alphabet, '001100', 'endswith')
    # Дополнительный вариант 10
    print_fsm_and_examples(alphabet, '001100', 'startswith')


if __name__ == "__main__":
    main()
Соседние файлы в папке Programs