[2] add stats(time of compliting maze)

This commit is contained in:
IvanBud123 2026-05-15 09:02:06 +03:00
parent 57c743c253
commit 133d076666

View File

@ -1,11 +1,12 @@
import sys import sys
from collections import deque from collections import deque
import heapq import heapq
from typing import List, Dict, Optional import time
from typing import List, Optional
class Cell: class Cell:
def __init__(self, x, y): def __init__(self, x: int, y: int):
self._x = x self._x = x
self._y = y self._y = y
self._is_wall = False self._is_wall = False
@ -13,43 +14,46 @@ class Cell:
self._is_exit = False self._is_exit = False
@property @property
def x(self): def x(self) -> int:
return self._x return self._x
@property @property
def y(self): def y(self) -> int:
return self._y return self._y
@property @property
def is_wall(self): def is_wall(self) -> bool:
return self._is_wall return self._is_wall
@is_wall.setter @is_wall.setter
def is_wall(self, value): def is_wall(self, value: bool) -> None:
self._is_wall = value self._is_wall = value
@property @property
def is_start(self): def is_start(self) -> bool:
return self._is_start return self._is_start
@is_start.setter @is_start.setter
def is_start(self, value): def is_start(self, value: bool) -> None:
self._is_start = value self._is_start = value
@property @property
def is_exit(self): def is_exit(self) -> bool:
return self._is_exit return self._is_exit
@is_exit.setter @is_exit.setter
def is_exit(self, value): def is_exit(self, value: bool) -> None:
self._is_exit = value self._is_exit = value
def is_passable(self): def is_passable(self) -> bool:
return not self._is_wall return not self._is_wall
def __repr__(self) -> str:
return f"Cell({self._x}, {self._y})"
class Maze: class Maze:
def __init__(self, width, height): def __init__(self, width: int, height: int):
self._width = width self._width = width
self._height = height self._height = height
self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)] self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
@ -57,27 +61,27 @@ class Maze:
self._exit = None self._exit = None
@property @property
def width(self): def width(self) -> int:
return self._width return self._width
@property @property
def height(self): def height(self) -> int:
return self._height return self._height
@property @property
def start(self): def start(self) -> Optional[Cell]:
return self._start return self._start
@property @property
def exit(self): def exit(self) -> Optional[Cell]:
return self._exit return self._exit
def get_cell(self, x, y): def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self._width and 0 <= y < self._height: if 0 <= x < self._width and 0 <= y < self._height:
return self._cells[y][x] return self._cells[y][x]
return None return None
def set_cell(self, x, y, cell_type): def set_cell(self, x: int, y: int, cell_type: str) -> None:
cell = self.get_cell(x, y) cell = self.get_cell(x, y)
if cell is None: if cell is None:
return return
@ -99,7 +103,7 @@ class Maze:
elif cell_type == 'path': elif cell_type == 'path':
cell.is_wall = False cell.is_wall = False
def get_neighbors(self, cell): def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = [] neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions: for dx, dy in directions:
@ -111,105 +115,119 @@ class Maze:
class MazeBuilder: class MazeBuilder:
def build_from_file(self, filename): def build_from_file(self, filename: str) -> Maze:
raise NotImplementedError("Need to realise in calss") raise NotImplementedError("Need to implement in subclass")
class TextFileMazeBuilder(MazeBuilder): class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename): def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r') as f: with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n')for line in f.readlines()] lines = [line.rstrip('\n') for line in f.readlines()]
if not lines:
raise ValueError("Файл пуст")
height = len(lines) height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0 width = max(len(line) for line in lines)
start_en = 0
exit_en = 0 start_count = 0
exit_count = 0
maze = Maze(width, height) maze = Maze(width, height)
for y,line in enumerate(lines): for y, line in enumerate(lines):
for x, ch in enumerate(line): for x, ch in enumerate(line):
if ch == "#": if x >= width:
maze.set_cell(x,y,"wall") continue
elif ch == "S": if ch == '#':
maze.set_cell(x,y,"start") maze.set_cell(x, y, "wall")
start_en+=1 elif ch == 'S':
elif ch == "E": maze.set_cell(x, y, "start")
maze.set_cell(x,y,"exit") start_count += 1
exit_en+=1 elif ch == 'E':
maze.set_cell(x, y, "exit")
exit_count += 1
else: else:
maze.set_cell(x, y, 'path') maze.set_cell(x, y, 'path')
if start_en > 1 or exit_en > 1 or start_en==0 or exit_en ==0:
sys.exit("Error while reading file(you have too many or no match start and exits)") if start_count != 1 or exit_count != 1:
raise ValueError(
f"Лабиринт должен иметь ровно один S и один E. "
f"Найдено: S={start_count}, E={exit_count}"
)
return maze return maze
class PathFindingStrategy:
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
raise NotImplementedError("Subclasses must implement find_path")
def _reconstruct_path(self, came_from: dict, start: Cell, exit_cell: Cell) -> List[Cell]:
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
class BFSStrategy: class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit): def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
queue = deque() queue = deque()
queue.append(start) queue.append(start)
came_from={}
came_from[start]=None came_from = {start: None}
visited = set() visited = {start}
visited.add(start)
while queue: while queue:
current = queue.popleft() current = queue.popleft()
if current == exit:
return self._reconstruct_path(came_from,start,exit)
for neighbors in maze.get_neighbors(current):
if neighbors not in visited:
visited.add(neighbors)
came_from[neighbors] = current
queue.append(neighbors)
return []
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse() if current == exit_cell:
return path return self._reconstruct_path(came_from, start, exit_cell)
class DFSStrategy:
def find_path(self,maze,start,exit): for neighbor in maze.get_neighbors(current):
stack =[] if neighbor not in visited:
stack.append(start) visited.add(neighbor)
came_from={} came_from[neighbor] = current
came_from[start]=None queue.append(neighbor)
visited = set()
visited.add(start) return []
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
visited = {start}
while stack: while stack:
current = stack.pop() current = stack.pop()
if current == exit:
return self._reconstruct_path(came_from,start,exit) if current == exit_cell:
for neighbors in maze.get_neighbors(current): return self._reconstruct_path(came_from, start, exit_cell)
if neighbors not in visited:
visited.add(neighbors) for neighbor in maze.get_neighbors(current):
came_from[neighbors]= current if neighbor not in visited:
stack.append(neighbors) visited.add(neighbor)
came_from[neighbor] = current
stack.append(neighbor)
return [] return []
def _reconstruct_path(self, came_from, start, exit_cell): class AStarStrategy(PathFindingStrategy):
path = [] def _heuristic(self, cell: Cell, exit_cell: Cell) -> int:
current = exit_cell return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse() def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
return path
class AStarStrategy:
def _heuristic(self, cell,exit_cell):
return abs(cell.x -exit_cell.x)+abs(cell.y-exit_cell.y)
def find_path(self,maze, start, exit_cell):
heap = [] heap = []
counter = 0 counter = 0
start_f = 0 + self._heuristic(start,exit_cell)
start_f = self._heuristic(start, exit_cell)
heapq.heappush(heap, (start_f, counter, start)) heapq.heappush(heap, (start_f, counter, start))
counter +=1 counter += 1
came_from = {} came_from = {}
g_score = {start: 0} g_score = {start: 0}
@ -217,12 +235,16 @@ class AStarStrategy:
while heap: while heap:
current_f, _, current = heapq.heappop(heap) current_f, _, current = heapq.heappop(heap)
if current==exit_cell:
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell) return self._reconstruct_path(came_from, start, exit_cell)
if current_f>f_score.get(current, float("inf")):
if current_f > f_score.get(current, float('inf')):
continue continue
for neighbor in maze.get_neighbors(current): for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1 tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')): if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current came_from[neighbor] = current
g_score[neighbor] = tentative_g g_score[neighbor] = tentative_g
@ -230,28 +252,65 @@ class AStarStrategy:
f_score[neighbor] = new_f f_score[neighbor] = new_f
heapq.heappush(heap, (new_f, counter, neighbor)) heapq.heappush(heap, (new_f, counter, neighbor))
counter += 1 counter += 1
return [] return []
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell class SearchStats:
while current is not None: def __init__(self, time_ms: float, visited_cells: int, path_length: int):
path.append(current) self.time_ms = time_ms
current = came_from.get(current) self.visited_cells = visited_cells
path.reverse() self.path_length = path_length
return path
def __repr__(self) -> str:
return f"SearchStats(time={self.time_ms:.3f}ms, visited={self.visited_cells}, path_length={self.path_length})"
class MazeSolver:
def __init__(self, maze: Maze):
self._maze = maze
self._strategy = None
def set_strategy(self, strategy: PathFindingStrategy) -> None:
self._strategy = strategy
def solve(self) -> Optional[SearchStats]:
if self._strategy is None:
print("Ошибка: стратегия не установлена")
return None
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
visited_cells = 0
return SearchStats(time_ms, visited_cells, len(path))
if __name__ == "__main__": if __name__ == "__main__":
builder = TextFileMazeBuilder() builder = TextFileMazeBuilder()
maze = builder.build_from_file("maze1.txt") maze = builder.build_from_file("maze1.txt")
print(f"Лабиринт {maze.width}x{maze.height}") print(f"Лабиринт {maze.width}x{maze.height}")
print(f"Старт: ({maze.start.x}, {maze.start.y})") print(f"Старт: ({maze.start.x}, {maze.start.y})")
print(f"Выход: ({maze.exit.x}, {maze.exit.y})") print(f"Выход: ({maze.exit.x}, {maze.exit.y})")
bfs = BFSStrategy() print("-" * 40)
path = bfs.find_path(maze, maze.start, maze.exit)
print(f"BFS: путь найден, длина = {len(path)}") solver = MazeSolver(maze)
dfs = DFSStrategy()
path = dfs.find_path(maze, maze.start, maze.exit) solver.set_strategy(BFSStrategy())
print(f"DFS: путь найден, длинна = {len(path)}") stats = solver.solve()
astar = AStarStrategy() print(f"BFS: {stats}")
path = astar.find_path(maze, maze.start, maze.exit)
print(f"A*: путь найден, длина = {len(path)}") solver.set_strategy(DFSStrategy())
stats = solver.solve()
print(f"DFS: {stats}")
solver.set_strategy(AStarStrategy())
stats = solver.solve()
print(f"A*: {stats}")