2026-rff_mp/ivankinad/task2/laba2.py
2026-09-05 01:36:33 +03:00

484 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import time
import csv
from abc import ABC, abstractmethod
from collections import deque
from typing import List, Dict, Optional, Tuple
import heapq
#Модель лабиринта
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
self.weight = 1
def is_passable(self) -> bool:
return not self.is_wall
def __lt__(self, other):
return (self.x, self.y) < (other.x, other.y)
def __repr__(self):
return f"Cell({self.x}, {self.y})"
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
#Постройка лабиринта
class MazeBuilder(ABC):
@abstractmethod
def build_from_string_list(self, lines: List[str]) -> Maze:
pass
class TextMazeBuilder(MazeBuilder):
def build_from_string_list(self, lines: List[str]) -> Maze:
height = len(lines)
width = len(lines[0]) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, char in enumerate(line):
cell = maze.get_cell(x, y)
if char == '#':
cell.is_wall = True
elif char == 'S':
cell.is_start = True
maze.start = cell
elif char == 'E':
cell.is_exit = True
maze.exit = cell
elif char == 'W':
cell.weight = 3
elif char == 'D':
cell.weight = 2
return maze
#Стратегии поиска пути
class PathFindingStrategy(ABC):
def __init__(self):
self.visited_count = 0
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
def _reconstruct_path(self, came_from: Dict, start: Cell, exit: Cell) -> List[Cell]:
if exit not in came_from:
return []
path = []
current = exit
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
self.visited_count = 0
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
self.visited_count += 1
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
queue.append(neighbor)
came_from[neighbor] = current
return self._reconstruct_path(came_from, start, exit)
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
self.visited_count = 0
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
self.visited_count += 1
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
stack.append(neighbor)
came_from[neighbor] = current
return self._reconstruct_path(came_from, start, exit)
class AStarStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
self.visited_count = 0
def heuristic(a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
priority_queue = []
heapq.heappush(priority_queue, (0, start))
came_from = {start: None}
g_score = {start: 0}
while priority_queue:
_, current = heapq.heappop(priority_queue)
self.visited_count += 1
if current == exit:
break
for neighbor in maze.get_neighbors(current):
tentative_g_score = g_score[current] + neighbor.weight
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
f_score = tentative_g_score + heuristic(neighbor, exit)
heapq.heappush(priority_queue, (f_score, neighbor))
return self._reconstruct_path(came_from, start, exit)
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
self.visited_count = 0
priority_queue = []
heapq.heappush(priority_queue, (0, start))
came_from = {start: None}
g_score = {start: 0}
while priority_queue:
current_g, current = heapq.heappop(priority_queue)
self.visited_count += 1
if current == exit:
break
for neighbor in maze.get_neighbors(current):
tentative_g_score = g_score[current] + neighbor.weight
if neighbor not in g_score or tentative_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g_score
heapq.heappush(priority_queue, (tentative_g_score, neighbor))
return self._reconstruct_path(came_from, start, exit)
#Оркестратор поиска
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.3f}ms | Visited: {self.visited_cells} | Path length: {self.path_length}"
class Observer(ABC):
@abstractmethod
def update(self, event: str):
pass
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def add_observer(self, observer: Observer):
self.observers.append(observer)
def _notify(self, event: str):
for observer in self.observers:
observer.update(event)
def solve(self) -> Tuple[List[Cell], SearchStats]:
self._notify("Search started")
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
stats = SearchStats(time_ms, self.strategy.visited_count, len(path))
self._notify("Search completed")
return path, stats
#Визуализация
class ConsoleView(Observer):
def update(self, event: str):
print(f"[Event] {event}")
def render(self, maze: Maze, path: List[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 == maze.start:
row += "S"
elif cell == maze.exit:
row += "E"
elif cell in path_set:
row += "*"
elif cell.is_wall:
row += "#"
elif cell.weight == 3:
row += "W" # Болото
elif cell.weight == 2:
row += "D" # Песок
else:
row += "."
print(row)
#Экспериментальная часть
def create_test_mazes() -> Dict[str, Maze]:
builder = TextMazeBuilder()
mazes = {}
#Маленький лабиринт 10x10 с простым путём
small_maze = [
"S.........",
"#####.####",
"..........",
"####.#####",
"..........",
"#.#######.",
"..........",
"######.###",
"..........",
".........E"
]
mazes["Small (10x10)"] = builder.build_from_string_list(small_maze)
#Пустой лабиринт 50x50
empty_maze = ["." * 50 for _ in range(50)]
empty_maze[0] = "S" + empty_maze[0][1:]
empty_maze[-1] = empty_maze[-1][:-1] + "E"
mazes["Empty (50x50)"] = builder.build_from_string_list(empty_maze)
#Средний лабиринт 50x50 с тупиками
medium_maze = []
for y in range(50):
if y == 0:
row = "S" + "." * 49
elif y == 49:
row = "." * 49 + "E"
elif y % 2 == 1:
row = "#" * 45 + "." * 5 if y % 4 == 1 else "." * 5 + "#" * 45
else:
row = "." * 50
medium_maze.append(row)
mazes["Medium with dead ends (50x50)"] = builder.build_from_string_list(medium_maze)
# Большой лабиринт 100x100
large_maze = []
for y in range(100):
if y == 0:
row = "S" + "." * 99
elif y == 99:
row = "." * 99 + "E"
elif y % 2 == 1:
row = ("#" * 9 + ".") * 10
else:
row = "." * 100
large_maze.append(row)
mazes["Large (100x100)"] = builder.build_from_string_list(large_maze)
# Лабиринт без выхода
no_exit_maze = [
"S....#....",
"##########",
"##########",
"##########",
"##########",
"##########",
"##########",
"##########",
"##########",
"######...E"
]
mazes["No exit (10x10)"] = builder.build_from_string_list(no_exit_maze)
return mazes
def run_experiments() -> None:
mazes = create_test_mazes()
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy(),
"Dijkstra": DijkstraStrategy()
}
results = []
print("=" * 80)
print("ЗАПУСК ЭКСПЕРИМЕНТОВ ПО СРАВНЕНИЮ АЛГОРИТМОВ ПОИСКА ПУТИ")
print("=" * 80)
for maze_name, maze in mazes.items():
print(f"\nТестирование: {maze_name}")
print("-" * 60)
for strategy_name, strategy in strategies.items():
solver = MazeSolver(maze, strategy)
runs = 5
total_time = 0
path = []
stats = None
for _ in range(runs):
path, stats = solver.solve()
total_time += stats.time_ms
avg_time = total_time / runs
results.append([
maze_name,
strategy_name,
f"{avg_time:.4f}",
stats.visited_cells,
stats.path_length
])
print(f" {strategy_name:10} -> "
f"Время: {avg_time:8.3f}мс | "
f"Посещено: {stats.visited_cells:5} | "
f"Длина пути: {stats.path_length:3}")
with open("results_all.csv", "w", newline="", encoding="utf-8") as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Лабиринт", "Стратегия", "Время (мс)",
"Посещено клеток", "Длина пути"])
writer.writerows(results)
print("\n" + "=" * 80)
print("Все эксперименты завершены!")
print("Результаты сохранены в файл 'results_all.csv'")
print("=" * 80)
def demonstrate_visualization() -> None:
"""Демонстрация визуализации и паттерна Observer."""
builder = TextMazeBuilder()
maze_data = [
"S...#.....",
".###.####.",
".....#....",
"####.#####",
".....#....",
".#######..",
"..........",
"######.###",
"..........",
".........E"
]
maze = builder.build_from_string_list(maze_data)
strategy = AStarStrategy()
solver = MazeSolver(maze, strategy)
console_view = ConsoleView()
solver.add_observer(console_view)
print("\nДЕМОНСТРАЦИЯ ВИЗУАЛИЗАЦИИ")
print("=" * 40)
path, stats = solver.solve()
print("\nНайденный путь:")
console_view.render(maze, path)
print(f"\nСтатистика: {stats}")
print(f" Время: {stats.time_ms:.3f}мс")
print(f" Посещено клеток: {stats.visited_cells}")
print(f" Длина пути: {stats.path_length}")
if __name__ == "__main__":
demonstrate_visualization()
run_experiments()