отчет 2(дополнил)

This commit is contained in:
FamutdinovMD 2026-05-25 02:50:22 +03:00
parent 8ce584051b
commit 275f6b7297

View File

@ -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)
}
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 (Строитель)
class Cell {
-x, y
-is_wall
-is_start
-is_exit
+is_passable()
}
class MazeBuilder {
<<interface>>
+build_from_file(filename)
}
class TextFileMazeBuilder {
+build_from_file(filename)
}
class PathFindingStrategy {
<<interface>>
+find_path(maze, start, exit)
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class MazeSolver {
-maze
-strategy
+set_strategy()
+solve()
}
class Observer {
<<interface>>
+update(event, data)
}
class ConsoleView {
+render(maze, path)
+update(event, data)
}
class Command {
<<interface>>
+execute()
+undo()
}
class MoveCommand {
-player
-direction
+execute()
+undo()
}
MazeBuilder <|.. TextFileMazeBuilder
PathFindingStrategy <|.. BFSStrategy
PathFindingStrategy <|.. DFSStrategy
PathFindingStrategy <|.. AStarStrategy
MazeSolver --> PathFindingStrategy
Observer <|.. ConsoleView
Command <|.. MoveCommand
```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()
# парсинг и создание лабиринта
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 TextFileMazeBuilder(MazeBuilder):
WALL_CHAR = '#'
START_CHAR = 'S'
EXIT_CHAR = 'E'
**Ячейка 6 (паттерн Strategy):**
```markdown
## Паттерн Strategy (Стратегия)
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()]
Реализованы три алгоритма поиска пути:
- **BFS** - поиск в ширину (гарантирует кратчайший путь)
- **DFS** - поиск в глубину (быстрый, но не оптимальный)
- **A*** - с эвристикой (манхэттенское расстояние)
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy()
}
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
results = []
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)
for name, strategy in strategies.items():
solver = MazeSolver(maze, strategy)
start_time = time.perf_counter()
path, stats = solver.solve()
end_time = time.perf_counter()
if maze.start is None:
raise ValueError("Нет стартовой клетки (S)")
if maze.exit is None:
raise ValueError("Нет выхода (E)")
results.append({
"Алгоритм": name,
"Время (мс)": stats.time_ms,
"Длина пути": stats.path_length,
"Посещено клеток": stats.visited_cells
})
return maze
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
queue = deque([start])
visited = {start}
parent = {start: None}
print(f"{name}: {stats}")
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 []
df = pd.DataFrame(results)
df
## Паттерн 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}
```python
class ConsoleView(Observer):
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))
algorithms = data['strategy'].unique()
mazes = data['maze_file'].unique()
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
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)
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
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()
## Выводы
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
| Паттерн | Где используется | Преимущество |
|---------|-----------------|--------------|
| **Builder** | `TextFileMazeBuilder` | Сокрытие сложности создания лабиринта |
| **Strategy** | `BFS/DFS/A*` | Легкая смена алгоритмов |
| **Observer** | `ConsoleView` | Отделение визуализации от логики |
| **Command** | `MoveCommand` | Поддержка отмены действий |
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
### Сравнение алгоритмов
- **BFS** - оптимальный по длине пути, стабильное время
- **DFS** - самый быстрый, но путь длиннее
- **A*** - баланс скорости и оптимальности