[2] #344
123
novikovsd/отчет_лабороторная2.txt
Normal file
123
novikovsd/отчет_лабороторная2.txt
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
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, добавляя в него вызовы отрисовки.
|
||||||
Loading…
Reference in New Issue
Block a user