diff --git a/zhigalovrd/lab2/solver.py b/zhigalovrd/lab2/solver.py new file mode 100644 index 0000000..fda02ba --- /dev/null +++ b/zhigalovrd/lab2/solver.py @@ -0,0 +1,28 @@ +import time +from dataclasses import dataclass, field +from typing import List +from maze import Maze, Cell +from strategies import PathFindingStrategy + +@dataclass +class SearchStats: + time_ms: float + visited_cells: int + path_length: int + path: List[Cell] = field(default_factory=list) + +class MazeSolver: + def __init__(self, maze: Maze, strategy: PathFindingStrategy): + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy: PathFindingStrategy): + self.strategy = strategy + + def solve(self) -> SearchStats: + start_time = time.perf_counter() + path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) + end_time = time.perf_counter() + time_ms = (end_time - start_time) * 1000 + return SearchStats(time_ms=time_ms, visited_cells=visited, + path_length=len(path), path=path) \ No newline at end of file diff --git a/zhigalovrd/lab2/strategies.py b/zhigalovrd/lab2/strategies.py new file mode 100644 index 0000000..b78dbec --- /dev/null +++ b/zhigalovrd/lab2/strategies.py @@ -0,0 +1,86 @@ +from abc import ABC, abstractmethod +from collections import deque +from typing import List, Tuple, Dict, Optional +import heapq +from maze import Maze, Cell + +class PathFindingStrategy(ABC): + @abstractmethod + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + """Возвращает (путь_список_клеток, количество_посещённых_клеток)""" + pass + +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + queue = deque([start]) + visited = {start} + parent = {start: None} + while queue: + current = queue.popleft() + if current is exit: + return self._reconstruct_path(parent, exit), len(visited) + for nb in maze.get_neighbors(current): + if nb not in visited: + visited.add(nb) + parent[nb] = current + queue.append(nb) + return [], len(visited) + + def _reconstruct_path(self, parent: Dict[Cell, Optional[Cell]], end: Cell) -> List[Cell]: + path = [] + cur = end + while cur is not None: + path.append(cur) + cur = parent[cur] + path.reverse() + return path + +class DFSStrategy(PathFindingStrategy): + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + stack = [(start, [start])] + visited = {start} + while stack: + current, path = stack.pop() + if current is exit: + return path, len(visited) + for nb in maze.get_neighbors(current): + if nb not in visited: + visited.add(nb) + stack.append((nb, path + [nb])) + return [], len(visited) + +class AStarStrategy(PathFindingStrategy): + @staticmethod + def heuristic(a: Cell, b: Cell) -> int: + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + open_set = [] + heapq.heappush(open_set, (0, start)) + came_from = {} + g_score = {start: 0} + f_score = {start: self.heuristic(start, exit)} + visited_count = 0 + + while open_set: + _, current = heapq.heappop(open_set) + visited_count += 1 + if current is exit: + path = self._reconstruct_path(came_from, exit) + return path, visited_count + for nb in maze.get_neighbors(current): + tentative_g = g_score[current] + 1 + if nb not in g_score or tentative_g < g_score[nb]: + came_from[nb] = current + g_score[nb] = tentative_g + f_score[nb] = tentative_g + self.heuristic(nb, exit) + heapq.heappush(open_set, (f_score[nb], nb)) + return [], visited_count + + def _reconstruct_path(self, came_from: Dict[Cell, Cell], current: Cell) -> List[Cell]: + path = [current] + while current in came_from: + current = came_from[current] + path.append(current) + path.reverse() + return path \ No newline at end of file