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