forked from UNN/2026-rff_mp
отчет 2(дополнил)
This commit is contained in:
parent
8ce584051b
commit
275f6b7297
|
|
@ -1,74 +1,239 @@
|
||||||
# Отчёт по лабораторной работе №2
|
# Отчёт по лабораторной работе №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
|
```python
|
||||||
import sys
|
classDiagram
|
||||||
sys.path.append('.')
|
class Maze {
|
||||||
|
-width, height
|
||||||
|
-_cells[][]
|
||||||
|
-start, exit
|
||||||
|
+get_cell(x,y)
|
||||||
|
+get_neighbors(cell)
|
||||||
|
}
|
||||||
|
|
||||||
from builders import TextFileMazeBuilder
|
class Cell {
|
||||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
-x, y
|
||||||
from solver import MazeSolver
|
-is_wall
|
||||||
from observers import ConsoleView
|
-is_start
|
||||||
import time
|
-is_exit
|
||||||
import pandas as pd
|
+is_passable()
|
||||||
import matplotlib.pyplot as plt
|
}
|
||||||
builder = TextFileMazeBuilder()
|
|
||||||
maze = builder.build_from_file("mazes/small.txt")
|
class MazeBuilder {
|
||||||
print(f"Размер лабиринта: {maze.width}×{maze.height}")
|
<<interface>>
|
||||||
print(maze)
|
+build_from_file(filename)
|
||||||
## Паттерн Builder (Строитель)
|
}
|
||||||
|
|
||||||
|
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):
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
WALL_CHAR = '#'
|
WALL_CHAR = '#'
|
||||||
START_CHAR = 'S'
|
START_CHAR = 'S'
|
||||||
EXIT_CHAR = 'E'
|
EXIT_CHAR = 'E'
|
||||||
|
|
||||||
def build_from_file(self, filename: str) -> Maze:
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
with open(filename, 'r') as f:
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
lines = f.readlines()
|
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
|
return maze
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
WALL_CHAR = '#'
|
||||||
|
START_CHAR = 'S'
|
||||||
|
EXIT_CHAR = 'E'
|
||||||
|
|
||||||
**Ячейка 6 (паттерн Strategy):**
|
def build_from_file(self, filename: str) -> Maze:
|
||||||
```markdown
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
## Паттерн Strategy (Стратегия)
|
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||||
|
|
||||||
Реализованы три алгоритма поиска пути:
|
height = len(lines)
|
||||||
- **BFS** - поиск в ширину (гарантирует кратчайший путь)
|
width = max(len(line) for line in lines)
|
||||||
- **DFS** - поиск в глубину (быстрый, но не оптимальный)
|
maze = Maze(width, height)
|
||||||
- **A*** - с эвристикой (манхэттенское расстояние)
|
|
||||||
strategies = {
|
|
||||||
"BFS": BFSStrategy(),
|
|
||||||
"DFS": DFSStrategy(),
|
|
||||||
"A*": AStarStrategy()
|
|
||||||
}
|
|
||||||
|
|
||||||
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():
|
if maze.start is None:
|
||||||
solver = MazeSolver(maze, strategy)
|
raise ValueError("Нет стартовой клетки (S)")
|
||||||
start_time = time.perf_counter()
|
if maze.exit is None:
|
||||||
path, stats = solver.solve()
|
raise ValueError("Нет выхода (E)")
|
||||||
end_time = time.perf_counter()
|
|
||||||
|
|
||||||
results.append({
|
return maze
|
||||||
"Алгоритм": name,
|
class BFSStrategy(PathFindingStrategy):
|
||||||
"Время (мс)": stats.time_ms,
|
def find_path(self, maze, start, exit_cell):
|
||||||
"Длина пути": stats.path_length,
|
queue = deque([start])
|
||||||
"Посещено клеток": stats.visited_cells
|
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)
|
def _reconstruct_path(self, parent, current):
|
||||||
df
|
path = []
|
||||||
## Паттерн Observer (Наблюдатель)
|
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
|
while stack:
|
||||||
class ConsoleView(Observer):
|
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):
|
def render(self, maze, path=None):
|
||||||
path_set = set(path) if path else set()
|
path_set = set(path) if path else set()
|
||||||
print("\n+" + "-" * maze.width + "+")
|
print("\n+" + "-" * maze.width + "+")
|
||||||
|
|
@ -88,46 +253,56 @@ class ConsoleView(Observer):
|
||||||
row.append(' ')
|
row.append(' ')
|
||||||
print("|" + ''.join(row) + "|")
|
print("|" + ''.join(row) + "|")
|
||||||
print("+" + "-" * maze.width + "+")
|
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()
|
def update(self, event, data):
|
||||||
mazes = data['maze_file'].unique()
|
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:
|
def execute(self):
|
||||||
algo_data = data[data['strategy'] == algo]
|
self.previous_cell = self.player.current_cell
|
||||||
axes[0].plot(algo_data['maze_file'], algo_data['time_mean'], marker='o', label=algo)
|
dx, dy = self.direction
|
||||||
axes[1].plot(algo_data['maze_file'], algo_data['path_length_mean'], marker='o', label=algo)
|
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('Время выполнения (мс)')
|
def undo(self):
|
||||||
axes[0].legend()
|
if self.previous_cell:
|
||||||
axes[1].set_title('Длина пути')
|
self.player.move_to(self.previous_cell)
|
||||||
axes[1].legend()
|
class MazeSolver:
|
||||||
plt.xticks(rotation=45)
|
def __init__(self, maze, strategy=None):
|
||||||
plt.tight_layout()
|
self.maze = maze
|
||||||
plt.savefig('report_graphs.png')
|
self._strategy = strategy
|
||||||
plt.show()
|
|
||||||
## Выводы
|
|
||||||
|
|
||||||
В ходе лабораторной работы были реализованы паттерны:
|
def set_strategy(self, strategy):
|
||||||
|
self._strategy = strategy
|
||||||
|
|
||||||
| Паттерн | Где используется | Преимущество |
|
def solve(self):
|
||||||
|---------|-----------------|--------------|
|
if not self._strategy:
|
||||||
| **Builder** | `TextFileMazeBuilder` | Сокрытие сложности создания лабиринта |
|
raise ValueError("Стратегия не установлена")
|
||||||
| **Strategy** | `BFS/DFS/A*` | Легкая смена алгоритмов |
|
|
||||||
| **Observer** | `ConsoleView` | Отделение визуализации от логики |
|
start_time = time.perf_counter()
|
||||||
| **Command** | `MoveCommand` | Поддержка отмены действий |
|
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*** - баланс скорости и оптимальности
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user