[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):
class ConsoleView(Observer): #красиво в консоль выводит лабиринт print(f"файл {maze_file} не найден")
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 continue
if cell is self.maze.start_cell: try:
row.append('S') maze = self.builder.build_from_file(maze_file)
elif cell is self.maze.exit_cell: except Exception as e:
row.append('E') print(f"ошибка загрузки {maze_file}: {e}")
elif cell in path_set: continue
row.append('*')
elif cell.is_wall: print(f"обработка лабиринта: {maze_file} (размер {maze.width}x{maze.height})")
row.append('#') 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: 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):
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.time_ms:.3f} мс")
print(f"посещено клеток: {stats.visited_cells}") print(f"посещено клеток: {stats.visited_cells}")
print(f"путь найден?: {'да' if stats.path_found else 'нет'}")
if stats.path_found:
print(f"длина пути: {stats.path_length}") 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()