МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ,
СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное бюджетное образовательное учреждение высшего образования «Санкт-Петербургский государственный университет
телекоммуникаций им. проф. М. А. Бонч-Бруевича»
(СПбГУТ)
Факультет инфокоммуникационных сетей и систем
Кафедра программной инженерии и вычислительной техники
Лабораторная работа №3
по дисциплине «Теория алгоритмов и формальных языков»
студенты гр. ИКПИ-93 |
_______________ |
Смирнов Д.А. Тюришев М.А. Цыганков М.А. Козлов Н.С. |
|
|
|
ассистент каф. ПИиВТ |
_______________ |
Марочкина А. В. |
Санкт-Петербург
2022
Постановка задачи
Необходимо написать программу, которая создает вендинговый автомат Мура/Мили.
Автомат принимает на вход монеты (купюры) заданных в варианте номиналов, и выдает товар при наличии достаточного количества внесенных денег, а также при необходимости выдает сдачу.
Входные сигналы должны соответствовать номиналам купюр/монет.
Состояния абстрактного автомата должны соответствовать накопленной в автомате сумме.
Должны быть следующие выходные состояния:
Не делать ничего;
Выдать товар без сдачи;
Выдать товар и сдачу N рублей.
Ход работы
Код разработанной программы на языке Python приведен в таблице 1.
Таблица 1. Код разработанной программы
main.py |
import collections import pprint import typing def to_flatten_tuple(any_arg: typing.Union[int, str, tuple, list, set]) -> tuple: """ Преобразует аргумент 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: кортеж """ import re return tuple(re.findall(pattern=r'\w+', string=str(any_arg))) class PairByFirst: """Класс "Пара значений". Уникальность по первому значению.""" def __init__(self, first=None, second=None): self.first = first self.second = second def __hash__(self) -> int: return hash(self.first) def __eq__(self, other) -> bool: return isinstance(other, PairByFirst) and self.first == other.first or self.first == other def __str__(self) -> str: return f"{self.first} | {self.second}" def __repr__(self) -> str: return repr(str(self)) |
def create_mealy_vending_machine(cost_of_goods: int, coins: set) -> (dict, str): """ Создает вендинговый автомат Мили Выходные сигналы: - -- не выдавать ничего 0 -- выдать товар без сдачи N -- выдать товар и N рублей сдачи :param cost_of_goods: стоимость товара :param coins: множество стоимостей монет/купюр (множество чисел) :return: (вендинговый автомат Мили, начальное состояние) """ transitions = sorted(coins) # список переходов machine = dict() # автомат Мили seen = set() # множество уже обработанных состояний initial_state = 0 # начальное состояние str_initial_state = str(initial_state) # начальное состояние в строковом виде q = collections.deque([initial_state]) # очередь состояний (0 рублей -- начальное состояние) while q: # пока очередь не пуста state = q.popleft() # выбираем первый элемент очереди if state in seen: # если состояние уже было обработано continue # идем к следующему элементу в очереди seen.add(state) # или добавляем текущее состояние в список обработанных и обрабатываем его str_state = str(state) # текущее состояние в строковом виде machine.setdefault(str_state, dict()) # добавляем словарь переходов по меткам, если не был добавлен for t in transitions: # для каждого перехода if state + t < cost_of_goods: # если сумма меньше стоимости товара machine[str_state][PairByFirst(str(t), '-')] = str(state + t) # добавляем в автомат состояние q.append(state + t) # добавляем в очередь состояние для последующей обработки else: # если сумма больше или равна стоимости товара, то выдаем сдачу и возвращаемся в initial_state machine[str_state][PairByFirst(str(t), str(state + t - cost_of_goods))] = str_initial_state return machine, str_initial_state def create_moore_vending_machine(cost_of_goods: int, coins: set, z_state: str = 'z') -> (dict, PairByFirst): """ Создает вендинговый автомат Мура Сигналы состояний: - -- не выдавать ничего 0 -- выдать товар без сдачи N -- выдать товар и N рублей сдачи z-переход означает, что сдача выдана и автомат должен перейти в начальное состояние :param cost_of_goods: стоимость товара :param coins: множество стоимостей монет/купюр (множество чисел) :param z_state: обозначение z перехода :return: (вендинговый автомат Мура, начальное состояние) """ transitions = sorted(coins) # список переходов machine = dict() # автомат Мура seen = set() # множество уже обработанных состояний initial_state = (0, '-') # начальное состояние pair_initial_state = PairByFirst(str(initial_state[0]), str(initial_state[1])) # начальное состояние в виде пары q = collections.deque([initial_state]) # очередь состояний (0 рублей -- начальное состояние) while q: # пока очередь не пуста state = q.popleft() # выбираем первый элемент очереди if state in seen: # если состояние уже было обработано continue # идем к следующему элементу в очереди seen.add(state) # или добавляем текущее состояние в список обработанных и обрабатываем его pair_state = PairByFirst(str(state[0]), str(state[1])) # текущее состояние в виде пары machine.setdefault(pair_state, dict()) # добавляем словарь переходов по меткам, если не был добавлен for t in transitions: # для каждого перехода if state[0] + t < cost_of_goods: # если сумма меньше стоимости товара new_state = (state[0] + t, '-') pair_new_state = PairByFirst(str(new_state[0]), str(new_state[1])) machine[pair_state][str(t)] = pair_new_state # добавляем в автомат состояние q.append(new_state) # добавляем в очередь состояние для последующей обработки else: # если сумма больше или равна стоимости товара, то выдаем сдачу и возвращаемся в initial_state pair_new_state = PairByFirst(str(state[0] + t), str(state[0] + t - cost_of_goods)) machine[pair_state][str(t)] = pair_new_state machine.setdefault(pair_new_state, dict()).setdefault(z_state, pair_initial_state) return machine, pair_initial_state def simulate_vending_machine(machine: dict, initial_state: typing.Any, coins: list, z_state: str = 'z') -> list: """ Симулирует поведение вендингового автомата (Мура или Мили) :param machine: вендинговый автомат (Мура или Мили) :param initial_state: начальное состояние автомата :param coins: список монет/купюр, принятых вендинговым автоматом (строки либо числа) :param z_state: обозначение z перехода (для автомата Мура) :return: список состояний с переходами (включая начальное и последнее состояние) """ state = initial_state # начинаем с начального состояния states_and_alphas_list = [state] # заводим список состояний с метками переходов for coin in coins: # для каждой монеты/купюры transition = machine.get(state) # находим возможные переходы из текущего состояния if transition is None: # если переходов нет, то ошибка raise ValueError(f'Нет переходов из состояния {state}') |
for alpha in (coin, str(coin), z_state): # для переходов coin / str(coin) / z_state state = transition.get(alpha) # проверяем, возможен ли переход if state is not None: # если да, то alpha = next(a for a in transition if a == alpha) # переполучаем метку (необходимо для автомата Мура) break # завершаем цикл if state is None: # если нужный переход не был найден raise ValueError(f'Номинал в {coin} не может быть принят') states_and_alphas_list.append(alpha) # добавляем в список метку перехода states_and_alphas_list.append(state) # и новое состояние # если есть переход z_state, то сразу переходим transition = machine.get(state) if transition is not None: # если есть переходы next_state = transition.get(z_state) if next_state is not None: # если есть переход по метке z_state alpha = next(a for a in transition if a == z_state) # переполучаем метку (необходимо для автомата Мура) states_and_alphas_list.append(alpha) # добавляем в список метку перехода states_and_alphas_list.append(next_state) # и новое состояние state = next_state # обновляем текущее состояние return states_and_alphas_list def to_string_states(fsm: dict, initial_states: typing.Any, final_states: typing.Any): """ Преобразует состояния и переходы в строки :param fsm: конечный автомат, например, {'1': {'a': {'1', '2'}}, '2': {'a': '1'}} :param initial_states: множество начальных состояний КА (элементы множества -- строки / кортежи) либо одно начальное состояние (строка / кортеж) :param final_states: множество заключительных состояний КА (элементы множества -- строки / кортежи) либо одно заключительное состояние (строка / кортеж) :return: (конечный автомат, множество начальных состояний, множество заключительных состояний) """ new_fsm = dict() initial_states = set(map(str, initial_states)) if isinstance(initial_states, (set, list)) else {str(initial_states)} final_states = set(map(str, final_states)) if isinstance(final_states, (set, list)) else {str(final_states)} for state, alphas in fsm.items(): str_state = str(state) new_fsm.setdefault(str_state, dict()) for alpha, alpha_states in alphas.items(): str_alpha = str(alpha) new_fsm[str_state].setdefault(str_alpha, set()) if isinstance(alpha_states, (set, list)): new_fsm[str_state][str_alpha].extend(set(map(str, alpha_states))) else: new_fsm[str_state][str_alpha].add(str(alpha_states)) return new_fsm, initial_states, final_states
def fsm_plot(filename: str, fsm: dict, initial_states: typing.Union[set, tuple, str], final_states: typing.Union[set, 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="LR", fontname="arial") # направление -- сверху вниз; шрифт Arial для графа G.attr('node', fontname="arial") # шрифт Arial для состояния G.attr('edge', fontname="arial") # шрифт Arial для дуги 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) 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='\n'.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='\n'.join(sorted(state_alpha_state, key=to_flatten_tuple))) # рисуем петлю G.render(filename, format='png', cleanup=True) def main(): variants = [ # варианты в виде (стоимость товара, список монет, эксперименты-примеры) # 1 вариант; товар: 2 руб.; монеты: 1, 2, 5, 10 (2, [1, 2, 5, 10], [[1, 2, 5, 10], [1, 1], [5, 10]]), ] for i, (cost_of_goods, coins, examples) in enumerate(variants, 1): mealy_vending_machine, initial_state = create_mealy_vending_machine(cost_of_goods, set(coins)) mvm, init, final = to_string_states(mealy_vending_machine, initial_state, set()) fsm_plot(f'{i}_mealy_vending_machine', mvm, init, final) print(f'Вендинговый автомат Мили для {i} варианта:') pprint.pprint(mealy_vending_machine) for example in examples: try: state = simulate_vending_machine(mealy_vending_machine, initial_state, example) print(f'{example} -> {state}') except Exception as e: print(str(example), '->', ', '.join(e.args)) print() moore_vending_machine, initial_state = create_moore_vending_machine(cost_of_goods, set(coins)) mvm, init, final = to_string_states(moore_vending_machine, initial_state, set()) fsm_plot(f'{i}_moore_vending_machine', mvm, init, final) print(f'Вендинговый автомат Мура для {i} варианта:') pprint.pprint(moore_vending_machine) for example in examples: try: state = simulate_vending_machine(moore_vending_machine, initial_state, example) print(f'{example} -> {state}') except Exception as e: print(str(example), '->', ', '.join(e.args)) print() if __name__ == "__main__": main()
|
Работа программы приведена на рис. 1-6.
Вывод программы и диаграмма состояний представлены в таблице 2.
Таблица 2. Вывод программы и соответствующие диаграммы состояний
Вывод программы |
Диаграммы состояний (генерируются программно) |
Вендинговый автомат Мили для 1 варианта: {'0': {'1 | -': '1', '2 | 0': '0', '5 | 3': '0', '10 | 8': '0'}, '1': {'1 | 0': '0', '2 | 1': '0', '5 | 4': '0', '10 | 9': '0'}} [1, 2, 5, 10] -> ['0', '1 | -', '1', '2 | 1', '0', '5 | 3', '0', '10 | 8', '0'] [1, 1] -> ['0', '1 | -', '1', '1 | 0', '0'] [5, 10] -> ['0', '5 | 3', '0', '10 | 8', '0'] |
|
Вендинговый автомат Мура для 1 варианта: {'5 | 3': {'z': '0 | -'}, '1 | -': {'1': '2 | 0', '10': '11 | 9', '2': '3 | 1', '5': '6 | 4'}, '10 | 8': {'z': '0 | -'}, '2 | 0': {'z': '0 | -'}, '3 | 1': {'z': '0 | -'}, '6 | 4': {'z': '0 | -'}, '11 | 9': {'z': '0 | -'}, '0 | -': {'1': '1 | -', '10': '10 | 8', '2': '2 | 0', '5': '5 | 3'}} [1, 2, 5, 10] -> ['0 | -', '1', '1 | -', '2', '3 | 1', 'z', '0 | -', '5', '5 | 3', 'z', '0 | -', '10', '10 | 8', 'z', '0 | -'] [1, 1] -> ['0 | -', '1', '1 | -', '1', '2 | 0', 'z', '0 | -'] [5, 10] -> ['0 | -', '5', '5 | 3', 'z', '0 | -', '10', '10 | 8', 'z', '0 | -'] |
|
