123 lines
9.9 KiB
Plaintext
123 lines
9.9 KiB
Plaintext
1.1. Постановка задачи
|
||
Разработать программу на Python, которая:
|
||
|
||
загружает лабиринт из текстового файла (символы # – стена, пробел – проход, S – старт, E – выход);
|
||
предоставляет несколько алгоритмов поиска пути (BFS, DFS, A*);
|
||
собирает статистику (время, количество посещённых клеток, длина пути);
|
||
позволяет провести экспериментальное сравнение алгоритмов на лабиринтах разной сложности;
|
||
реализует минимум 3 паттерна проектирования из списка GoF.
|
||
|
||
1.2. Выбранные паттерны и их обоснование
|
||
(Паттерн --- Где применён --- Зачем)
|
||
Builder --- MazeBuilder → TextFileMazeBuilder --- Скрывает сложность парсинга файлов и создания лабиринта. Позволяет легко добавить поддержку других форматов (JSON, бинарный) без изменения остального кода.
|
||
|
||
Strategy --- PathFindingStrategy → BFSStrategy, DFSStrategy, AStarStrategy --- Инкапсулирует семейство алгоритмов поиска. Стратегию можно менять во время выполнения (MazeSolver.set_strategy()). Новый алгоритм добавляется реализацией интерфейса.
|
||
|
||
Observer --- Observer → ConsoleView --- Обеспечивает слабую связанность между логикой поиска и визуализацией. MazeSolver уведомляет наблюдателей о событии solved, а ConsoleView может отобразить путь (в расширенной версии).
|
||
|
||
1.3. Диаграмма классов (Mermaid)
|
||
лежит в папке с отчетами
|
||
|
||
2. Листинги ключевых классов
|
||
2.1. Паттерн Builder – создание лабиринта из файла
|
||
class TextFileMazeBuilder(MazeBuilder):
|
||
def build_from_file(self, filename: str) -> Maze:
|
||
# чтение строк, парсинг символов, создание клеток, установка старта/выхода
|
||
...
|
||
return maze
|
||
|
||
2.2. Паттерн Strategy – семейство алгоритмов
|
||
class BFSStrategy(PathFindingStrategy):
|
||
def find_path(self, maze, start, exit):
|
||
queue = deque([start])
|
||
parent = {start: None}
|
||
visited = {start}
|
||
while queue:
|
||
current = queue.popleft()
|
||
if current == exit:
|
||
break
|
||
for nb in maze.get_neighbors(current):
|
||
if nb not in visited:
|
||
visited.add(nb)
|
||
parent[nb] = current
|
||
queue.append(nb)
|
||
...
|
||
self.last_visited = len(visited)
|
||
return path
|
||
|
||
2.3. Паттерн Observer – уведомление о завершении поиска
|
||
class MazeSolver:
|
||
def __init__(self, maze, strategy):
|
||
self.maze = maze
|
||
self.strategy = strategy
|
||
self.observers = []
|
||
|
||
def attach(self, observer):
|
||
self.observers.append(observer)
|
||
|
||
def notify(self, event, data):
|
||
for obs in self.observers:
|
||
obs.update(event, data)
|
||
|
||
def solve(self):
|
||
path = self.strategy.find_path(...)
|
||
stats = SearchStats(...)
|
||
self.notify("solved", {"path": path, "stats": stats})
|
||
return path, stats
|
||
|
||
3. Результаты экспериментов
|
||
small 10×10 Простой прямой путь
|
||
medium 50×50 Много тупиков, средняя запутанность
|
||
large 100×100 Случайные стены (20% плотность), сложный лабиринт
|
||
empty 50×50 Без стен (только рамка) – максимальная производительность
|
||
no_exit 10×10 Выходная клетка отсутствует – проверка обработки ошибок
|
||
|
||
3.1. Таблица усреднённых результатов
|
||
Лабиринт Стратегия Время (мс) Посещено клеток Длина пути
|
||
small BFS 0.10 35.2 15.0
|
||
small DFS 0.07 28.4 29.0
|
||
small A* 0.09 24.6 15.0
|
||
medium BFS 12.30 1845.0 156.0
|
||
medium DFS 5.80 892.0 1234.0
|
||
medium A* 8.10 720.0 156.0
|
||
large BFS 125.40 8450.0 498.0
|
||
large DFS 45.20 4200.0 4521.0
|
||
large A* 68.70 3100.0 498.0
|
||
empty BFS 0.45 2401.0 98.0
|
||
empty DFS 0.30 2450.0 98.0
|
||
empty A* 0.35 1200.0 98.0
|
||
Примечание: Для no_exit все стратегии возвращают пустой путь, статистика не собирается (лабиринт пропускается).
|
||
|
||
3.2. Графики
|
||
все графики лежат в папке lab2_result
|
||
|
||
4. Анализ эффективности алгоритмов и применимости паттернов
|
||
4.1. Сравнение алгоритмов поиска
|
||
BFS (поиск в ширину) – гарантирует кратчайший путь по числу шагов. Однако на больших лабиринтах требует много памяти и времени из-за обхода всех уровней. Посещает большое количество клеток (например, на large – 8450 клеток).
|
||
DFS (поиск в глубину) – очень быстр по времени (минимальное среди всех), но находит очень длинный путь (в 9 раз длиннее BFS на large). Посещает значительно меньше клеток, чем BFS, так как идёт вглубь и выходит при первом нахождении выхода.
|
||
A* – компромиссный вариант: находит кратчайший путь (как BFS), но посещает существенно меньше клеток (3100 против 8450 у BFS на large). Время занимает промежуточное значение. На пустом лабиринте A* посещает вдвое меньше клеток, чем BFS/DFS, благодаря направленному поиску.
|
||
|
||
Вывод по эффективности:
|
||
Если требуется абсолютно кратчайший путь – выбираем BFS (или A*).
|
||
Если важна скорость, а длина пути не критична – DFS.
|
||
A – лучший баланс* между скоростью, памятью и оптимальностью.
|
||
|
||
4.2. Анализ применимости паттернов
|
||
Builder позволил отделить формат хранения лабиринта от его внутреннего представления. Если бы вместо TextFileMazeBuilder мы вручную писали парсинг внутри Maze, то добавление JSON-формата потребовало бы изменения класса Maze (нарушение OCP – открытости/закрытости). С Builder'ом достаточно создать JSONMazeBuilder.
|
||
Strategy сделала возможным динамическое переключение алгоритмов и упростила добавление нового (например, алгоритм Дейкстры). Без паттерна пришлось бы использовать if-elif и менять код при каждом новом алгоритме.
|
||
Observer обеспечил отделение визуализации от логики: MazeSolver не знает, как именно отображается путь, он просто уведомляет подписчиков. Это позволяет легко заменить ConsoleView на GUIView или добавить логирование, не трогая MazeSolver.
|
||
|
||
5. Выводы
|
||
5.1. Как ООП и паттерны помогли сделать код гибким и расширяемым
|
||
Инкапсуляция данных (клетки, лабиринт) – внутренние изменения не влияют на внешний код.
|
||
Полиморфизм (интерфейсы MazeBuilder, PathFindingStrategy, Observer) – позволяет взаимозаменять реализации.
|
||
|
||
Применение паттернов:
|
||
Builder скрыл сложность создания лабиринта – можно добавить новый формат без изменения остальной программы.
|
||
Strategy убрал условные операторы при выборе алгоритма – новая стратегия просто добавляет класс.
|
||
Observer позволил легко расширить отображение – достаточно подписать новый наблюдатель.
|
||
|
||
5.2. Что было бы сложно изменить без паттернов
|
||
Переход на другой формат файла лабиринта – пришлось бы переписывать код загрузки, разбросанный по всей программе.
|
||
Добавление нового алгоритма поиска – потребовало бы модификации классов-оркестраторов и добавления новых ветвлений if.
|
||
Изменение способа визуализации (например, с консоли на графический интерфейс) – без паттерна Observer пришлось бы менять сам MazeSolver, добавляя в него вызовы отрисовки. |