[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:
#тут что такое клетка #тут что такое клетка
@ -15,7 +18,13 @@ class Cell:
self.is_exit = is_exit self.is_exit = is_exit
self.is_start = is_start self.is_start = is_start
def is_passable(self) -> bool: #можно ли пройти через клетку 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 is_passable(self) -> bool:
return not self.is_wall return not self.is_wall
def __repr__(self) -> str: def __repr__(self) -> str:
@ -243,8 +252,9 @@ 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):
@ -252,131 +262,168 @@ class MazeSolver(Subject):
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: class Benchmark:
self.notify("path_found", {"path": path, "stats": stats}) def __init__(self, maze_files: List[str], runs_per_strategy: int = 5):
else: self.maze_files = maze_files
self.notify("no_path", {"stats": stats}) self.runs = runs_per_strategy
self.strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"AStar": AStarStrategy()
}
self.builder = TextFileMazeBuilder()
self.results = []
self.notify("solving_end", {"stats": stats}) def run(self, output_csv: str):
return stats 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
class ConsoleView(Observer): #красиво в консоль выводит лабиринт print(f"обработка лабиринта: {maze_file} (размер {maze.width}x{maze.height})")
def __init__(self, maze: Maze): for strat_name, strategy in self.strategies.items():
self.maze = maze solver = MazeSolver(maze, strategy)
self.last_path: List[Cell] = [] times = []
visited_list = []
def update(self, event_type: str, data: Any = None) -> None: path_lengths = []
if event_type == "path_found": path_found = False
self.last_path = data["path"] for run_idx in range(self.runs):
self.render() stats = solver.solve()
elif event_type == "no_path": if stats is None:
self.last_path = [] continue
self.render(no_path=True) times.append(stats.time_ms)
elif event_type == "solving_start": visited_list.append(stats.visited_cells)
print(f"\nпоиск пути (алгоритм: {data['strategy']})\n") path_lengths.append(stats.path_length)
path_found = stats.path_found
def render(self, no_path: bool = False) -> None: if times:
path_set = set(self.last_path) if self.last_path else set() avg_time = sum(times) / len(times)
avg_visited = sum(visited_list) / len(visited_list)
for y in range(self.maze.height): avg_length = sum(path_lengths) / len(path_lengths)
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('#')
else: else:
row.append(' ') avg_time = avg_visited = avg_length = 0.0
print(''.join(row)) 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}")
if no_path: with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
print("\nнет пути (no way)") fieldnames = ["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути", "путь_найден"]
elif self.last_path: writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=',')
print(f"\nнайден путь длиной {len(self.last_path)} клеток") writer.writeheader()
else: for row in self.results:
print("\nожидание решения") writer.writerow(row)
print(f"\nрезультаты сохранены в {output_csv}")
def main():
import sys
if len(sys.argv) < 2: # ----------------------------- Консольный режим (для одного лабиринта, с визуализацией) -----------------------------
print("для запуска: python main.py <имя_лабиринта>.txt") def interactive_mode(maze_file: str):
return
filename = sys.argv[1]
#строится лабиринт из файла
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 choice not in strategies:
if not strategy:
print("неверный выбор, по умолчанию используется BFS.") print("неверный выбор, по умолчанию используется BFS.")
strategy = BFSStrategy() strat_name, strategy = strategies["1"]
else:
strat_name, strategy = strategies[choice]
solver.set_strategy(strategy) solver = MazeSolver(maze, strategy)
stats = solver.solve() stats = solver.solve()
if stats is None:
print("ошибка с решением")
return
if stats: path, _ = strategy.find_path(maze, maze.start_cell, maze.exit_cell)
print("\nстатистика по поиску пути в данном лабиринте") path_set = set(path)
print(f"выбранный алгоритм: {strategy.__class__.__name__}") for y in range(maze.height):
print(f"время выполнения: {stats.time_ms:.3f} мс") row = []
print(f"посещено клеток: {stats.visited_cells}") for x in range(maze.width):
print(f"путь найден?: {'да' if stats.path_found else 'нет'}") cell = maze.get_cell(x, y)
if stats.path_found: if cell is maze.start_cell:
print(f"длина пути: {stats.path_length}") 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()