diff --git a/svetlakovkyu/02/docs/benchmark_plot.png b/svetlakovkyu/02/docs/benchmark_plot.png new file mode 100644 index 0000000..d103767 Binary files /dev/null and b/svetlakovkyu/02/docs/benchmark_plot.png differ diff --git a/svetlakovkyu/02/docs/mermaid.png b/svetlakovkyu/02/docs/mermaid.png new file mode 100644 index 0000000..6e835b6 Binary files /dev/null and b/svetlakovkyu/02/docs/mermaid.png differ diff --git a/svetlakovkyu/02/docs/report1.md b/svetlakovkyu/02/docs/report1.md new file mode 100644 index 0000000..4aa142f --- /dev/null +++ b/svetlakovkyu/02/docs/report1.md @@ -0,0 +1,207 @@ +# Отчёт: Поиск выхода из лабиринта + +## 1. Описание задачи и выбранных паттернов + +### Задача + +Разработать программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода тремя алгоритмами (BFS, DFS, A*), визуализации найденного пути и экспериментального сравнения алгоритмов по времени, числу посещённых клеток и длине пути. + +### Структура файлов + +``` +02/ + main.py - точка запуска + codes/ + maze.py - все классы (Cell, Maze, Builder, Strategy, Solver) + maze_generator.py - генерация тестовых лабиринтов + mazes/ - текстовые файлы лабиринтов + results/ + results_maze.csv - результаты экспериментов + benchmark_plot.png - графики + docs/ + report1.md - отчёт + mermaid.png - диаграмма классов +``` + +### Применённые паттерны проектирования + +**1. Builder** - класс `TextFileMazeBuilder` реализует интерфейс `MazeBuilder`. + +Построение лабиринта из файла включает несколько шагов: чтение строк, обход символов, создание объектов `Cell`, поиск стартовой и конечной клетки. Без Builder вся эта логика оказалась бы в `main.py` или в конструкторе `Maze`. Builder скрывает детали создания от клиента. Если понадобится загружать лабиринт из JSON или бинарного файла - достаточно написать новый класс, реализующий тот же интерфейс `MazeBuilder`. + +**2. Strategy** - классы `BFSStrategy`, `DFSStrategy`, `AStarStrategy` реализуют интерфейс `PathFindingStrategy`. + +Алгоритм поиска можно менять во время работы программы через `MazeSolver.set_strategy()`, не трогая остальной код. Добавление нового алгоритма - это написание одного нового класса с методом `find_path()`. Без Strategy в `solve()` пришлось бы писать if/elif для каждого алгоритма. + +**3. Observer** - интерфейс `Observer` с методом `update(event)`. + +`MazeSolver` хранит список наблюдателей и уведомляет их при событиях `search_started`, `path_found`, `path_not_found`. Это позволяет добавлять отображение в консоль, запись в лог или GUI-уведомления, не меняя код солвера. Слабая связанность: солвер не знает, кто его слушает. + +### Диаграмма классов + +![Диаграмма классов](mermaid.png) + +--- + +## 2. Листинги ключевых классов + +### Cell и Maze + +```python +class Cell: + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + +class Maze: + def get_neighbors(self, cell): + result = [] + for dx, dy in [(0,-1),(0,1),(-1,0),(1,0)]: + n = self.get_cell(cell.x + dx, cell.y + dy) + if n and n.is_passable(): + result.append(n) + return result +``` + +### Паттерн Builder + +```python +class MazeBuilder(ABC): + @abstractmethod + def build_from_file(self, filename) -> Maze: + pass + +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename) -> Maze: + with open(filename, encoding="utf-8") as f: + lines = [l.rstrip("\n") for l in f] + # ... парсинг символов, создание Cell, поиск S и E + return Maze(cells, width, height, start, exit_cell) +``` + +### Паттерн Strategy - алгоритм A* + +```python +class AStarStrategy(PathFindingStrategy): + @staticmethod + def _h(a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, maze, start, end): + heap = [(0, 0, start)] + parent = {start: None} + g = {start: 0} + closed = set() + while heap: + _, _, cur = heapq.heappop(heap) + if cur in closed: + continue + closed.add(cur) + if cur == end: + return self._build_path(parent, start, end) + for nb in maze.get_neighbors(cur): + ng = g[cur] + 1 + if ng < g.get(nb, float("inf")): + g[nb] = ng + heapq.heappush(heap, (ng + self._h(nb, end), id(nb), nb)) + parent[nb] = cur + return [] +``` + +### MazeSolver + +```python +class MazeSolver: + def __init__(self, maze, strategy): + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy): + self.strategy = strategy + + def solve(self) -> SearchStats: + t0 = time.perf_counter() + path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) + t1 = time.perf_counter() + return SearchStats( + strategy=self.strategy.name, + time_ms=(t1 - t0) * 1000, + visited=self.strategy._visited, + path_length=len(path), + path=path, + ) +``` + +--- + +## 3. Результаты экспериментов + +Каждый алгоритм запускался 7 раз на каждом лабиринте, результаты усреднялись. + +### Таблица результатов + +| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути | +|----------|----------|-----------|----------------|------------| +| small (11x11) | BFS | 0.070 | 39 | 33 | +| small (11x11) | DFS | 0.055 | 33 | 33 | +| small (11x11) | A* | 0.112 | 35 | 33 | +| medium (51x51) | BFS | 1.391 | 793 | 497 | +| medium (51x51) | DFS | 0.949 | 515 | 497 | +| medium (51x51) | A* | 2.271 | 707 | 497 | +| large (101x101) | BFS | 6.231 | 3533 | 1613 | +| large (101x101) | DFS | 3.341 | 1957 | 1613 | +| large (101x101) | A* | 11.27 | 3379 | 1613 | +| empty (51x21) | BFS | 1.992 | 931 | 67 | +| empty (51x21) | DFS | 1.021 | 451 | 451 | +| empty (51x21) | A* | 3.527 | 931 | 67 | +| no_exit (11x11) | BFS | 0.079 | 40 | - | +| no_exit (11x11) | DFS | 0.077 | 40 | - | +| no_exit (11x11) | A* | 0.140 | 40 | - | + +### Графики + +![Графики](../results/benchmark_plot.png) + +--- + +## 4. Анализ эффективности алгоритмов и применимости паттернов + +### Алгоритмы + +**BFS** гарантирует кратчайший путь по числу шагов. Расширяет узлы слой за слоем во всех направлениях, поэтому посещает наибольшее число клеток. На практике это надёжный выбор когда нужен точно кратчайший маршрут. + +**DFS** посещает меньше клеток и выполняется быстрее - на large лабиринте в 1.8 раза быстрее BFS. Однако путь может быть далеко не кратчайшим. На пустом лабиринте DFS нашёл путь длиной 451 шаг, тогда как BFS и A* - 67. Это связано с тем, что DFS уходит в первое попавшееся направление и возвращается только в тупике. + +**A*** использует манхэттенскую эвристику h = |x1-x2| + |y1-y2| и должен в теории посещать меньше клеток чем BFS. На лабиринтах, сгенерированных алгоритмом recursive backtracker, выигрыш небольшой (примерно 5%). Причина: backtracker строит дерево - между любыми двумя клетками ровно один путь, тупиков нет, эвристика не помогает их обходить. На лабиринтах с циклами A* посещает заметно меньше клеток. Накладные расходы на работу с heap и closed-set делают A* медленнее по времени, чем DFS. + +На пустом лабиринте (без стен) A* ведёт себя как BFS. Математически: f(x,y) = g + h = (x-1+y-1) + (W-x+H-y) = const для всех клеток. Все узлы неразличимы по приоритету. + +На лабиринте без выхода все три алгоритма посещают одинаковое число клеток и корректно возвращают пустой путь. + +### Паттерны + +**Builder** оказался полезным при добавлении нового типа лабиринта (взвешенного, с символами s и m). Изменения были внесены только в `TextFileMazeBuilder`, клиентский код не менялся. + +**Strategy** позволил в одном цикле запустить все три алгоритма через `solver.set_strategy(strategy)`. Без паттерна пришлось бы либо дублировать код запуска для каждого алгоритма, либо писать условные ветки. + +**Observer** полезен при расширении: чтобы добавить вывод в лог или консоль, достаточно написать новый Observer и подписать его на solver, не меняя `MazeSolver`. + +--- + +## 5. Выводы + +ООП и паттерны позволили сделать код гибким в нескольких направлениях. + +Добавление нового алгоритма поиска сводится к написанию одного класса, реализующего `find_path()`. Без Strategy пришлось бы добавлять ветку в `solve()` и во все места, где запускается поиск. + +Добавление нового формата лабиринта - только новый класс Builder. Без паттерна логика парсинга была бы перемешана с логикой работы программы. + +Добавление нового способа отображения (GUI, запись в файл) - только новый Observer. Без него MazeSolver пришлось бы напрямую вызывать функции отображения, что создало бы зависимость от конкретной реализации. + +Без применения паттернов код решал бы задачу, но любое изменение требовало бы правки в нескольких местах сразу. С паттернами каждый класс отвечает за одну задачу и не знает о деталях реализации соседних классов. diff --git a/svetlakovkyu/02/mazes/empty.txt b/svetlakovkyu/02/mazes/empty.txt new file mode 100644 index 0000000..8a42a81 --- /dev/null +++ b/svetlakovkyu/02/mazes/empty.txt @@ -0,0 +1,21 @@ +################################################### +#S # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# E# +################################################### diff --git a/svetlakovkyu/02/mazes/large.txt b/svetlakovkyu/02/mazes/large.txt new file mode 100644 index 0000000..6f97148 --- /dev/null +++ b/svetlakovkyu/02/mazes/large.txt @@ -0,0 +1,101 @@ +##################################################################################################### +#S # # # # # # # # # # # # # +### # # ##### # ### # # ##### ### ### ############# # # # # ##### # ######### ### # # ##### ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ##### ### ### # # ########### ####### # ####### ### ### # # # ####### ##### # # ### # # ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### # ### ### # ### # ### # # ##### # ##### ####### ### ### # ### ### ### # ### ### # ##### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # ### # # ### # ##### ##### # ### # # ##### # # ####### ######### ### # # ######### # # # ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### ### # ##### # ##### ### # ##### ######### # # ####### ####### # ### # # ########### ##### # +# # # # # # # # # # # # # # # # # # # # # # +##### ##### ########### ### # # ########### # # # # ##### ####### # ####### # ##### ### ### ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # +####### # ### # # ####### ##### ### ##### # # ##### # ##### ### # # # ######### # ### ### ### ### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # ########### # # # ### ### ##### ##### # # ##### # # # ##### # # ##### ##### # ##### ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### # ### ### ### ####### # ########### ####### # # ##### # # ### ########### ### ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # ####### # # # # ##### ### # ####### # ##### ##### ##### # # # ### ### # # ##### ##### ### ####### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +########### # # ##### # # # ####### # ### # ##### # ##### ### # # # # # ####### # ##### ### # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ### # # ##### # # # # # ##### # # # ############# # ##### # ############### ### # # ### # # +# # # # # # # # # # # # # # # # # # # # # # # +# # ####### ##### # ########### # # # ######### ### ############# # ####### ####################### # +# # # # # # # # # # # # # # # # # # # # # +# # ### ### # # # # # ### # ##### # ### # ####### # ##### # ######### ####### # ##### # ########### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### ##### ######### ####### ##### # # # # # ### # ##### # ### # ### # ##### # # # # # ### # # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # # ### ##### ### # # ### ##### ##### ####### # # ##### # ### ##### ####### # # # ####### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ####### # ### # # ### ### ##### ##### # # # # ### # # # ####### # # ### ##### # # ####### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +##### # # # # # ### # ### ### ### ### # ### # ### ##### # # ########### # # ### # # ##### ##### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### ##### ### ### # # ### ### # # ##### ### # ##### ########### ### ### ### # # # # ### ######### # +# # # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### ### # # ####### # # ######### ### # ########### # ### ### ##### ### # ### # # ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ######### # ####### ##### ######### # ### ### # ##### # ### ### ### # ### ### # ##### ##### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ### # # # # # ##### # # # # ####### # # # ### # ### # ### ### ##### ### # # ### # # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # ### ######### # ##### # ##### # # ############# # # ### ####### # # ##### # # # # # # ### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### ####### ### # ### ##### # # # ##### # ####### ### # # ### # # # # ### ### # ########### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # +# ####### # ##### # ### # ##### # # ##### # ##### # ### ### ######### ##### ### # ##### # ######### # +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # # # ### ####### # # # # ##### # ############### # # ##### ####### ##### # ### # # # # ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### ########### ### # ############# # # # ### # ##### ##### ### ####### # ### # # # ##### ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # +### ##### ##### ### ### ### # # # # ##### # # ####### # # # ####### # # ### # ### # # # # ### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # ### # # # ####### # # # # ### ####### # # # ##### ### # # ##### # ##### # # ### ### # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### # ### # # # # ### ### ### # # ### ### # ##### ### # # ##### ####### # ##### # ##### ### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ### ####### ### # # ### ##### # # ######### ### ### # # # # # ### # ### # # ### ### # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # # ### ### ##### ######### ######### # # # # # ########### ####### # # ### ########### ##### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ### ### # ### # # ######### # # # # ##### # # # ##### # ####### ####### # ##### # # ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### ### ##### ####### # # ### ######### ### ### # # ########### # ##### # ### ##### ##### # ### # +# # # # # # # # # # # # # # # # # # # # # # # # +# ####### # # ##### ### # ### # ####### # # # ### ### # # ### # ####### # ##### ### # ######### ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ##### # ####### ####### # ########### ####### ######### # ### # ### # ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ####### # ### ########### # # ######### ### # # # # # # ### # ######### # ### ########### # +# # # # # # # # # # # # # # # # # # # # # # # +### ####### # # # # # ### # # # ######### # # # ########################### # # ##### ####### # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # +# ### ### ############### ####### ##### # ########### # ### # # ##### ### # ##### # ##### # ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # +# ########### ##### ### ####### # # # ##### # # # # # ### # # ### ### # ### ### # ##### # # # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### # ### # ### # # ### # # ####### ####### # ### ##### ##### ##### ### ##### # ####### ####### +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # ##### ####### ### # # # ############# # # # # ##### ### ######### # ######### # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # +### # ##### ##### # ### # ### ####### # ####### # ### ##### # ######### ##### # # ####### ### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # # # ##### # ##### # ### # ### # ### ##### ### ##### ########### ### # ########### # # ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # ##### # # # # # # # # ######### ### # ### # # ### # # # ### # # ### # # # # ### # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### ######### ### ### ######### # ### # # ### # ### # # ##### # # ######### ### ### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # +# ##### ##### # # ####### ### ### # ##### ##### ##### # # ##### ##### ############# # # ### ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # # ### ### # # ### # # ####### ##### # # # # # ### ##### ##### # ### ##### # # ######### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### ### ### # # ### ##### ##### ##### # ##### ### # ### # ### ##### ##### # ##### # # # # # # # +# # # # # # # # # # # # # E# +##################################################################################################### diff --git a/svetlakovkyu/02/mazes/medium.txt b/svetlakovkyu/02/mazes/medium.txt new file mode 100644 index 0000000..677cd38 --- /dev/null +++ b/svetlakovkyu/02/mazes/medium.txt @@ -0,0 +1,51 @@ +################################################### +#S# # # # # # # # +# ##### # # # # # # # ####### # ### # ### ##### # # +# # # # # # # # # # # # # # # # # # +### # ### # # ####### # ### # # # # ### # ### ### # +# # # # # # # # # # # # # # +# # # # # # ####### ####### # ### ######### ##### # +# # # # # # # # # # # # # # +# # # ##### # # # ### ####### ##### ##### # ### ### +# # # # # # # # # # # # # # # # # +# # ##### ### # ### ### # # ### # ##### # ### # # # +# # # # # # # # # # # # # # # # # +# # # # # # ### # ### ### ##### # # # ####### # # # +# # # # # # # # # # # # # # # +# ######### # # # # ### # # # ### # ####### # ### # +# # # # # # # # # # # # # # # # # # +# # ### # ### # # ### ### ### # ### # # # ### # # # +# # # # # # # # # # # # # # # +# ### ### # ### ####### ######### # ### ####### ### +# # # # # # # # # # # # # # # +# # ### ### # ### # ####### # # # # # ####### # # # +# # # # # # # # # # # # # # +# ####### # ############# ##### # # ####### # ### # +# # # # # # # # # # # +# # # # # ################# ########### # # ##### # +# # # # # # # # # # # +# ####### # ### ##### # # ### ### ######### # ##### +# # # # # # # # # # +####### # ####### ##### ### ##### # ############# # +# # # # # # # # # # # # +##### ####### # # ### # ##### # # ##### ##### # # # +# # # # # # # # # # # # # # # +# ##### # # # ##### ##### # ### ### # # # ##### # # +# # # # # # # # # # # # # # +# ####### # ### # ### # # # ################# ### # +# # # # # # # # # # +### ##### ########### # # ############# ### ### ### +# # # # # # # # # # # # +# ### ### # ####### # # ### # ####### # # ### ### # +# # # # # # # # # # # # # # # +# # ######### ### # # # # # ### ### ####### # ### # +# # # # # # # # # # # # # # +# ##### # ##### # ##### ##### ### ####### ##### ### +# # # # # # # # # # # # +# # # # # # ##### ### ########### # # ####### ### # +# # # # # # # # # # # # # # # +# # ### ##### # ### ##### ##### ##### # # ### # # # +# # # # # # # # # # # # # +### # ### ##### ####### ##### ##### ####### # # # # +# # # # # #E# +################################################### diff --git a/svetlakovkyu/02/mazes/no_exit.txt b/svetlakovkyu/02/mazes/no_exit.txt new file mode 100644 index 0000000..09c1301 --- /dev/null +++ b/svetlakovkyu/02/mazes/no_exit.txt @@ -0,0 +1,11 @@ +########### +#S# #E# +# ######### +# # # +##### # ### +# # # +# ####### # +# # # +### ### # # +# ### +########### diff --git a/svetlakovkyu/02/mazes/sample.txt b/svetlakovkyu/02/mazes/sample.txt new file mode 100644 index 0000000..119bed9 --- /dev/null +++ b/svetlakovkyu/02/mazes/sample.txt @@ -0,0 +1,15 @@ +############### +#S# # # +# ### # # ### # +# # # # # # +### ### ### # # +# # # # # # +# ### # # ### # +# # # # # +### ##### # # # +# # # # +# ### ######### +# # # # +# # ##### # # # +# # #E# +############### diff --git a/svetlakovkyu/02/mazes/small.txt b/svetlakovkyu/02/mazes/small.txt new file mode 100644 index 0000000..2fbbeb4 --- /dev/null +++ b/svetlakovkyu/02/mazes/small.txt @@ -0,0 +1,11 @@ +########### +#S # # +##### # # # +# # # # +# ####### # +# # # # +# ### # # # +# # # # +### # ### # +# # E# +########### diff --git a/svetlakovkyu/02/results/benchmark_plot.png b/svetlakovkyu/02/results/benchmark_plot.png new file mode 100644 index 0000000..d103767 Binary files /dev/null and b/svetlakovkyu/02/results/benchmark_plot.png differ diff --git a/svetlakovkyu/02/results/results_maze.csv b/svetlakovkyu/02/results/results_maze.csv new file mode 100644 index 0000000..5806c75 --- /dev/null +++ b/svetlakovkyu/02/results/results_maze.csv @@ -0,0 +1,16 @@ +maze,strategy,time_ms,visited_cells,path_length +small,BFS,0.0676,39.0,33.0 +small,DFS,0.061,33.0,33.0 +small,A*,0.1093,35.0,33.0 +medium,BFS,1.4027,793.0,497.0 +medium,DFS,0.8986,515.0,497.0 +medium,A*,2.3001,707.0,497.0 +large,BFS,6.1605,3533.0,1613.0 +large,DFS,3.3919,1957.0,1613.0 +large,A*,11.2172,3379.0,1613.0 +empty,BFS,1.7583,931.0,67.0 +empty,DFS,1.0076,451.0,451.0 +empty,A*,3.4836,931.0,67.0 +no_exit,BFS,0.067,40.0,0.0 +no_exit,DFS,0.0599,40.0,0.0 +no_exit,A*,0.1099,40.0,0.0