376 lines
20 KiB
Plaintext
376 lines
20 KiB
Plaintext
{
|
||
"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",
|
||
" <<interface>>\n",
|
||
" +buildFromFile(filename)\n",
|
||
" }\n",
|
||
" class TextFileMazeBuilder {\n",
|
||
" +buildFromFile(filename)\n",
|
||
" }\n",
|
||
" class PathFindingStrategy {\n",
|
||
" <<interface>>\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",
|
||
" <<interface>>\n",
|
||
" +update(event_type, data)\n",
|
||
" }\n",
|
||
" class ConsoleLogger {\n",
|
||
" +update(event_type, data)\n",
|
||
" }\n",
|
||
" class Command {\n",
|
||
" <<interface>>\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",
|
||
"\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
|
||
}
|