[5] больше лабиринтов + стата
This commit is contained in:
parent
31466e3743
commit
b1331cab37
|
|
@ -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()
|
||||
Loading…
Reference in New Issue
Block a user