Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТАФЯ. Лабораторная работа 3.docx
Скачиваний:
0
Добавлен:
15.04.2026
Размер:
105.22 Кб
Скачать

МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ,

СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ

Федеральное государственное бюджетное образовательное учреждение высшего образования «Санкт-Петербургский государственный университет

телекоммуникаций им. проф. М. А. Бонч-Бруевича»

(СПбГУТ)

Факультет инфокоммуникационных сетей и систем

Кафедра программной инженерии и вычислительной техники

Лабораторная работа №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 | -']