2026-rff_mp/lukovnikovde/docs/data/2-nd-exercise/main.py

220 lines
6.4 KiB
Python

import sys
import os
from collections import deque
import heapq
import time
class Tile:
"""Клетка лабиринта."""
def __init__(self, x, y):
self.x = x
self.y = y
self.wall = False
self.start = False
self.exit = False
def passable(self):
return not self.wall
class Labyrinth:
"""Модель лабиринта."""
def __init__(self, width, height):
self.width = width
self.height = height
self._tiles = [[Tile(x, y) for x in range(width)] for y in range(height)]
self.start_tile = None
self.exit_tile = None
def get_tile(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self._tiles[y][x]
return None
def set_tile(self, x, y, kind):
tile = self.get_tile(x, y)
if tile is None:
return
if kind == 'wall':
tile.wall = True
elif kind == 'start':
if self.start_tile:
self.start_tile.start = False
tile.start = True
tile.wall = False
self.start_tile = tile
elif kind == 'exit':
if self.exit_tile:
self.exit_tile.exit = False
tile.exit = True
tile.wall = False
self.exit_tile = tile
elif kind == 'path':
tile.wall = False
def neighbours(self, tile):
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
result = []
for dx, dy in dirs:
nx, ny = tile.x + dx, tile.y + dy
nb = self.get_tile(nx, ny)
if nb and nb.passable():
result.append(nb)
return result
class LabyrinthLoader:
def load(self, filename):
raise NotImplementedError
class TextLabyrinthLoader(LabyrinthLoader):
def load(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f]
h = len(lines)
w = max(len(line) for line in lines) if h > 0 else 0
start_cnt = 0
exit_cnt = 0
lab = Labyrinth(w, h)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == '#':
lab.set_tile(x, y, 'wall')
elif ch == 'S':
lab.set_tile(x, y, 'start')
start_cnt += 1
elif ch == 'E':
lab.set_tile(x, y, 'exit')
exit_cnt += 1
else:
lab.set_tile(x, y, 'path')
if start_cnt != 1 or exit_cnt != 1:
raise ValueError(f"Нужны ровно S и E, найдено S={start_cnt}, E={exit_cnt}")
return lab
class Pathfinder:
def find_path(self, lab, start, goal):
raise NotImplementedError
def _build_path(self, came_from, start, goal):
path = []
cur = goal
while cur is not None:
path.append(cur)
cur = came_from.get(cur)
path.reverse()
return path
def visited_count(self):
return getattr(self, '_visited', 0)
class BFS(Pathfinder):
def find_path(self, lab, start, goal):
q = deque([start])
parent = {start: None}
visited = {start}
while q:
cur = q.popleft()
if cur == goal:
self._visited = len(visited)
return self._build_path(parent, start, goal)
for nb in lab.neighbours(cur):
if nb not in visited:
visited.add(nb)
parent[nb] = cur
q.append(nb)
self._visited = len(visited)
return []
class DFS(Pathfinder):
def find_path(self, lab, start, goal):
stack = [start]
parent = {start: None}
visited = {start}
while stack:
cur = stack.pop()
if cur == goal:
self._visited = len(visited)
return self._build_path(parent, start, goal)
for nb in lab.neighbours(cur):
if nb not in visited:
visited.add(nb)
parent[nb] = cur
stack.append(nb)
self._visited = len(visited)
return []
class AStar(Pathfinder):
def _heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, lab, start, goal):
heap = []
counter = 0
start_f = self._heuristic(start, goal)
heapq.heappush(heap, (start_f, counter, start))
counter += 1
parent = {}
g = {start: 0}
f = {start: start_f}
visited = set()
while heap:
cur_f, _, cur = heapq.heappop(heap)
visited.add(cur)
if cur == goal:
self._visited = len(visited)
return self._build_path(parent, start, goal)
if cur_f > f.get(cur, float('inf')):
continue
for nb in lab.neighbours(cur):
new_g = g[cur] + 1
if new_g < g.get(nb, float('inf')):
parent[nb] = cur
g[nb] = new_g
new_f = new_g + self._heuristic(nb, goal)
f[nb] = new_f
heapq.heappush(heap, (new_f, counter, nb))
counter += 1
self._visited = len(visited)
return []
class LabyrinthSolver:
def __init__(self, lab):
self.lab = lab
self.strategy = None
def set_strategy(self, strategy):
self.strategy = strategy
def solve(self):
if not self.strategy:
return None
t0 = time.perf_counter()
path = self.strategy.find_path(self.lab, self.lab.start_tile, self.lab.exit_tile)
t1 = time.perf_counter()
return {
'time_ms': (t1 - t0) * 1000,
'visited': self.strategy.visited_count(),
'length': len(path)
}
if __name__ == "__main__":
loader = TextLabyrinthLoader()
lab = loader.load("maze/maze1.txt")
print("Загружен лабиринт, пробуем BFS...")
solver = LabyrinthSolver(lab)
solver.set_strategy(BFS())
stats = solver.solve()
if stats['length'] > 0:
print(f"BFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
else:
print("Путь не найден")