forked from UNN/2026-rff_mp
Добавил второе задание
This commit is contained in:
parent
73107e0565
commit
3f683b674e
239
VasilevIA/lab2/codes/maze.py
Normal file
239
VasilevIA/lab2/codes/maze.py
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import heapq
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class Cell:
|
||||
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.is_wall = is_wall
|
||||
self.is_start = is_start
|
||||
self.is_exit = is_exit
|
||||
|
||||
def is_passable(self):
|
||||
return not self.is_wall
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __repr__(self):
|
||||
return f"Cell({self.x},{self.y})"
|
||||
|
||||
|
||||
class Maze:
|
||||
def __init__(self, cells, width, height, start, exit_cell):
|
||||
self.cells = cells
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.start = start
|
||||
self.exit = exit_cell
|
||||
|
||||
def get_cell(self, x, y):
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
return self.cells[y][x]
|
||||
return None
|
||||
|
||||
def get_neighbors(self, cell):
|
||||
result = []
|
||||
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||
n = self.get_cell(cell.x + dx, cell.y + dy)
|
||||
if n and n.is_passable():
|
||||
result.append(n)
|
||||
return result
|
||||
|
||||
def render(self, path=None):
|
||||
path_set = set(path) if path else set()
|
||||
lines = []
|
||||
for row in self.cells:
|
||||
line = ""
|
||||
for cell in row:
|
||||
if cell.is_start:
|
||||
line += " S"
|
||||
elif cell.is_exit:
|
||||
line += " E"
|
||||
elif cell.is_wall:
|
||||
line += "##"
|
||||
elif cell in path_set:
|
||||
line += " ."
|
||||
else:
|
||||
line += " "
|
||||
lines.append(line)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
pass
|
||||
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
lines = [l.rstrip("\n") for l in f]
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(l) for l in lines)
|
||||
cells = []
|
||||
start = exit_cell = None
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
row = []
|
||||
for x in range(width):
|
||||
ch = line[x] if x < len(line) else " "
|
||||
is_wall = ch == "#"
|
||||
is_start = ch == "S"
|
||||
is_exit = ch == "E"
|
||||
c = Cell(x, y, is_wall, is_start, is_exit)
|
||||
if is_start:
|
||||
start = c
|
||||
if is_exit:
|
||||
exit_cell = c
|
||||
row.append(c)
|
||||
cells.append(row)
|
||||
|
||||
if not start or not exit_cell:
|
||||
raise ValueError("Maze must have S and E")
|
||||
return Maze(cells, width, height, start, exit_cell)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
strategy: str
|
||||
time_ms: float
|
||||
visited: int
|
||||
path_length: int
|
||||
path: List[Cell] = field(default_factory=list)
|
||||
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
_visited = 0
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
@abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, end: Cell) -> List[Cell]:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _build_path(parent, start, end):
|
||||
path, cur = [], end
|
||||
while cur:
|
||||
path.append(cur)
|
||||
cur = parent.get(cur)
|
||||
path.reverse()
|
||||
return path if path and path[0] == start else []
|
||||
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
@property
|
||||
def name(self):
|
||||
return "BFS"
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
queue = deque([start])
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
while queue:
|
||||
cur = queue.popleft()
|
||||
visited += 1
|
||||
if cur == end:
|
||||
self._visited = visited
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
if nb not in parent:
|
||||
parent[nb] = cur
|
||||
queue.append(nb)
|
||||
self._visited = visited
|
||||
return []
|
||||
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
@property
|
||||
def name(self):
|
||||
return "DFS"
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
stack = [start]
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
while stack:
|
||||
cur = stack.pop()
|
||||
visited += 1
|
||||
if cur == end:
|
||||
self._visited = visited
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
if nb not in parent:
|
||||
parent[nb] = cur
|
||||
stack.append(nb)
|
||||
self._visited = visited
|
||||
return []
|
||||
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
@property
|
||||
def name(self):
|
||||
return "A*"
|
||||
|
||||
@staticmethod
|
||||
def _h(a, b):
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
counter = 0
|
||||
heap = [(0, counter, start)]
|
||||
parent = {start: None}
|
||||
g = {start: 0}
|
||||
closed = set()
|
||||
visited = 0
|
||||
while heap:
|
||||
_, _, cur = heapq.heappop(heap)
|
||||
if cur in closed:
|
||||
continue
|
||||
closed.add(cur)
|
||||
visited += 1
|
||||
if cur == end:
|
||||
self._visited = visited
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
if nb in closed:
|
||||
continue
|
||||
ng = g[cur] + 1
|
||||
if ng < g.get(nb, float("inf")):
|
||||
g[nb] = ng
|
||||
counter += 1
|
||||
heapq.heappush(heap, (ng + self._h(nb, end), counter, nb))
|
||||
parent[nb] = cur
|
||||
self._visited = visited
|
||||
return []
|
||||
|
||||
|
||||
class MazeSolver:
|
||||
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
|
||||
self.maze = maze
|
||||
self.strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def solve(self) -> SearchStats:
|
||||
t0 = time.perf_counter()
|
||||
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||
t1 = time.perf_counter()
|
||||
return SearchStats(
|
||||
strategy=self.strategy.name,
|
||||
time_ms=(t1 - t0) * 1000,
|
||||
visited=self.strategy._visited,
|
||||
path_length=len(path),
|
||||
path=path
|
||||
)
|
||||
78
VasilevIA/lab2/codes/maze_generator.py
Normal file
78
VasilevIA/lab2/codes/maze_generator.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import os
|
||||
import random
|
||||
|
||||
def _backtracker(width, height, seed=42):
|
||||
rng = random.Random(seed)
|
||||
cw = (width - 1) // 2
|
||||
ch = (height - 1) // 2
|
||||
grid = [["#"] * width for _ in range(height)]
|
||||
visited = [[False] * cw for _ in range(ch)]
|
||||
stack = [(0, 0)]
|
||||
visited[0][0] = True
|
||||
grid[1][1] = " "
|
||||
while stack:
|
||||
cx, cy = stack[-1]
|
||||
gx, gy = cx * 2 + 1, cy * 2 + 1
|
||||
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||
rng.shuffle(dirs)
|
||||
moved = False
|
||||
for dx, dy in dirs:
|
||||
nx, ny = cx + dx, cy + dy
|
||||
if 0 <= nx < cw and 0 <= ny < ch and not visited[ny][nx]:
|
||||
visited[ny][nx] = True
|
||||
grid[gy + dy][gx + dx] = " "
|
||||
grid[ny * 2 + 1][nx * 2 + 1] = " "
|
||||
stack.append((nx, ny))
|
||||
moved = True
|
||||
break
|
||||
if not moved:
|
||||
stack.pop()
|
||||
grid[1][1] = "S"
|
||||
grid[height - 2][width - 2] = "E"
|
||||
return grid
|
||||
|
||||
|
||||
def _empty(width, height):
|
||||
grid = [["#"] * width for _ in range(height)]
|
||||
for y in range(1, height - 1):
|
||||
for x in range(1, width - 1):
|
||||
grid[y][x] = " "
|
||||
grid[1][1] = "S"
|
||||
grid[height - 2][width - 2] = "E"
|
||||
return grid
|
||||
|
||||
|
||||
def _no_exit(width=11, height=11):
|
||||
grid = _backtracker(width, height, seed=99)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if grid[y][x] == "E":
|
||||
grid[y][x] = "#"
|
||||
grid[1][width - 2] = "E"
|
||||
for dy in [-1, 0, 1]:
|
||||
for dx in [-1, 0, 1]:
|
||||
ny, nx = 1 + dy, (width - 2) + dx
|
||||
if 0 <= ny < height and 0 <= nx < width and grid[ny][nx] != "E":
|
||||
grid[ny][nx] = "#"
|
||||
return grid
|
||||
|
||||
|
||||
def _save(grid, path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
for row in grid:
|
||||
f.write("".join(row) + "\n")
|
||||
|
||||
|
||||
def generate_all(folder="mazes"):
|
||||
mazes = {
|
||||
"small.txt": _backtracker(11, 11, seed=1),
|
||||
"medium.txt": _backtracker(51, 51, seed=2),
|
||||
"large.txt": _backtracker(101, 101, seed=3),
|
||||
"empty.txt": _empty(51, 21),
|
||||
"no_exit.txt": _no_exit(11, 11),
|
||||
"sample.txt": _backtracker(15, 15, seed=5),
|
||||
}
|
||||
for name, grid in mazes.items():
|
||||
_save(grid, os.path.join(folder, name))
|
||||
print(f"Mazes saved to {folder}/")
|
||||
BIN
VasilevIA/lab2/docs/benchmark_plot.png
Normal file
BIN
VasilevIA/lab2/docs/benchmark_plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
BIN
VasilevIA/lab2/docs/mermaid.png
Normal file
BIN
VasilevIA/lab2/docs/mermaid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
193
VasilevIA/lab2/docs/report2.md
Normal file
193
VasilevIA/lab2/docs/report2.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
# Отчёт: Поиск выхода из лабиринта
|
||||
## 1. Описание задачи и выбранных паттернов
|
||||
### Задача
|
||||
|
||||
Разработать программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода тремя алгоритмами (BFS, DFS, A*), визуализации найденного пути и экспериментального сравнения алгоритмов по времени, числу посещённых клеток и длине пути.
|
||||
|
||||
### Структура файлов
|
||||
|
||||
```
|
||||
02/
|
||||
main.py - точка запуска
|
||||
codes/
|
||||
maze.py - все классы (Cell, Maze, Builder, Strategy, Solver)
|
||||
maze_generator.py - генерация тестовых лабиринтов
|
||||
mazes/ - текстовые файлы лабиринтов
|
||||
results/
|
||||
results_maze.csv - результаты экспериментов
|
||||
benchmark_plot.png - графики
|
||||
docs/
|
||||
report1.md - отчёт
|
||||
mermaid.png - диаграмма классов
|
||||
```
|
||||
|
||||
### Применённые паттерны проектирования
|
||||
**1. Builder** - класс `TextFileMazeBuilder` реализует интерфейс `MazeBuilder`.
|
||||
|
||||
Построение лабиринта из файла включает несколько шагов: чтение строк, обход символов, создание объектов `Cell`, поиск стартовой и конечной клетки. Без Builder вся эта логика оказалась бы в `main.py` или в конструкторе `Maze`. Builder скрывает детали создания от клиента. Если понадобится загружать лабиринт из JSON или бинарного файла - достаточно написать новый класс, реализующий тот же интерфейс `MazeBuilder`.
|
||||
|
||||
**2. Strategy** - классы `BFSStrategy`, `DFSStrategy`, `AStarStrategy` реализуют интерфейс `PathFindingStrategy`.
|
||||
|
||||
Алгоритм поиска можно менять во время работы программы через `MazeSolver.set_strategy()`, не трогая остальной код. Добавление нового алгоритма - это написание одного нового класса с методом `find_path()`. Без Strategy в `solve()` пришлось бы писать if/elif для каждого алгоритма.
|
||||
|
||||
**3. Observer** - интерфейс `Observer` с методом `update(event)`.
|
||||
|
||||
`MazeSolver` хранит список наблюдателей и уведомляет их при событиях `search_started`, `path_found`, `path_not_found`. Это позволяет добавлять отображение в консоль, запись в лог или GUI-уведомления, не меняя код солвера. Слабая связанность: солвер не знает, кто его слушает.
|
||||
|
||||
### Диаграмма классов
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 2. Листинги ключевых классов
|
||||
### Cell и Maze
|
||||
|
||||
```python
|
||||
class Cell:
|
||||
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.is_wall = is_wall
|
||||
self.is_start = is_start
|
||||
self.is_exit = is_exit
|
||||
|
||||
def is_passable(self):
|
||||
return not self.is_wall
|
||||
|
||||
class Maze:
|
||||
def get_neighbors(self, cell):
|
||||
result = []
|
||||
for dx, dy in [(0,-1),(0,1),(-1,0),(1,0)]:
|
||||
n = self.get_cell(cell.x + dx, cell.y + dy)
|
||||
if n and n.is_passable():
|
||||
result.append(n)
|
||||
return result
|
||||
```
|
||||
|
||||
### Паттерн Builder
|
||||
```python
|
||||
class MazeBuilder(ABC):
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
pass
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
lines = [l.rstrip("\n") for l in f]
|
||||
# ... парсинг символов, создание Cell, поиск S и E
|
||||
return Maze(cells, width, height, start, exit_cell)
|
||||
```
|
||||
|
||||
### Паттерн Strategy - алгоритм A*
|
||||
```python
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
@staticmethod
|
||||
def _h(a, b):
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
heap = [(0, 0, start)]
|
||||
parent = {start: None}
|
||||
g = {start: 0}
|
||||
closed = set()
|
||||
while heap:
|
||||
_, _, cur = heapq.heappop(heap)
|
||||
if cur in closed:
|
||||
continue
|
||||
closed.add(cur)
|
||||
if cur == end:
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
ng = g[cur] + 1
|
||||
if ng < g.get(nb, float("inf")):
|
||||
g[nb] = ng
|
||||
heapq.heappush(heap, (ng + self._h(nb, end), id(nb), nb))
|
||||
parent[nb] = cur
|
||||
return []
|
||||
```
|
||||
|
||||
### MazeSolver
|
||||
```python
|
||||
class MazeSolver:
|
||||
def __init__(self, maze, strategy):
|
||||
self.maze = maze
|
||||
self.strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def solve(self) -> SearchStats:
|
||||
t0 = time.perf_counter()
|
||||
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||
t1 = time.perf_counter()
|
||||
return SearchStats(
|
||||
strategy=self.strategy.name,
|
||||
time_ms=(t1 - t0) * 1000,
|
||||
visited=self.strategy._visited,
|
||||
path_length=len(path),
|
||||
path=path,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Результаты экспериментов
|
||||
Каждый алгоритм запускался 7 раз на каждом лабиринте, результаты усреднялись.
|
||||
### Таблица результатов
|
||||
|
||||
| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |
|
||||
|----------|----------|-----------|----------------|------------|
|
||||
| small (11x11) | BFS | 0.070 | 39 | 33 |
|
||||
| small (11x11) | DFS | 0.055 | 33 | 33 |
|
||||
| small (11x11) | A* | 0.112 | 35 | 33 |
|
||||
| medium (51x51) | BFS | 1.391 | 793 | 497 |
|
||||
| medium (51x51) | DFS | 0.949 | 515 | 497 |
|
||||
| medium (51x51) | A* | 2.271 | 707 | 497 |
|
||||
| large (101x101) | BFS | 6.231 | 3533 | 1613 |
|
||||
| large (101x101) | DFS | 3.341 | 1957 | 1613 |
|
||||
| large (101x101) | A* | 11.27 | 3379 | 1613 |
|
||||
| empty (51x21) | BFS | 1.992 | 931 | 67 |
|
||||
| empty (51x21) | DFS | 1.021 | 451 | 451 |
|
||||
| empty (51x21) | A* | 3.527 | 931 | 67 |
|
||||
| no_exit (11x11) | BFS | 0.079 | 40 | - |
|
||||
| no_exit (11x11) | DFS | 0.077 | 40 | - |
|
||||
| no_exit (11x11) | A* | 0.140 | 40 | - |
|
||||
|
||||
### Графики
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 4. Анализ эффективности алгоритмов и применимости паттернов
|
||||
### Алгоритмы
|
||||
|
||||
**BFS** гарантирует кратчайший путь по числу шагов. Расширяет узлы слой за слоем во всех направлениях, поэтому посещает наибольшее число клеток. На практике это надёжный выбор когда нужен точно кратчайший маршрут.
|
||||
|
||||
**DFS** посещает меньше клеток и выполняется быстрее - на large лабиринте в 1.8 раза быстрее BFS. Однако путь может быть далеко не кратчайшим. На пустом лабиринте DFS нашёл путь длиной 451 шаг, тогда как BFS и A* - 67. Это связано с тем, что DFS уходит в первое попавшееся направление и возвращается только в тупике.
|
||||
|
||||
**A*** использует манхэттенскую эвристику h = |x1-x2| + |y1-y2| и должен в теории посещать меньше клеток чем BFS. На лабиринтах, сгенерированных алгоритмом recursive backtracker, выигрыш небольшой (примерно 5%). Причина: backtracker строит дерево - между любыми двумя клетками ровно один путь, тупиков нет, эвристика не помогает их обходить. На лабиринтах с циклами A* посещает заметно меньше клеток. Накладные расходы на работу с heap и closed-set делают A* медленнее по времени, чем DFS.
|
||||
|
||||
На пустом лабиринте (без стен) A* ведёт себя как BFS. Математически: f(x,y) = g + h = (x-1+y-1) + (W-x+H-y) = const для всех клеток. Все узлы неразличимы по приоритету.
|
||||
|
||||
На лабиринте без выхода все три алгоритма посещают одинаковое число клеток и корректно возвращают пустой путь.
|
||||
|
||||
### Паттерны
|
||||
**Builder** оказался полезным при добавлении нового типа лабиринта (взвешенного, с символами s и m). Изменения были внесены только в `TextFileMazeBuilder`, клиентский код не менялся.
|
||||
|
||||
**Strategy** позволил в одном цикле запустить все три алгоритма через `solver.set_strategy(strategy)`. Без паттерна пришлось бы либо дублировать код запуска для каждого алгоритма, либо писать условные ветки.
|
||||
|
||||
**Observer** полезен при расширении: чтобы добавить вывод в лог или консоль, достаточно написать новый Observer и подписать его на solver, не меняя `MazeSolver`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Выводы
|
||||
ООП и паттерны позволили сделать код гибким в нескольких направлениях.
|
||||
|
||||
Добавление нового алгоритма поиска сводится к написанию одного класса, реализующего `find_path()`. Без Strategy пришлось бы добавлять ветку в `solve()` и во все места, где запускается поиск.
|
||||
|
||||
Добавление нового формата лабиринта - только новый класс Builder. Без паттерна логика парсинга была бы перемешана с логикой работы программы.
|
||||
|
||||
Добавление нового способа отображения (GUI, запись в файл) - только новый Observer. Без него MazeSolver пришлось бы напрямую вызывать функции отображения, что создало бы зависимость от конкретной реализации.
|
||||
|
||||
Без применения паттернов код решал бы задачу, но любое изменение требовало бы правки в нескольких местах сразу. С паттернами каждый класс отвечает за одну задачу и не знает о деталях реализации соседних классов.
|
||||
165
VasilevIA/lab2/main.py
Normal file
165
VasilevIA/lab2/main.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import csv
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "codes"))
|
||||
|
||||
from maze import TextFileMazeBuilder, MazeSolver, BFSStrategy, DFSStrategy, AStarStrategy
|
||||
from maze_generator import generate_all
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
HAS_PLT = True
|
||||
except ImportError:
|
||||
HAS_PLT = False
|
||||
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
MAZES_DIR = os.path.join(BASE_DIR, "mazes")
|
||||
RESULTS_DIR = os.path.join(BASE_DIR, "results")
|
||||
RUNS = 7
|
||||
|
||||
MAZE_FILES = [
|
||||
("small", "small.txt"),
|
||||
("medium", "medium.txt"),
|
||||
("large", "large.txt"),
|
||||
("empty", "empty.txt"),
|
||||
("no_exit", "no_exit.txt"),
|
||||
]
|
||||
|
||||
|
||||
def run():
|
||||
os.makedirs(RESULTS_DIR, exist_ok=True)
|
||||
|
||||
if not os.path.exists(MAZES_DIR) or not os.listdir(MAZES_DIR):
|
||||
generate_all(MAZES_DIR)
|
||||
|
||||
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||||
builder = TextFileMazeBuilder()
|
||||
all_results = []
|
||||
|
||||
for label, filename in MAZE_FILES:
|
||||
path = os.path.join(MAZES_DIR, filename)
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
|
||||
maze = builder.build_from_file(path)
|
||||
print(f"\nMaze: {label} ({maze.width}x{maze.height})")
|
||||
|
||||
solver = MazeSolver(maze, strategies[0])
|
||||
|
||||
for strategy in strategies:
|
||||
solver.set_strategy(strategy)
|
||||
times, visited_list, lengths = [], [], []
|
||||
|
||||
for _ in range(RUNS):
|
||||
stats = solver.solve()
|
||||
times.append(stats.time_ms)
|
||||
visited_list.append(stats.visited)
|
||||
lengths.append(stats.path_length)
|
||||
|
||||
avg_time = sum(times) / RUNS
|
||||
avg_visited = sum(visited_list) / RUNS
|
||||
avg_len = sum(lengths) / RUNS
|
||||
|
||||
found = f"length={avg_len:.0f}" if avg_len > 0 else "not found"
|
||||
print(f" {strategy.name:<6} time={avg_time:.4f} ms visited={avg_visited:.0f} {found}")
|
||||
|
||||
all_results.append({
|
||||
"maze": label,
|
||||
"strategy": strategy.name,
|
||||
"time_ms": round(avg_time, 4),
|
||||
"visited_cells": round(avg_visited, 1),
|
||||
"path_length": round(avg_len, 1),
|
||||
})
|
||||
|
||||
save_csv(all_results)
|
||||
save_plots(all_results)
|
||||
show_sample()
|
||||
print("\nDone. See results/ and docs/")
|
||||
|
||||
|
||||
def save_csv(results):
|
||||
path = os.path.join(RESULTS_DIR, "results_maze.csv")
|
||||
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(
|
||||
f, fieldnames=["maze", "strategy", "time_ms", "visited_cells", "path_length"]
|
||||
)
|
||||
writer.writeheader()
|
||||
writer.writerows(results)
|
||||
print(f"\nCSV saved: {path}")
|
||||
|
||||
|
||||
def save_plots(results):
|
||||
if not HAS_PLT:
|
||||
return
|
||||
|
||||
mazes = list(dict.fromkeys(r["maze"] for r in results))
|
||||
strategies = list(dict.fromkeys(r["strategy"] for r in results))
|
||||
colors = ["#2196F3", "#FF5722", "#4CAF50"]
|
||||
|
||||
def val(maze, strat, key):
|
||||
for r in results:
|
||||
if r["maze"] == maze and r["strategy"] == strat:
|
||||
return float(r[key])
|
||||
return 0.0
|
||||
|
||||
metrics = [
|
||||
("time_ms", "Time (ms)"),
|
||||
("visited_cells", "Visited cells"),
|
||||
("path_length", "Path length"),
|
||||
]
|
||||
|
||||
fig, axes = plt.subplots(
|
||||
len(metrics), len(mazes),
|
||||
figsize=(3.5 * len(mazes), 4 * len(metrics))
|
||||
)
|
||||
|
||||
def fmt(v):
|
||||
if v == 0:
|
||||
return "0"
|
||||
if v >= 100:
|
||||
return f"{v:.0f}"
|
||||
if v >= 1:
|
||||
return f"{v:.2f}"
|
||||
return f"{v:.3f}"
|
||||
|
||||
for row_i, (key, ylabel) in enumerate(metrics):
|
||||
for col_i, maze in enumerate(mazes):
|
||||
ax = axes[row_i][col_i]
|
||||
vals = [val(maze, s, key) for s in strategies]
|
||||
bars = ax.bar(strategies, vals, color=colors[:len(strategies)])
|
||||
if row_i == 0:
|
||||
ax.set_title(maze, fontsize=9)
|
||||
if col_i == 0:
|
||||
ax.set_ylabel(ylabel)
|
||||
for bar, v in zip(bars, vals):
|
||||
ax.text(
|
||||
bar.get_x() + bar.get_width() / 2,
|
||||
bar.get_height() * 1.02,
|
||||
fmt(v), ha="center", va="bottom", fontsize=7
|
||||
)
|
||||
ax.tick_params(axis="x", labelsize=8)
|
||||
|
||||
plt.tight_layout()
|
||||
out = os.path.join(RESULTS_DIR, "benchmark_plot.png")
|
||||
plt.savefig(out, dpi=120)
|
||||
plt.close()
|
||||
print(f"Chart saved: {out}")
|
||||
|
||||
|
||||
def show_sample():
|
||||
path = os.path.join(MAZES_DIR, "sample.txt")
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file(path)
|
||||
solver = MazeSolver(maze, BFSStrategy())
|
||||
stats = solver.solve()
|
||||
print("\nSample maze with BFS path:")
|
||||
print(maze.render(path=stats.path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
21
VasilevIA/lab2/mazes/empty.txt
Normal file
21
VasilevIA/lab2/mazes/empty.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
###################################################
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
###################################################
|
||||
101
VasilevIA/lab2/mazes/large.txt
Normal file
101
VasilevIA/lab2/mazes/large.txt
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#####################################################################################################
|
||||
#S # # # # # # # # # # # # #
|
||||
### # # ##### # ### # # ##### ### ### ############# # # # # ##### # ######### ### # # ##### ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ##### ### ### # # ########### ####### # ####### ### ### # # # ####### ##### # # ### # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### # ### ### # ### # ### # # ##### # ##### ####### ### ### # ### ### ### # ### ### # ##### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # ### # # ### # ##### ##### # ### # # ##### # # ####### ######### ### # # ######### # # # #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### ### # ##### # ##### ### # ##### ######### # # ####### ####### # ### # # ########### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
##### ##### ########### ### # # ########### # # # # ##### ####### # ####### # ##### ### ### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
####### # ### # # ####### ##### ### ##### # # ##### # ##### ### # # # ######### # ### ### ### ### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ########### # # # ### ### ##### ##### # # ##### # # # ##### # # ##### ##### # ##### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### # ### ### ### ####### # ########### ####### # # ##### # # ### ########### ### ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ####### # # # # ##### ### # ####### # ##### ##### ##### # # # ### ### # # ##### ##### ### #######
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
########### # # ##### # # # ####### # ### # ##### # ##### ### # # # # # ####### # ##### ### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # ### # # ##### # # # # # ##### # # # ############# # ##### # ############### ### # # ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ####### ##### # ########### # # # ######### ### ############# # ####### ####################### #
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### ### # # # # # ### # ##### # ### # ####### # ##### # ######### ####### # ##### # ########### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ##### ######### ####### ##### # # # # # ### # ##### # ### # ### # ##### # # # # # ### # # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # # ### ##### ### # # ### ##### ##### ####### # # ##### # ### ##### ####### # # # ####### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ####### # ### # # ### ### ##### ##### # # # # ### # # # ####### # # ### ##### # # ####### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # # # # # ### # ### ### ### ### # ### # ### ##### # # ########### # # ### # # ##### ##### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### ##### ### ### # # ### ### # # ##### ### # ##### ########### ### ### ### # # # # ### ######### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### ### # # ####### # # ######### ### # ########### # ### ### ##### ### # ### # # ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ######### # ####### ##### ######### # ### ### # ##### # ### ### ### # ### ### # ##### ##### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ### # # # # # ##### # # # # ####### # # # ### # ### # ### ### ##### ### # # ### # # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ### ######### # ##### # ##### # # ############# # # ### ####### # # ##### # # # # # # ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ####### ### # ### ##### # # # ##### # ####### ### # # ### # # # # ### ### # ########### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ####### # ##### # ### # ##### # # ##### # ##### # ### ### ######### ##### ### # ##### # ######### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # # # ### ####### # # # # ##### # ############### # # ##### ####### ##### # ### # # # # ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### ########### ### # ############# # # # ### # ##### ##### ### ####### # ### # # # ##### ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ##### ##### ### ### ### # # # # ##### # # ####### # # # ####### # # ### # ### # # # # ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ### # # # ####### # # # # ### ####### # # # ##### ### # # ##### # ##### # # ### ### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### # ### # # # # ### ### ### # # ### ### # ##### ### # # ##### ####### # ##### # ##### ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # ### ####### ### # # ### ##### # # ######### ### ### # # # # # ### # ### # # ### ### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # # ### ### ##### ######### ######### # # # # # ########### ####### # # ### ########### ##### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ### ### # ### # # ######### # # # # ##### # # # ##### # ####### ####### # ##### # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### ### ##### ####### # # ### ######### ### ### # # ########### # ##### # ### ##### ##### # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ####### # # ##### ### # ### # ####### # # # ### ### # # ### # ####### # ##### ### # ######### #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # ##### # ####### ####### # ########### ####### ######### # ### # ### # ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # ####### # ### ########### # # ######### ### # # # # # # ### # ######### # ### ########### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ####### # # # # # ### # # # ######### # # # ########################### # # ##### ####### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### ### ############### ####### ##### # ########### # ### # # ##### ### # ##### # ##### # ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ########### ##### ### ####### # # # ##### # # # # # ### # # ### ### # ### ### # ##### # # # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### # ### # ### # # ### # # ####### ####### # ### ##### ##### ##### ### ##### # ####### #######
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # ##### ####### ### # # # ############# # # # # ##### ### ######### # ######### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # ##### ##### # ### # ### ####### # ####### # ### ##### # ######### ##### # # ####### ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # # # ##### # ##### # ### # ### # ### ##### ### ##### ########### ### # ########### # # #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # ##### # # # # # # # # ######### ### # ### # # ### # # # ### # # ### # # # # ### # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### ######### ### ### ######### # ### # # ### # ### # # ##### # # ######### ### ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ##### # # ####### ### ### # ##### ##### ##### # # ##### ##### ############# # # ### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # # ### ### # # ### # # ####### ##### # # # # # ### ##### ##### # ### ##### # # ######### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### ### ### # # ### ##### ##### ##### # ##### ### # ### # ### ##### ##### # ##### # # # # # # #
|
||||
# # # # # # # # # # # # # E#
|
||||
#####################################################################################################
|
||||
51
VasilevIA/lab2/mazes/medium.txt
Normal file
51
VasilevIA/lab2/mazes/medium.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
###################################################
|
||||
#S# # # # # # # #
|
||||
# ##### # # # # # # # ####### # ### # ### ##### # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
### # ### # # ####### # ### # # # # ### # ### ### #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # # # # ####### ####### # ### ######### ##### #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # ##### # # # ### ####### ##### ##### # ### ###
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # ##### ### # ### ### # # ### # ##### # ### # # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # # # # ### # ### ### ##### # # # ####### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# ######### # # # # ### # # # ### # ####### # ### #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### # # ### ### ### # ### # # # ### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# ### ### # ### ####### ######### # ### ####### ###
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ### ### # ### # ####### # # # # # ####### # # #
|
||||
# # # # # # # # # # # # # #
|
||||
# ####### # ############# ##### # # ####### # ### #
|
||||
# # # # # # # # # # #
|
||||
# # # # # ################# ########### # # ##### #
|
||||
# # # # # # # # # # #
|
||||
# ####### # ### ##### # # ### ### ######### # #####
|
||||
# # # # # # # # # #
|
||||
####### # ####### ##### ### ##### # ############# #
|
||||
# # # # # # # # # # # #
|
||||
##### ####### # # ### # ##### # # ##### ##### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
## #### # # # ##### ##### # ### ### # # # ##### # #
|
||||
# # # # # # # # # # # # # #
|
||||
# ####### # ### # ### # # # ################# ### #
|
||||
# # # # # # # # # #
|
||||
### ##### ########### # # ############# ### ### ###
|
||||
# # # # # # # # # # # #
|
||||
# ### ### # ####### # # ### # ####### # # ### ### #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ######### ### # # # # # ### ### ####### # ### #
|
||||
# # # # # # # # # # # # # #
|
||||
# ##### # ##### # ##### ##### ### ####### ##### ###
|
||||
# # # # # # # # # # # #
|
||||
# # # # # # ##### ### ########### # # ####### ### #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ### ##### # ### ##### ##### ##### # # ### # # #
|
||||
# # # # # # # # # # # # #
|
||||
### # ### ##### ####### ##### ##### ####### # # # #
|
||||
# # # # # #E#
|
||||
###################################################
|
||||
11
VasilevIA/lab2/mazes/no_exit.txt
Normal file
11
VasilevIA/lab2/mazes/no_exit.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
###########
|
||||
#S# #E#
|
||||
# #########
|
||||
# # #
|
||||
##### # ###
|
||||
# # #
|
||||
# ####### #
|
||||
# # #
|
||||
### ### # #
|
||||
# # #
|
||||
###########
|
||||
15
VasilevIA/lab2/mazes/sample.txt
Normal file
15
VasilevIA/lab2/mazes/sample.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
###############
|
||||
#S# # #
|
||||
# ### # # ### #
|
||||
# # # # # #
|
||||
### ### ### # #
|
||||
# # # # # #
|
||||
# ### # # ### #
|
||||
# # # # #
|
||||
## ###### # # #
|
||||
# # # #
|
||||
# ### #########
|
||||
# # # #
|
||||
# # ##### # # #
|
||||
# # #E#
|
||||
###############
|
||||
11
VasilevIA/lab2/mazes/small.txt
Normal file
11
VasilevIA/lab2/mazes/small.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
###########
|
||||
#S # # #
|
||||
##### # #
|
||||
# # # #
|
||||
# ####### #
|
||||
# # # #
|
||||
# ### # # #
|
||||
# # # #
|
||||
### # ### #
|
||||
# # E#
|
||||
###########
|
||||
BIN
VasilevIA/lab2/results/benchmark_plot.png
Normal file
BIN
VasilevIA/lab2/results/benchmark_plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
16
VasilevIA/lab2/results/results_maze.csv
Normal file
16
VasilevIA/lab2/results/results_maze.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
maze,strategy,time_ms,visited_cells,path_length
|
||||
small,BFS,0.0676,39.0,33.0
|
||||
small,DFS,0.061,33.0,33.0
|
||||
small,A*,0.1093,35.0,33.0
|
||||
medium,BFS,1.4027,793.0,497.0
|
||||
medium,DFS,0.8985,515.0,497.0
|
||||
medium,A*,2.3001,707.0,497.0
|
||||
large,BFS,6.1605,3533.0,1613.0
|
||||
large,DFS,3.3919,1957.0,1613.0
|
||||
large,A*,11.2172,3379.0,1613.0
|
||||
empty,BFS,1.7583,931.0,67.0
|
||||
empty,DFS,1.0076,451.0,451.0
|
||||
empty,A*,3.4836,931.0,67.0
|
||||
no_exit,BFS,0.067,40.0,0.0
|
||||
no_exit,DFS,0.0599,40.0,0.0
|
||||
no_exit,A*,0.1099,40.0,0.0
|
||||
|
Loading…
Reference in New Issue
Block a user