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