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()