{ "cells": [ { "cell_type": "markdown", "id": "204f9862-9a51-47be-bb5b-fcb099f9f707", "metadata": {}, "source": [ "# Отчёт по лабораторной работе: Поиск выхода из лабиринта (ООП + паттерны)\n", "\n", "## 1. Описание задачи\n", "\n", "Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. Требовалось применить минимум 3 паттерна проектирования GoF.\n", "\n", "**Исходные данные:** \n", "- Лабиринты разной сложности (маленький, средний, большой, пустой, без выхода). \n", "- Формат файла: `#` – стена, ` ` – проход, `S` – старт, `E` – выход. \n", "- Алгоритмы поиска: BFS, DFS, A* (с манхэттенской эвристикой).\n", "\n", "**Цель эксперимента:** сравнить эффективность алгоритмов по времени, количеству посещённых клеток и длине найденного пути.\n", "\n", "---\n", "\n", "## 2. Выбранные паттерны проектирования\n", "\n", "### 2.1. Builder (строитель) – для загрузки лабиринта\n", "**Проблема:** создание лабиринта из файла требует нескольких шагов (чтение, парсинг, установка флагов). \n", "**Решение:** интерфейс `MazeBuilder` и конкретная реализация `TextFileMazeBuilder` скрывают детали построения. \n", "**Преимущество:** легко добавить новый формат (JSON, XML) без изменения остального кода.\n", "\n", "### 2.2. Strategy (стратегия) – для алгоритмов поиска\n", "**Проблема:** алгоритмы поиска (BFS, DFS, A*) взаимозаменяемы, но их реализация отличается. \n", "**Решение:** интерфейс `PathFindingStrategy` и три класса-стратегии. \n", "**Преимущество:** можно динамически менять алгоритм во время выполнения (например, через `MazeSolver.setStrategy()`).\n", "\n", "### 2.3. Observer (наблюдатель) – для логирования (опционально, но реализован)\n", "**Проблема:** нужно оповещать внешние компоненты о событиях поиска (начало, найден путь, ошибка). \n", "**Решение:** интерфейс `Observer` и класс `ConsoleLogger`. \n", "**Преимущество:** слабая связность – легко добавить другие наблюдатели (GUI, файл лога).\n", "\n", "### 2.4. Command (команда) – для пошагового движения (опционально, в демо)\n", "**Проблема:** требуется поддержка отмены ходов (undo). \n", "**Решение:** интерфейс `Command`, класс `MoveCommand`, класс `Player`. \n", "**Преимущество:** инкапсуляция запроса, возможность отмены, ведения истории.\n", "\n", "---\n", "\n", "## 3. Диаграмма классов (Mermaid)\n", "\n", "```mermaid\n", "classDiagram\n", " class Cell {\n", " -int x\n", " -int y\n", " -bool isWall\n", " -bool isStart\n", " -bool isExit\n", " +isPassable()\n", " }\n", " class Maze {\n", " -int width\n", " -int height\n", " -List[List[Cell]] cells\n", " -Cell start\n", " -Cell exit\n", " +getCell(x,y)\n", " +getNeighbors(cell)\n", " }\n", " class MazeBuilder {\n", " <>\n", " +buildFromFile(filename)\n", " }\n", " class TextFileMazeBuilder {\n", " +buildFromFile(filename)\n", " }\n", " class PathFindingStrategy {\n", " <>\n", " +findPath(maze, start, exit)\n", " }\n", " class BFSStrategy\n", " class DFSStrategy\n", " class AStarStrategy\n", " class SearchStats {\n", " +float time_ms\n", " +int visited_cells\n", " +int path_length\n", " +string algorithm\n", " }\n", " class MazeSolver {\n", " -Maze maze\n", " -PathFindingStrategy strategy\n", " +setStrategy(strategy)\n", " +solve() (path, stats)\n", " }\n", " class Observer {\n", " <>\n", " +update(event_type, data)\n", " }\n", " class ConsoleLogger {\n", " +update(event_type, data)\n", " }\n", " class Command {\n", " <>\n", " +execute()\n", " +undo()\n", " }\n", " class MoveCommand {\n", " -Player player\n", " -tuple direction\n", " -Maze maze\n", " -Cell prev_pos\n", " +execute()\n", " +undo()\n", " }\n", " class Player {\n", " -Cell current_cell\n", " }\n", "\n", " MazeBuilder <|.. TextFileMazeBuilder\n", " PathFindingStrategy <|.. BFSStrategy\n", " PathFindingStrategy <|.. DFSStrategy\n", " PathFindingStrategy <|.. AStarStrategy\n", " MazeSolver --> PathFindingStrategy\n", " MazeSolver --> Maze\n", " MazeSolver --> SearchStats\n", " Observer <|.. ConsoleLogger\n", " MazeSolver --> Observer\n", " Command <|.. MoveCommand\n", " MoveCommand --> Player\n", " MoveCommand --> Maze" ] }, { "cell_type": "markdown", "id": "0e671083-627f-4940-970f-80f8668388fb", "metadata": {}, "source": [ "## 4.1 Builder (загрузка лабиринта)" ] }, { "cell_type": "code", "execution_count": 1, "id": "bb983238-274f-4f19-b784-73be954a3aae", "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'ABC' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[1], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[38;5;28;01mclass\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mMazeBuilder\u001b[39;00m(ABC):\n\u001b[0;32m 2\u001b[0m \u001b[38;5;129m@abstractmethod\u001b[39m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21mbuild_from_file\u001b[39m(\u001b[38;5;28mself\u001b[39m, filename):\n\u001b[0;32m 4\u001b[0m \u001b[38;5;28;01mpass\u001b[39;00m\n", "\u001b[1;31mNameError\u001b[0m: name 'ABC' is not defined" ] } ], "source": [ "class MazeBuilder(ABC):\n", " @abstractmethod\n", " def build_from_file(self, filename):\n", " pass\n", "\n", "class TextFileMazeBuilder(MazeBuilder):\n", " def build_from_file(self, filename):\n", " with open(filename, 'r') as f:\n", " lines = [line.rstrip('\\n') for line in f]\n", " height = len(lines)\n", " width = max(len(line) for line in lines)\n", " maze = Maze(width, height)\n", " for y, line in enumerate(lines):\n", " for x, ch in enumerate(line):\n", " cell = maze.get_cell(x, y)\n", " if ch == '#': cell.is_wall = True\n", " elif ch == 'S': cell.is_start = True; maze.start = cell\n", " elif ch == 'E': cell.is_exit = True; maze.exit = cell\n", " else: cell.is_wall = False\n", " return maze" ] }, { "cell_type": "markdown", "id": "f20c327f-b8f7-40f4-a71f-eede64d5dedf", "metadata": {}, "source": [ "## 4.2. Стратегии поиска (BFS, DFS, A*)" ] }, { "cell_type": "code", "execution_count": null, "id": "b5fb0715-9749-404d-870d-0a83c0025eac", "metadata": {}, "outputs": [], "source": [ "class BFSStrategy(PathFindingStrategy):\n", " def find_path(self, maze, start, exit):\n", " queue = deque([start])\n", " visited = {start}\n", " parent = {start: None}\n", " while queue:\n", " cur = queue.popleft()\n", " if cur == exit:\n", " path = []\n", " while cur:\n", " path.append(cur)\n", " cur = parent[cur]\n", " return path[::-1], len(visited)\n", " for nb in maze.get_neighbors(cur):\n", " if nb not in visited:\n", " visited.add(nb)\n", " parent[nb] = cur\n", " queue.append(nb)\n", " return [], len(visited)" ] }, { "cell_type": "markdown", "id": "bae1222c-07f6-487d-ac05-7d09d7086e67", "metadata": {}, "source": [ "## 4.3. Оркестратор MazeSolver и статистика" ] }, { "cell_type": "code", "execution_count": null, "id": "247f19cf-4811-4dd8-8e35-03af6550d202", "metadata": {}, "outputs": [], "source": [ "class MazeSolver:\n", " def solve(self):\n", " start_time = time.perf_counter()\n", " path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)\n", " end_time = time.perf_counter()\n", " stats = SearchStats(\n", " time_ms=(end_time - start_time)*1000,\n", " visited_cells=visited,\n", " path_length=len(path),\n", " algorithm=self.strategy.__class__.__name__\n", " )\n", " return path, stats" ] }, { "cell_type": "markdown", "id": "8a7167d5-efd8-4b3c-a14e-5b6c1cba83ef", "metadata": {}, "source": [ "## 5. Результаты экспериментов\n", "\n", "**Тестовые лабиринты:**\n", "- `tiny.txt` – 10×10, простой путь\n", "- `medium.txt` – 50×50, с тупиками\n", "- `large.txt` – 100×100, запутанный\n", "- `empty.txt` – 100×100, без стен (старт в левом верхнем углу, выход в правом нижнем)\n", "- `no_exit.txt` – лабиринт без выхода\n", "\n", "> **Примечание:** средний лабиринт (`medium.txt`) не был включён в замеры из-за отсутствия корректного файла. Остальные четыре лабиринта соответствуют заданию.\n", "\n", "**Методика:** каждый алгоритм запущен 5 раз на каждом лабиринте, значения усреднены. Данные получены из `all_results.csv`.\n", "\n", "### 5.1. Таблица результатов\n", "\n", "| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |\n", "|----------------|----------------|------------|-----------------|------------|\n", "| tiny.txt | BFSStrategy | 0.2854 | 72.0 | 16.0 |\n", "| tiny.txt | DFSStrategy | 0.2665 | 71.0 | 72.0 |\n", "| tiny.txt | AStarStrategy | 0.3941 | 72.0 | 16.0 |\n", "| large.txt | BFSStrategy | 4.9520 | 1275.0 | 50.0 |\n", "| large.txt | DFSStrategy | 0.2159 | 49.0 | 50.0 |\n", "| large.txt | AStarStrategy | 0.3549 | 50.0 | 50.0 |\n", "| empty.txt | BFSStrategy | 24.3337 | 5049.0 | 100.0 |\n", "| empty.txt | DFSStrategy | 0.5570 | 99.0 | 100.0 |\n", "| empty.txt | AStarStrategy | 0.7525 | 100.0 | 100.0 |\n", "| no_exit.txt | BFSStrategy | 1.2649 | 324.0 | 0.0 |\n", "| no_exit.txt | DFSStrategy | 3.2304 | 324.0 | 0.0 |\n", "| no_exit.txt | AStarStrategy | 2.2239 | 324.0 | 0.0 |\n", "\n", "### 5.2. Графики\n", "\n", "Графики для каждого по отдельности сохранены в файле, тут предоставляю общее сравнение\n", "![Сводный график](summary_comparison.png)\n", "\n", "---\n", "\n", "## 6. Анализ эффективности алгоритмов\n", "\n", "### 6.1. Время выполнения\n", "- На **tiny.txt** все алгоритмы показали близкое время (~0.2–0.4 мс); DFS незначительно быстрее.\n", "- На **large.txt** BFS значительно медленнее (4.95 мс) из-за равномерного обхода, а DFS и A* работают быстро (~0.2–0.35 мс).\n", "- На **empty.txt** BFS крайне медленен (24.3 мс), поскольку вынужден обойти почти всё поле; DFS и A* справляются за ~0.5–0.75 мс.\n", "- В лабиринте **no_exit.txt** BFS быстрее (1.26 мс), а DFS и A* медленнее (2.2–3.2 мс) из-за разных порядков обхода.\n", "\n", "### 6.2. Количество посещённых клеток\n", "- **BFS** на `empty.txt` посещает 5049 клеток (почти половину поля), тогда как DFS и A* – всего ~100 клеток.\n", "- В `large.txt` BFS посещает 1275 клеток, DFS – 49, A* – 50. Это показывает, что A* (как и DFS) находит путь, исследуя лишь узкую область.\n", "- В `tiny.txt` все алгоритмы посещают около 70 клеток (различия незначительны).\n", "\n", "### 6.3. Длина найденного пути\n", "- **BFS** и **A*** находят кратчайший путь (16 шагов в tiny.txt, 50 в large.txt, 100 в empty.txt).\n", "- **DFS** в tiny.txt даёт очень длинный путь (72 шага вместо 16), в large.txt – 50 (совпал с оптимальным), в empty.txt – 100 (оптимально).\n", "- Таким образом, DFS не гарантирует кратчайший путь, хотя в некоторых случаях может его найти.\n", "\n", "### 6.4. Лабиринт без выхода\n", "- Все алгоритмы исследуют всю достижимую область (324 клетки). \n", "- Время различается: BFS – 1.26 мс, DFS – 3.23 мс, A* – 2.22 мс. Это связано с тем, что DFS «закапывается» в глубину, а A* тратит время на поддержание очереди с приоритетами.\n", "\n", "---\n", "\n", "## 7. Применимость паттернов и гибкость архитектуры\n", "\n", "### 7.1. Паттерн Builder\n", "- **Без него:** код загрузки был бы прямо в `main` или в конструкторе `Maze`. Пришлось бы переписывать при добавлении нового формата.\n", "- **С ним:** легко добавить `JSONMazeBuilder`, заменив всего одну строку в клиентском коде.\n", "\n", "### 7.2. Паттерн Strategy\n", "- **Без него:** пришлось бы использовать громоздкие `if-elif` для выбора алгоритма, дублировать код замера времени.\n", "- **С ним:** алгоритмы полностью независимы, можно динамически менять стратегию (например, на основе размера лабиринта).\n", "\n", "### 7.3. Паттерн Observer (опционально)\n", "- **Без него:** логирование и визуализация были бы вплетены в алгоритмы поиска.\n", "- **С ним:** наблюдатели подписываются на события, и логирование можно отключить или заменить на другой вывод без изменения `MazeSolver`.\n", "\n", "### 7.4. Паттерн Command (для пошагового движения)\n", "- **Без него:** отмена хода пришлось бы реализовывать вручную, что привело бы к дублированию кода.\n", "- **С ним:** команды легко складывать в историю, реализовать `undo` и `redo`.\n", "\n", "---\n", "\n", "## 8. Выводы\n", "\n", "- **ООП и паттерны проектирования** позволили создать гибкую, расширяемую и легко тестируемую программу.\n", "- **Builder** упростил добавление новых форматов лабиринтов.\n", "- **Strategy** сделал алгоритмы поиска взаимозаменяемыми и позволил проводить честное сравнение.\n", "- **Observer** и **Command** добавили возможности логирования и отмены действий без «засорения» основной логики.\n", "- Экспериментально подтверждены теоретические свойства алгоритмов: A* – лучший выбор для большинства случаев (оптимальный путь и высокая скорость), BFS – оптимален по длине пути, но медленен на больших картах, DFS – прост, но не даёт гарантий кратчайшего пути.\n", "\n", "**Итог:** использование паттернов повысило качество кода, уменьшило связанность и облегчило поддержку. Без них любое изменение (добавление нового алгоритма или формата файла) потребовало бы правки многих классов.\n", "\n", "--- \n" ] }, { "cell_type": "code", "execution_count": null, "id": "5e634d95-e0c0-4b8a-a330-02463cd085c8", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.5" } }, "nbformat": 4, "nbformat_minor": 5 }