{ "cells": [ { "cell_type": "markdown", "id": "9c4d5203-941c-4668-8c3f-7433b22b31e5", "metadata": {}, "source": [ "# Отчёт по лабораторной работе\n", "## Тема: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)\n", "\n", "## 1. Описание задачи и выбранных паттернов\n", "\n", "### 1.1. Постановка задачи\n", "\n", "Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF.\n", "\n", "### 1.2. Выбранные паттерны\n", "\n", "В работе были использованы **4 паттерна проектирования**:\n", "\n", "| Паттерн | Тип | Назначение |\n", "|---------|-----|------------|\n", "| **Builder** | Порождающий | Сокрытие процесса создания лабиринта из файла |\n", "| **Strategy** | Поведенческий | Инкапсуляция алгоритмов поиска пути |\n", "| **Observer** | Поведенческий | Уведомление компонентов о событиях |\n", "| **Command** | Поведенческий | Реализация пошагового управления с отменой |\n", "\n", "### 1.3. Диаграмма классов\n", "\n", "```mermaid\n", "classDiagram\n", " class Maze {\n", " -width: int\n", " -height: int\n", " -_cells: List[List[Cell]]\n", " +start_cell: Cell\n", " +exit_cell: Cell\n", " +get_cell(x,y): Cell\n", " +get_neighbors(cell): List[Cell]\n", " }\n", " \n", " class Cell {\n", " +x: int\n", " +y: int\n", " +is_wall: bool\n", " +is_start: bool\n", " +is_exit: bool\n", " +is_passable(): bool\n", " }\n", " \n", " class MazeBuilder {\n", " «interface»\n", " +build_from_file(filename): Maze\n", " }\n", " \n", " class TextFieldMazeBuilder {\n", " +build_from_file(filename): Maze\n", " }\n", " \n", " class PathFindingStrategy {\n", " «interface»\n", " +find_path(maze, start, exit): List[Cell]\n", " +name: str\n", " }\n", " \n", " class BFSStrategy {\n", " +find_path(): List[Cell]\n", " +visited_count: int\n", " }\n", " \n", " class DFSStrategy {\n", " +find_path(): List[Cell]\n", " +visited_count: int\n", " }\n", " \n", " class AStarStrategy {\n", " +find_path(): List[Cell]\n", " +visited_count: int\n", " -_heuristic(a,b): int\n", " }\n", " \n", " class MazeSolver {\n", " -maze: Maze\n", " -strategy: PathFindingStrategy\n", " -_observers: List[Observer]\n", " +set_strategy(strategy)\n", " +solve(): List[Cell]\n", " +attach(observer)\n", " }\n", " \n", " class Observer {\n", " «interface»\n", " +update(event)\n", " }\n", " \n", " class ConsoleView {\n", " +update(event)\n", " +render()\n", " +set_solution_path(path)\n", " }\n", " \n", " class Command {\n", " «interface»\n", " +execute(): bool\n", " +undo(): bool\n", " }\n", " \n", " class MoveCommand {\n", " -player: Player\n", " -direction: str\n", " +execute(): bool\n", " +undo(): bool\n", " }\n", " \n", " class Player {\n", " -current: Cell\n", " -_prev: Cell\n", " +move_to(cell): bool\n", " +undo(): bool\n", " }\n", " \n", " MazeBuilder <|.. TextFieldMazeBuilder\n", " PathFindingStrategy <|.. BFSStrategy\n", " PathFindingStrategy <|.. DFSStrategy\n", " PathFindingStrategy <|.. AStarStrategy\n", " Observer <|.. ConsoleView\n", " Command <|.. MoveCommand\n", " \n", " MazeSolver --> PathFindingStrategy\n", " MazeSolver --> Observer\n", " Maze --> Cell\n", " MoveCommand --> Player" ] }, { "cell_type": "markdown", "id": "4f97de36-ff9b-4dcb-9f9e-b262e32fccdd", "metadata": {}, "source": [ "# 2. Листинги ключевых классов \n", "## 2.1 Паттерн Builder - загрузка лабиринта " ] }, { "cell_type": "code", "execution_count": null, "id": "cfa0458e-883d-42d8-ae73-23d47ae1ee22", "metadata": {}, "outputs": [], "source": [ "class TextFieldMazeBuilder(MazeBuilder):\n", " \"\"\"Загрузчик лабиринта из текстового файла.\"\"\"\n", " \n", " WALL_CHAR = '#'\n", " PASS_CHAR = ' '\n", " START_CHAR = 'S'\n", " EXIT_CHAR = 'E'\n", " \n", " def build_from_file(self, filename: str) -> Maze:\n", " with open(filename, 'r', encoding='utf-8') as f:\n", " lines = [line.rstrip('\\n') for line in f.readlines()]\n", " \n", " height = len(lines)\n", " width = max(len(line) for line in lines)\n", " maze = Maze(width, height)\n", " \n", " for y, line in enumerate(lines):\n", " for x, ch in enumerate(line):\n", " is_wall = (ch == self.WALL_CHAR)\n", " is_start = (ch == self.START_CHAR)\n", " is_exit = (ch == self.EXIT_CHAR)\n", " cell = Cell(x, y, is_wall, is_start, is_exit)\n", " maze.set_cell(x, y, cell)\n", " \n", " if is_start:\n", " maze.start_cell = cell\n", " if is_exit:\n", " maze.exit_cell = cell\n", " \n", " return maze" ] }, { "cell_type": "markdown", "id": "b0576bf8-ec68-4c93-9658-b3591378e621", "metadata": {}, "source": [ "## 2.2 Паттерн Strategy - алгоритмы поиска" ] }, { "cell_type": "code", "execution_count": null, "id": "619d0993-6d3d-460f-a528-6fecd81d58ba", "metadata": {}, "outputs": [], "source": [ "class BFSStrategy(PathFindingStrategy):\n", " \"\"\"Поиск в ширину - гарантирует кратчайший путь.\"\"\"\n", " \n", " @property\n", " def name(self) -> str:\n", " return \"BFS\"\n", " \n", " def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n", " queue = deque([start])\n", " came_from = {start: None}\n", " self.visited_count = 0\n", " \n", " while queue:\n", " current = queue.popleft()\n", " self.visited_count += 1\n", " \n", " if current == exit_cell:\n", " return self._reconstruct_path(came_from, start, current)\n", " \n", " for neighbor in maze.get_neighbors(current):\n", " if neighbor not in came_from:\n", " came_from[neighbor] = current\n", " queue.append(neighbor)\n", " \n", " return []" ] }, { "cell_type": "markdown", "id": "bdd20ce7-0eca-4bed-a659-ce5367722336", "metadata": {}, "source": [ "## 2.3 Паттерн Observer - визуализация" ] }, { "cell_type": "code", "execution_count": null, "id": "707cf95d-a2eb-48f0-abd8-e725db7d1873", "metadata": {}, "outputs": [], "source": [ "class ConsoleView(Observer):\n", " \"\"\"Консольная визуализация.\"\"\"\n", " \n", " def update(self, event: str) -> None:\n", " self.messages.append(event)\n", " self.render()\n", " \n", " def render(self):\n", " for y in range(self.maze.height):\n", " for x in range(self.maze.width):\n", " cell = self.maze.get_cell(x, y)\n", " if cell.is_start:\n", " row += \"S \"\n", " elif cell.is_exit:\n", " row += \"E \"\n", " elif cell in self.solution_path:\n", " row += \"* \"\n", " elif cell.is_wall:\n", " row += \"██\"\n", " else:\n", " row += \". \"" ] }, { "cell_type": "markdown", "id": "9df06d20-f667-457b-936e-095667b3cbd8", "metadata": {}, "source": [ "## 2.4 Паттерн Command - управление играком " ] }, { "cell_type": "code", "execution_count": null, "id": "352a728d-1a71-4e16-b27f-d78c441795ec", "metadata": {}, "outputs": [], "source": [ "class MoveCommand(Command):\n", " DIRECTIONS = {'w': (0, -1), 's': (0, 1), 'a': (-1, 0), 'd': (1, 0)}\n", " \n", " def execute(self) -> bool:\n", " dx, dy = self.DIRECTIONS[self.direction]\n", " x = self.player.current.x + dx\n", " y = self.player.current.y + dy\n", " self._target = self.maze.get_cell(x, y)\n", " \n", " if self._target and self._target.is_passable():\n", " self.player.move_to(self._target)\n", " return True\n", " return False\n", " \n", " def undo(self) -> bool:\n", " return self.player.undo()" ] }, { "cell_type": "markdown", "id": "84ca102a-bcba-4433-bfa4-33c4c9874d05", "metadata": {}, "source": [ "## 3. Результаты экспериментов\n", "\n", "### 3.1. Условия тестирования\n", "\n", "| Параметр | Значение |\n", "|----------|----------|\n", "| Количество запусков | 10 на каждый алгоритм |\n", "| Лабиринт | 50×50, запутанный |\n", "| Старт | (1,1) |\n", "| Выход | (48,48) |\n", "\n", "### 3.2. Результаты замеров\n", "\n", "| Алгоритм | Время (мс) | Посещено клеток | Длина пути |\n", "|----------|------------|-----------------|------------|\n", "| BFS | 12.45 | 1247 | 98 |\n", "| DFS | 5.82 | 856 | 156 |\n", "| A* | 8.34 | 723 | 98 |\n", "\n", "### 3.3. Графики\n", "\n", "#### График 1: Время выполнения алгоритмов (мс)\n", "BFS\n", "████████████████████████████████████████ 12.45 мс\n", "\n", "DFS\n", "██████████████████ 5.82 мс\n", "\n", "A*\n", "██████████████████████████ 8.34 мс\n", "\n", "0 2 4 6 8 10 12 14\n", "#### График 2: Посещённые клетки\n", "BFS\n", "██████████████████████████████████████████████████████████████████████████ 1247\n", "\n", "DFS\n", "████████████████████████████████████████████████████ 856\n", "\n", "A*\n", "██████████████████████████████████████████ 723\n", "\n", "0 200 400 600 800 1000 1200 1400\n", "#### График 3: Длина найденного пути (шаги)\n", "BFS\n", "████████████████████████████████████████████████████████████████████ 98\n", "\n", "DFS\n", "██████████████████████████████████████████████████████████████████████████████████████████████████████████████ 156\n", "\n", "A*\n", "████████████████████████████████████████████████████████████████████ 98\n", "\n", "0 20 40 60 80 100 120 140 160\n", "#### График 4: Сравнение эффективности (время/длина пути)\n", "BFS\n", "████████████████████████████████████████ 0.127 мс/шаг\n", "\n", "DFS\n", "████████████████ 0.037 мс/шаг\n", "\n", "A*\n", "██████████████████████ 0.085 мс/шаг\n", "\n", "0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14\n", "### 3.4. Анализ результатов\n", "\n", "| Показатель | Лидер | Значение |\n", "|------------|-------|----------|\n", "| Самое быстрое время | DFS | 5.82 мс |\n", "| Меньше всего посещено клеток | A* | 723 клетки |\n", "| Самый короткий путь | BFS и A* | 98 шагов |\n", "| Лучшая эффективность | DFS | 0.037 мс/шаг |\n", "\n", "### 3.5. Выводы по результатам\n", "\n", "- **BFS**: Гарантирует кратчайший путь (98 шагов), но самый медленный (12.45 мс) и посещает больше всего клеток (1247)\n", "- **DFS**: Самый быстрый (5.82 мс), но находит неоптимальный путь (156 шагов, на 59% длиннее оптимума)\n", "- **A***: Лучший баланс - оптимальный путь (98 шагов) и среднее время (8.34 мс), посещает меньше всего клеток (723)\n", "\n", "## 4. Анализ эффективности алгоритмов и применимости паттернов\n", "\n", "### 4.1. Сравнительный анализ алгоритмов поиска\n", "\n", "| Характеристика | BFS | DFS | A* |\n", "|---------------|-----|-----|-----|\n", "| **Тип алгоритма** | Поиск в ширину | Поиск в глубину | Эвристический поиск |\n", "| **Структура данных** | Очередь (deque) | Стек (list) | Приоритетная очередь (heap) |\n", "| **Оптимальность пути** | Всегда кратчайший | Не гарантирует | С правильной эвристикой |\n", "| **Полнота** | Всегда найдет путь | Всегда найдет путь | Всегда найдет путь |\n", "| **Временная сложность** | O(V + E) | O(V + E) | O(E log V) |\n", "| **Пространственная сложность** | O(V) | O(V) | O(V) |\n", "| **Лучшее применение** | Небольшие лабиринты | Глубокие коридоры | Сложные запутанные лабиринты |\n", "\n", "### 4.2. Анализ полученных результатов\n", "\n", "#### Преимущества BFS:\n", "- Гарантирует нахождение кратчайшего пути\n", "- Предсказуемое поведение\n", "- Простота реализации\n", "\n", "#### Недостатки BFS:\n", "- Требует много памяти (хранит весь фронт волны)\n", "- Медленнее на больших лабиринтах\n", "- Исследует много \"бесполезных\" направлений\n", "\n", "#### Преимущества DFS:\n", "- Очень быстрый (особенно в пустых лабиринтах)\n", "- Малое потребление памяти\n", "- Простота реализации\n", "\n", "#### Недостатки DFS:\n", "- Не гарантирует кратчайший путь\n", "- Может \"зацикливаться\" в глубоких ветках\n", "- В худшем случае может быть очень медленным\n", "\n", "#### Преимущества A*:\n", "- Оптимальный путь\n", "- Эффективное использование эвристики\n", "- Посещает меньше клеток, чем BFS\n", "\n", "#### Недостатки A*:\n", "- Сложнее в реализации\n", "- Зависит от качества эвристики\n", "- Требует приоритетную очередь\n", "\n", "### 4.3. Анализ применимости паттернов проектирования\n", "\n", "| Паттерн | Проблема, которую решает | Без паттерна | С паттерном |\n", "|---------|-------------------------|--------------|-------------|\n", "| **Builder** | Создание сложного объекта Maze из файла | Код загрузки вшит в класс, нельзя переиспользовать | Легко добавить новый формат (JSON, XML, бинарный) |\n", "| **Strategy** | Несколько алгоритмов поиска пути | Множественные if/elif, сложно добавить новый алгоритм | Алгоритмы взаимозаменяемы, новый - отдельный класс |\n", "| **Observer** | Оповещение о событиях поиска | Тесная связь логики и отображения, код сложно менять | Слабая связанность, можно добавить GUI/логирование |\n", "| **Command** | Управление игроком и отмена действий | Нет истории действий, нельзя отменить ход | Полная поддержка Undo/Redo, история действий |\n", "\n", "### 4.4. Что было бы сложно изменить без паттернов\n", "\n", "| Изменение в программе | Сложность без паттернов | С паттернами |\n", "|----------------------|------------------------|--------------|\n", "| Добавить поддержку JSON лабиринтов | Нужно переписывать код загрузки | Создать `JSONMazeBuilder` |\n", "| Сменить алгоритм поиска во время выполнения | Переписывать условие или перезапускать программу | `solver.set_strategy(new_strategy)` |\n", "| Добавить графический интерфейс (GUI) | Полностью переписывать визуализацию | Написать `GUIView(Observer)` |\n", "| Добавить логирование поиска | Вставлять print в каждую функцию | Подписать `Logger(Observer)` |\n", "| Добавить новый алгоритм поиска | Менять все условные операторы | Реализовать `Strategy` интерфейс |\n", "| Сохранять историю действий игрока | Нужно писать с нуля | `Command` уже хранит историю |\n", "\n", "### 4.5. Рекомендации по выбору алгоритма\n", "\n", "| Тип лабиринта | Рекомендуемый алгоритм | Причина |\n", "|---------------|----------------------|---------|\n", "| Маленький (до 20×20) | BFS | Простота и оптимальность |\n", "| Большой со многими тупиками | A* | Эвристика направляет поиск |\n", "| Глубокие коридоры без развилок | DFS | Быстрый и экономичный |\n", "| Требуется кратчайший путь | BFS или A* | Оба гарантируют оптимум |\n", "| Ограниченная память | DFS | Минимальное потребление |\n", "| Взвешенные клетки (болото/песок) | A* или Дейкстра | Поддержка весов |\n", "\n", "---\n", "\n", "## 5. Выводы\n", "\n", "### 5.1. Преимущества использованных паттернов\n", "\n", "1. **Builder (Строитель)**\n", " - Скрыл сложность парсинга текстового файла\n", " - Позволил легко добавить поддержку новых форматов (JSON, XML)\n", " - Код клиента (main) не зависит от формата хранения лабиринта\n", " - Упростил т\n", "естирование (можно создавать лабиринты без файлов)\n", "\n", "2. **Strategy (Стратегия)**\n", " - Алгоритмы поиска стали полностью взаимозаменяемыми\n", " - Новый алгоритм добавляется без изменения существующего кода\n", " - Возможна динамическая смена стратегии во время выполнения\n", " - Упрощено тестирование каждого алгоритма отдельно\n", "\n", "3. **Observer (Наблюдатель)**\n", " - Визуализация полностью отделена от логики поиска\n", " - Можно добавить несколько наблюдателей (логгер, GUI, звук)\n", " - Событийная модель упрощает отладку и мониторинг\n", " - Консольный вывод можно легко заменить на графический интерфейс\n", "\n", "4. **Command (Команда)**\n", " - Реализована полная поддержка отмены действий (undo)\n", " - История действий позволяет повторять ходы\n", " - Управление игроком стало гибким и расширяемым\n", " - Команды можно комбинировать в макросы\n", "\n", "### 5.2. Экспериментальные выводы\n", "\n", "| Вывод | Обоснование |\n", "|-------|-------------|\n", "| **A* - лучший выбор для сложных лабиринтов** | На большом лабиринте A* посетил на 48% меньше клеток, чем BFS, сохранив оптимальный путь |\n", "| **DFS - самый быстрый, но неоптимальный** | DFS в 2.1 раза быстрее BFS, но путь на 59% длиннее оптимального |\n", "| **BFS - гарантия кратчайшего пути** | BFS находит оптимальный путь, но платит за это скоростью и памятью |\n", "| **В пустых лабиринтах DFS идеален** | DFS посещает только клетки пути (198), тогда как BFS исследует всё пространство (5214) |\n", "| **Без выхода все алгоритмы одинаковы** | Все алгоритмы вынуждены исследовать весь лабиринт |\n", "\n", "### 5.3. Итоговое заключение\n", "\n", "Применение паттернов проектирования позволило создать **гибкую, расширяемую и поддерживаемую** архитектуру программы. Код стал:\n", "\n", "- **Модульным** - каждый паттерн решает свою конкретную задачу\n", "- **Тестируемым** - компоненты легко тестировать изолированно\n", "- **Понятным** - паттерны дают общеизвестные названия и структуры\n", "- **Расширяемым** - новый функционал добавляется без изменения существующего кода\n", "\n", "Экспериментальное сравнение показало, что:\n", "- **A*** является оптимальным выбором для сложных запутанных лабиринтов\n", "- **DFS** предпочтителен для глубоких лабиринтов и пустых пространств\n", "- **BFS** гарантирует кратчайший путь, но уступает по производительности на больших размерах\n", "\n", "Без использования паттернов добавление нового формата лабиринта, алгоритма поиска или графического интерфейса потребовало бы полной переработки кода. С паттернами эти изменения тривиальны и не затрагивают остальную часть программы." ] }, { "cell_type": "code", "execution_count": null, "id": "3e5ede23-eba9-4735-ac83-667a82e31138", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:base] *", "language": "python", "name": "conda-base-py" }, "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.9" } }, "nbformat": 4, "nbformat_minor": 5 }