From 275f6b7297279abf173836379568f208d249cb5a Mon Sep 17 00:00:00 2001 From: FamutdinovMD Date: Mon, 25 May 2026 02:50:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BE=D1=82=D1=87=D0=B5=D1=82=202(=D0=B4=D0=BE?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D0=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- famutdinovmd/report.md | 373 ++++++++++++++++++++++++++++++----------- 1 file changed, 274 insertions(+), 99 deletions(-) diff --git a/famutdinovmd/report.md b/famutdinovmd/report.md index a3cd5e0..40de7e7 100644 --- a/famutdinovmd/report.md +++ b/famutdinovmd/report.md @@ -1,74 +1,239 @@ # Отчёт по лабораторной работе №2 -## Поиск выхода из лабиринта -## Цель работы -Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов с применением паттернов проектирования GoF. +## Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами) + +--- + +## 1. Описание задачи + +Разработать программу для поиска выхода из лабиринта с возможностью выбора алгоритма поиска, визуализации процесса и экспериментального сравнения алгоритмов. Программа должна загружать лабиринт из текстового файла, поддерживать алгоритмы BFS, DFS, A* и использовать паттерны проектирования GoF. + +--- + +## 2. Выбранные паттерны + +### 2.1 Builder (Строитель) +**Где:** `TextFileMazeBuilder` +**Зачем:** Сокрытие сложности создания лабиринта из файла +**Преимущество:** Легко добавить новый формат (JSON, XML) + +### 2.2 Strategy (Стратегия) +**Где:** `BFSStrategy`, `DFSStrategy`, `AStarStrategy` +**Зачем:** Возможность переключения алгоритмов во время выполнения +**Преимущество:** Новый алгоритм добавляется без изменения кода + +### 2.3 Observer (Наблюдатель) +**Где:** `ConsoleView` +**Зачем:** Отделение визуализации от логики поиска +**Преимущество:** Можно добавить GUI без изменения MazeSolver + +### 2.4 Command (Команда) +**Где:** `MoveCommand`, `Player` +**Зачем:** Поддержка отмены действий при ручном управлении +**Преимущество:** История действий и возможность Undo + +--- + +## 3. Диаграмма классов (Mermaid) ```python -import sys -sys.path.append('.') +classDiagram + class Maze { + -width, height + -_cells[][] + -start, exit + +get_cell(x,y) + +get_neighbors(cell) + } + + class Cell { + -x, y + -is_wall + -is_start + -is_exit + +is_passable() + } + + class MazeBuilder { + <> + +build_from_file(filename) + } + + class TextFileMazeBuilder { + +build_from_file(filename) + } + + class PathFindingStrategy { + <> + +find_path(maze, start, exit) + } + + class BFSStrategy + class DFSStrategy + class AStarStrategy + + class MazeSolver { + -maze + -strategy + +set_strategy() + +solve() + } + + class Observer { + <> + +update(event, data) + } + + class ConsoleView { + +render(maze, path) + +update(event, data) + } + + class Command { + <> + +execute() + +undo() + } + + class MoveCommand { + -player + -direction + +execute() + +undo() + } + + MazeBuilder <|.. TextFileMazeBuilder + PathFindingStrategy <|.. BFSStrategy + PathFindingStrategy <|.. DFSStrategy + PathFindingStrategy <|.. AStarStrategy + MazeSolver --> PathFindingStrategy + Observer <|.. ConsoleView + Command <|.. MoveCommand -from builders import TextFileMazeBuilder -from strategies import BFSStrategy, DFSStrategy, AStarStrategy -from solver import MazeSolver -from observers import ConsoleView -import time -import pandas as pd -import matplotlib.pyplot as plt -builder = TextFileMazeBuilder() -maze = builder.build_from_file("mazes/small.txt") -print(f"Размер лабиринта: {maze.width}×{maze.height}") -print(maze) -## Паттерн Builder (Строитель) - -```python class TextFileMazeBuilder(MazeBuilder): WALL_CHAR = '#' START_CHAR = 'S' EXIT_CHAR = 'E' def build_from_file(self, filename: str) -> Maze: - with open(filename, 'r') as f: - lines = f.readlines() - # парсинг и создание лабиринта - return maze + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f.readlines()] -**Ячейка 6 (паттерн Strategy):** -```markdown -## Паттерн Strategy (Стратегия) - -Реализованы три алгоритма поиска пути: -- **BFS** - поиск в ширину (гарантирует кратчайший путь) -- **DFS** - поиск в глубину (быстрый, но не оптимальный) -- **A*** - с эвристикой (манхэттенское расстояние) -strategies = { - "BFS": BFSStrategy(), - "DFS": DFSStrategy(), - "A*": AStarStrategy() -} - -results = [] - -for name, strategy in strategies.items(): - solver = MazeSolver(maze, strategy) - start_time = time.perf_counter() - path, stats = solver.solve() - end_time = time.perf_counter() + height = len(lines) + width = max(len(line) for line in lines) + maze = Maze(width, height) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if x >= width: + continue + cell = Cell(x, y) + if ch == self.WALL_CHAR: + cell.is_wall = True + elif ch == self.START_CHAR: + cell.is_start = True + elif ch == self.EXIT_CHAR: + cell.is_exit = True + maze.set_cell(x, y, cell) + + if maze.start is None: + raise ValueError("Нет стартовой клетки (S)") + if maze.exit is None: + raise ValueError("Нет выхода (E)") + + return maze + class TextFileMazeBuilder(MazeBuilder): + WALL_CHAR = '#' + START_CHAR = 'S' + EXIT_CHAR = 'E' - results.append({ - "Алгоритм": name, - "Время (мс)": stats.time_ms, - "Длина пути": stats.path_length, - "Посещено клеток": stats.visited_cells - }) + def build_from_file(self, filename: str) -> Maze: + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + + height = len(lines) + width = max(len(line) for line in lines) + maze = Maze(width, height) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if x >= width: + continue + cell = Cell(x, y) + if ch == self.WALL_CHAR: + cell.is_wall = True + elif ch == self.START_CHAR: + cell.is_start = True + elif ch == self.EXIT_CHAR: + cell.is_exit = True + maze.set_cell(x, y, cell) + + if maze.start is None: + raise ValueError("Нет стартовой клетки (S)") + if maze.exit is None: + raise ValueError("Нет выхода (E)") + + return maze + class BFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit_cell): + queue = deque([start]) + visited = {start} + parent = {start: None} + + while queue: + current = queue.popleft() + if current == exit_cell: + return self._reconstruct_path(parent, current) + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + queue.append(neighbor) + return [] - print(f"{name}: {stats}") - -df = pd.DataFrame(results) -df -## Паттерн Observer (Наблюдатель) - -```python -class ConsoleView(Observer): + def _reconstruct_path(self, parent, current): + path = [] + while current: + path.append(current) + current = parent[current] + return list(reversed(path)) + class DFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit_cell): + stack = [(start, [start])] + visited = {start} + + while stack: + current, path = stack.pop() + if current == exit_cell: + return path + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + stack.append((neighbor, path + [neighbor])) + return [] + class AStarStrategy(PathFindingStrategy): + def _heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, maze, start, exit_cell): + counter = 0 + open_set = [(self._heuristic(start, exit_cell), counter, start)] + g_score = {start: 0} + parent = {start: None} + + while open_set: + _, _, current = heappop(open_set) + if current == exit_cell: + return self._reconstruct_path(parent, current) + for neighbor in maze.get_neighbors(current): + tentative_g = g_score[current] + 1 + if neighbor not in g_score or tentative_g < g_score[neighbor]: + parent[neighbor] = current + g_score[neighbor] = tentative_g + counter += 1 + f = tentative_g + self._heuristic(neighbor, exit_cell) + heappush(open_set, (f, counter, neighbor)) + return [] + class ConsoleView(Observer): def render(self, maze, path=None): path_set = set(path) if path else set() print("\n+" + "-" * maze.width + "+") @@ -88,46 +253,56 @@ class ConsoleView(Observer): row.append(' ') print("|" + ''.join(row) + "|") print("+" + "-" * maze.width + "+") -**Ячейка 9 (визуализация пути):** -```python -view = ConsoleView() -solver = MazeSolver(maze, BFSStrategy()) -path, stats = solver.solve() -view.render(maze, path=path) -print(f"Найден путь длиной {len(path)} клеток") -data = pd.read_csv("experiment_results.csv") -data -fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + + def update(self, event, data): + if event == "maze_loaded": + self.render(data.get('maze')) + elif event == "path_found": + self.render(data.get('maze'), data.get('path')) + class MoveCommand(Command): + def __init__(self, player, maze, direction): + self.player = player + self.maze = maze + self.direction = direction + self.previous_cell = None + + def execute(self): + self.previous_cell = self.player.current_cell + dx, dy = self.direction + new_cell = self.maze.get_cell( + self.player.current_cell.x + dx, + self.player.current_cell.y + dy + ) + if new_cell and new_cell.is_passable(): + self.player.move_to(new_cell) + return True + return False + + def undo(self): + if self.previous_cell: + self.player.move_to(self.previous_cell) + class MazeSolver: + def __init__(self, maze, strategy=None): + self.maze = maze + self._strategy = strategy + + def set_strategy(self, strategy): + self._strategy = strategy + + def solve(self): + if not self._strategy: + raise ValueError("Стратегия не установлена") + + start_time = time.perf_counter() + path = self._strategy.find_path(self.maze, self.maze.start, self.maze.exit) + end_time = time.perf_counter() + + stats = SearchStats( + time_ms=(end_time - start_time) * 1000, + visited_cells=len(path) if path else 0, + path_length=len(path) if path else 0, + path_found=bool(path) + ) + return path, stats -algorithms = data['strategy'].unique() -mazes = data['maze_file'].unique() -for algo in algorithms: - algo_data = data[data['strategy'] == algo] - axes[0].plot(algo_data['maze_file'], algo_data['time_mean'], marker='o', label=algo) - axes[1].plot(algo_data['maze_file'], algo_data['path_length_mean'], marker='o', label=algo) - -axes[0].set_title('Время выполнения (мс)') -axes[0].legend() -axes[1].set_title('Длина пути') -axes[1].legend() -plt.xticks(rotation=45) -plt.tight_layout() -plt.savefig('report_graphs.png') -plt.show() -## Выводы - -В ходе лабораторной работы были реализованы паттерны: - -| Паттерн | Где используется | Преимущество | -|---------|-----------------|--------------| -| **Builder** | `TextFileMazeBuilder` | Сокрытие сложности создания лабиринта | -| **Strategy** | `BFS/DFS/A*` | Легкая смена алгоритмов | -| **Observer** | `ConsoleView` | Отделение визуализации от логики | -| **Command** | `MoveCommand` | Поддержка отмены действий | - -### Сравнение алгоритмов - -- **BFS** - оптимальный по длине пути, стабильное время -- **DFS** - самый быстрый, но путь длиннее -- **A*** - баланс скорости и оптимальности