[5] больше лабиринтов + стата

This commit is contained in:
mddcorporation 2026-05-09 16:08:21 +03:00
parent 31466e3743
commit b1331cab37

View File

@ -4,6 +4,9 @@ import time
from collections import deque
from dataclasses import dataclass
from typing import List, Optional, Dict, Set, Tuple, Any
import csv
import os
import sys
class Cell:
#тут что такое клетка
@ -14,8 +17,14 @@ class Cell:
self.is_wall = is_wall
self.is_exit = is_exit
self.is_start = is_start
def __eq__(self, other):
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
def is_passable(self) -> bool: #можно ли пройти через клетку
def __hash__(self):
return hash((self.x, self.y))
def is_passable(self) -> bool:
return not self.is_wall
def __repr__(self) -> str:
@ -243,140 +252,178 @@ class Subject:
self._observers.remove(observer)
def notify(self, event_type: str, data: Any = None) -> None:
for observer in self._observers:
observer.update(event_type, data)
for obs in self._observers:
obs.update(event_type, data)
class MazeSolver(Subject):
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
super().__init__()
self.maze = maze
self._strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy) -> None: #смена алгоритма
def set_strategy(self, strategy: PathFindingStrategy) -> None:
self._strategy = strategy
def solve(self) -> Optional[SearchStats]:
if self._strategy is None:
print("где стратегия?")
return None
self.notify("solving_start", {"strategy": self._strategy.__class__.__name__})
start_time = time.perf_counter()
path, visited = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000.0
path_found = len(path) > 0
stats = SearchStats(
time_ms=time_ms,
visited_cells=visited,
path_length=len(path) if path_found else 0,
path_found=path_found
)
if path_found:
self.notify("path_found", {"path": path, "stats": stats})
else:
self.notify("no_path", {"stats": stats})
self.notify("solving_end", {"stats": stats})
return stats
return SearchStats(time_ms, visited, len(path), len(path) > 0)
class ConsoleView(Observer): #красиво в консоль выводит лабиринт
def __init__(self, maze: Maze):
self.maze = maze
self.last_path: List[Cell] = []
def update(self, event_type: str, data: Any = None) -> None:
if event_type == "path_found":
self.last_path = data["path"]
self.render()
elif event_type == "no_path":
self.last_path = []
self.render(no_path=True)
elif event_type == "solving_start":
print(f"\nпоиск пути (алгоритм: {data['strategy']})\n")
def render(self, no_path: bool = False) -> None:
path_set = set(self.last_path) if self.last_path else set()
for y in range(self.maze.height):
row = []
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell is None:
row.append('?')
continue
if cell is self.maze.start_cell:
row.append('S')
elif cell is self.maze.exit_cell:
row.append('E')
elif cell in path_set:
row.append('*')
elif cell.is_wall:
row.append('#')
class Benchmark:
def __init__(self, maze_files: List[str], runs_per_strategy: int = 5):
self.maze_files = maze_files
self.runs = runs_per_strategy
self.strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"AStar": AStarStrategy()
}
self.builder = TextFileMazeBuilder()
self.results = []
def run(self, output_csv: str):
for maze_file in self.maze_files:
if not os.path.exists(maze_file):
print(f"файл {maze_file} не найден")
continue
try:
maze = self.builder.build_from_file(maze_file)
except Exception as e:
print(f"ошибка загрузки {maze_file}: {e}")
continue
print(f"обработка лабиринта: {maze_file} (размер {maze.width}x{maze.height})")
for strat_name, strategy in self.strategies.items():
solver = MazeSolver(maze, strategy)
times = []
visited_list = []
path_lengths = []
path_found = False
for run_idx in range(self.runs):
stats = solver.solve()
if stats is None:
continue
times.append(stats.time_ms)
visited_list.append(stats.visited_cells)
path_lengths.append(stats.path_length)
path_found = stats.path_found
if times:
avg_time = sum(times) / len(times)
avg_visited = sum(visited_list) / len(visited_list)
avg_length = sum(path_lengths) / len(path_lengths)
else:
row.append(' ')
print(''.join(row))
if no_path:
print("\nнет пути (no way)")
elif self.last_path:
print(f"\nнайден путь длиной {len(self.last_path)} клеток")
else:
print("\nожидание решения")
avg_time = avg_visited = avg_length = 0.0
self.results.append({
"лабиринт": os.path.basename(maze_file),
"стратегия": strat_name,
"время_мс": round(avg_time, 3),
"посещено_клеток": round(avg_visited, 1),
"длина_пути": round(avg_length, 1),
"путь_найден": path_found
})
print(f" {strat_name}: {avg_time:.3f} мс, посещено {avg_visited:.1f}, длина {avg_length:.1f}")
def main():
import sys
if len(sys.argv) < 2:
print("для запуска: python main.py <имя_лабиринта>.txt")
return
filename = sys.argv[1]
#строится лабиринт из файла
with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
fieldnames = ["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути", "путь_найден"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=',')
writer.writeheader()
for row in self.results:
writer.writerow(row)
print(f"\nрезультаты сохранены в {output_csv}")
# ----------------------------- Консольный режим (для одного лабиринта, с визуализацией) -----------------------------
def interactive_mode(maze_file: str):
builder = TextFileMazeBuilder()
try:
maze = builder.build_from_file(filename)
maze = builder.build_from_file(maze_file)
except Exception as e:
print(f"ошибка загрузки лабиринта: {e}")
return
#наблюдатель и решатель прикрепляются
solver = MazeSolver(maze)
view = ConsoleView(maze)
solver.attach(view)
strategies = {
"1": BFSStrategy(),
"2": DFSStrategy(),
"3": AStarStrategy()
"1": ("BFS", BFSStrategy()),
"2": ("DFS", DFSStrategy()),
"3": ("A*", AStarStrategy())
}
print("\nвыберите алгоритм поиска:")
print("1. BFS")
print("2. DFS")
print("3. A*")
choice = input("введите (1/2/3): ").strip()
strategy = strategies.get(choice)
if not strategy:
print("неверный выбор, по умолчанию используется BFS.")
strategy = BFSStrategy()
solver.set_strategy(strategy)
stats = solver.solve()
if stats:
print("\nстатистика по поиску пути в данном лабиринте")
print(f"выбранный алгоритм: {strategy.__class__.__name__}")
print(f"время выполнения: {stats.time_ms:.3f} мс")
print(f"посещено клеток: {stats.visited_cells}")
print(f"путь найден?: {'да' if stats.path_found else 'нет'}")
if stats.path_found:
print(f"длина пути: {stats.path_length}")
if choice not in strategies:
print("неверный выбор, по умолчанию используется BFS.")
strat_name, strategy = strategies["1"]
else:
strat_name, strategy = strategies[choice]
solver = MazeSolver(maze, strategy)
stats = solver.solve()
if stats is None:
print("ошибка с решением")
return
path, _ = strategy.find_path(maze, maze.start_cell, maze.exit_cell)
path_set = set(path)
for y in range(maze.height):
row = []
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell is maze.start_cell:
row.append('S')
elif cell is maze.exit_cell:
row.append('E')
elif cell in path_set:
row.append('*')
elif cell and cell.is_wall:
row.append('#')
else:
row.append(' ')
print(''.join(row))
print(f"\nстатистика ({strat_name}):")
print(f"время выполнения: {stats.time_ms:.3f} мс")
print(f"посещено клеток: {stats.visited_cells}")
print(f"длина пути: {stats.path_length}")
print(f"путь найден: {'да' if stats.path_found else 'нет'}")
def main():
if len(sys.argv) < 2:
print("использование:")
print("режим визуализации: python main.py <файл_лабиринта>")
print("режим замера: python main.py --benchmark <список_лабиринтов> --runs <кол_во_итераций> --output <названиеаблицы>.csv")
return
if sys.argv[1] == "--benchmark":
args = sys.argv[2:]
maze_files = []
runs = 5
output = "benchmark_results.csv"
i = 0
while i < len(args):
if args[i] == "--runs" and i+1 < len(args):
runs = int(args[i+1])
i += 2
elif args[i] == "--output" and i+1 < len(args):
output = args[i+1]
i += 2
else:
maze_files.append(args[i])
i += 1
if not maze_files:
print("Ошибка: не указаны файлы лабиринтов.")
return
benchmark = Benchmark(maze_files, runs_per_strategy=runs)
benchmark.run(output)
else:
interactive_mode(sys.argv[1])
if __name__ == "__main__":
main()