From 06b5b017e49279faf698a34f148bdebd3a6d40bf Mon Sep 17 00:00:00 2001 From: konnovaea Date: Mon, 18 May 2026 20:55:01 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D1=8B=20=D0=B2?= =?UTF-8?q?=D1=81=D0=B5=20=D1=8D=D1=82=D0=B0=D0=BF=D1=8B=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=88=D0=BB=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- konnovaea/maze_solver.py | 246 ++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 146 deletions(-) diff --git a/konnovaea/maze_solver.py b/konnovaea/maze_solver.py index 8b06667..d6195b9 100644 --- a/konnovaea/maze_solver.py +++ b/konnovaea/maze_solver.py @@ -1,242 +1,196 @@ -class Cell: +from abc import ABC, abstractmethod +from collections import deque +import heapq +import time + +class Cell: def __init__(self, x, y): self.x = x self.y = y self.is_wall = False self.is_start = False self.is_exit = False - + def is_passable(self): return not self.is_wall - def __repr__(self): - return f"Cell({self.x}, {self.y})" -class Maze: + def __repr__(self): + return f"Cell({self.x},{self.y})" + +class Maze: def __init__(self, width, height): self.width = width self.height = height self.cells = [] self.start = None self.exit = None - + for y in range(height): row = [] for x in range(width): row.append(Cell(x, y)) self.cells.append(row) - + def get_cell(self, x, y): - if 0 <= x < self.width and 0 <= y < self.height: return self.cells[y][x] return None def get_neighbors(self, cell): - neighbors = [] - directions = [(0, -1), (0, 1), (-1, 0), (1,0)] - - for dx, dy in directions: + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: 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 - - def __repr__(self): - return f"Maze({self.width}x{self.height})" - -from abc import ABC, abstractmethod - -class MazeBuilder(ABC): - - @abstractmethod - def build_from_file(self, filename): - pass - -class TextFileMazeBuilder(MazeBuilder): +class TextFileMazeBuilder: def build_from_file(self, filename): + with open(filename, 'r') as f: + lines = [line.rstrip() for line in f.readlines()] - with open(filename, 'r', encoding='utf-8') as f: - lines = f.readlines() - - lines = [line.rstrip('\n\r') for line in lines] - height = len(lines) - width = len(lines[0]) if height > 0 else 0 - - for i, line in enumerate(lines): - if len(line) != width: - raise ValueError(f"Строка {i+1} имеет длину {len(line)}, ожидается {width}") - + width = len(lines[0]) maze = Maze(width, height) - start = None - exit_cell = None - + for y, line in enumerate(lines): for x, ch in enumerate(line): cell = maze.get_cell(x, y) - if ch == '#': cell.is_wall = True - elif ch == ' ': - cell.is_wall = False elif ch == 'S': - cell.is_wall = False + maze.start = cell cell.is_start = True - start = cell elif ch == 'E': - cell.is_wall = False - cell.ia_exit = True - exit_cell = cell - else: - cell.is_wall = True + maze.exit = cell + cell.is_exit = True - if start is None: - raise ValueError("В лабиринте не найден старт (S)") - if exit_cell is None: - raise ValueError("В лабиринте не найден выход (E)") - - maze.start = start - maze.exit = exit_cell - return maze - -from collections import deque -import heapq -from abc import ABC, abstractmethod -class PathfindingStrategy(ABC): +class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze, start, exit): pass -class BFSStrategy(PathfindingStrategy): +class BFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): - if start is None or exit is None: - return [] + if not start or not exit: + return [], 0 - queue = deque() - queue.append((start, [start])) - - visited = set() - visited.add(start) + queue = deque([(start, [start])]) + visited = {start} while queue: current, path = queue.popleft() - if current == exit: - return path + return path, len(visited) - neighbors = maze.get_neighbors(current) - for neighbor in neighbors: + for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) queue.append((neighbor, path + [neighbor])) - return [] - -class DFSStrategy(PathfindingStrategy): + return [], len(visited) + +class DFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): - if start is None or exit is None: - return [] + if not start or not exit: + return [], 0 stack = [(start, [start])] - - visited = set() - visited.add(start) - + visited = {start} + while stack: current, path = stack.pop() - if current == exit: - return path + return path, len(visited) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) stack.append((neighbor, path + [neighbor])) + + return [], len(visited) - return [] + +class AStarStrategy(PathFindingStrategy): + def _heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) -class AStartStrategy(PathfindingStrategy): + def find_path(self, maze, start, exit): + if not start or not exit: + return [], 0 + + heap = [(self._heuristic(start, exit), 0, start, [start])] + g_score = {start: 0} + visited = set() + counter = 1 + + while heap: + _, _, current, path = heapq.heappop(heap) + + if current in visited: + continue + + visited.add(current) + + if current == exit: + return path, len(visited) + + for neighbor in maze.get_neighbors(current): + tentative_g = g_score[current] + 1 + if neighbor not in g_score or tentative_g < g_score[neighbor]: + g_score[neighbor] = tentative_g + f = tentative_g + self._heuristic(neighbor, exit) + heapq.heappush(heap, (f, counter, neighbor, path + [neighbor])) + counter += 1 + + return [], len(visited) - def _heuristic(self, cell, exit): - return abs(cell.x - exit.x) + abs(cell.y -exit.y) - - def find_path(self, maze, start, exit): - if start is None or exit is None: - return [] - - counter = 0 - heap = [] - heapq.heappush(heap, (self._heuristic(start, exit), counter, start, [start])) - g_score = {start: 0} +class SearchStats: + def __init__(self, path, time_ms, visited_count): + self.path = path + self.time_ms = time_ms + self.visited_count = visited_count + self.path_length = len(path) if path else 0 - visited = set() - while heap: - f_score, _, current, path = heapq.heappop(heap) +class MazeSolver: + def __init__(self, maze, strategy=None): + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy): + self.strategy = strategy + + def solve(self): + start_time = time.perf_counter() + path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) + end_time = time.perf_counter() + return SearchStats(path, (end_time - start_time) * 1000, visited) - if current in visited: - continue - - visited.add(current) - - if current == exit: - return path - - for neighbor in maze.get_neighbors(current): - tentative_g = g_score[current] + 1 - - if neighbor not in g_score or tentative_g < g_score[neighbor]: - g_score[neighbor] = tentative_g - f_score = tentative_g + self._heuristic(neighbor, exit) - counter += 1 - heapq.heappush(heap, (f_score, counter, neighbor, path + [neighbor])) - - return [] - -#тест if __name__ == "__main__": builder = TextFileMazeBuilder() maze = builder.build_from_file("maze1.txt") - print("Лабиринт загружен") + print(f"Лабиринт: {maze.width}x{maze.height}") print(f"Старт: {maze.start}") print(f"Выход: {maze.exit}") + print() - # Проверяем, что старт и выход проходимые - print(f"Старт проходим: {maze.start.is_passable()}") - print(f"Выход проходим: {maze.exit.is_passable()}") + solver = MazeSolver(maze) - # Проверяем соседей старта - neighbors = maze.get_neighbors(maze.start) - print(f"Соседи старта: {neighbors}") - - # Тестируем BFS - bfs = BFSStrategy() - path = bfs.find_path(maze, maze.start, maze.exit) - print(f"BFS путь: {[f'({c.x},{c.y})' for c in path]}") - print(f"BFS длина пути: {len(path)}") - - # Тестируем DFS - dfs = DFSStrategy() - path = dfs.find_path(maze, maze.start, maze.exit) - print(f"DFS путь: {[f'({c.x},{c.y})' for c in path]}") - print(f"DFS длина пути: {len(path)}") - - # Тестируем A* - astar = AStartStrategy() - path = astar.find_path(maze, maze.start, maze.exit) - print(f"A* путь: {[f'({c.x},{c.y})' for c in path]}") - print(f"A* длина пути: {len(path)}") \ No newline at end of file + for name, strategy in [("BFS", BFSStrategy()), ("DFS", DFSStrategy()), ("A*", AStarStrategy())]: + solver.set_strategy(strategy) + stats = solver.solve() + print(f"{name}: путь={stats.path_length}, время={stats.time_ms:.3f}мс, посещено={stats.visited_count}") +