отчет 2(дополнил)
This commit is contained in:
parent
8ce584051b
commit
275f6b7297
|
|
@ -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*** - баланс скорости и оптимальности
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user