[2] 2nd zadanie, otchet, benchmark & cool_pics

This commit is contained in:
danch0us 2026-05-22 17:54:05 +03:00
parent 949252d245
commit 56a26a676a
9 changed files with 605 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

48
kornevma/docs/2/main.py Normal file
View File

@ -0,0 +1,48 @@
import csv
from maze import *
from maze_generator import *
def run_experiments():
maze_configs = [
("small_random", lambda: random_maze(15, 15, wall_prob=0.3)),
("medium_recursive_div", lambda: recursive_division_maze(31, 31)), #odd хз
("large_empty", lambda: empty_maze(100, 100)),
("large_random", lambda: random_maze(100, 100, wall_prob=0.25)),
("no_path", lambda: no_path_maze(20, 20)),
]
algorithms = [("BFS", BFSPathFinding()),
("DFS", DFSPathFinding()),
("A*", AStarPathFinding())]
results = []
for name, gen_func in maze_configs:
maze = gen_func()
for alg_name, strategy in algorithms:
solver = MazeSolver(maze, strategy)
times, visited, lengths = [], [], []
for _ in range(5):
stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited)
lengths.append(stats.path_length)
avg_t = sum(times) / len(times)
avg_v = sum(visited) / len(visited)
avg_l = sum(lengths) / len(lengths)
results.append([name, alg_name, avg_t, avg_v, avg_l])
print(f"{name:20} {alg_name:5} time={avg_t:8.2f}ms visited={avg_v:8.1f} length={avg_l:5.1f}")
with open("results_maze.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["maze", "algorithm", "time_ms", "visited", "path_length"])
writer.writerows(results)
print("saved results_maze.csv")
if __name__ == "__main__":
import sys
DEBUG = True
if (len(sys.argv) > 1 and sys.argv[1] == "exp") or DEBUG:
run_experiments()
else:
#run_interactive()
pass

296
kornevma/docs/2/maze.py Normal file
View File

@ -0,0 +1,296 @@
import heapq
import time
import os
from collections import deque
from abc import ABC, abstractmethod
import itertools
class Cell:
def __init__(self, x, y):
self.x = x
self.y = y
self.isWall = False
self.isStart = False
self.isExit = False
self.weight = 1
def isPassable(self):
return not self.isWall
def __repr__(self):
return f"({self.x},{self.y})"
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start = None
self.exit = None
def getCell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
return None
def getNeighbors(self, cell):
dirs = [(-1,0), (1,0), (0,-1), (0,1)]
result = []
for dx, dy in dirs:
nx, ny = cell.x + dx, cell.y + dy
ncell = self.getCell(nx, ny)
if ncell and ncell.isPassable():
result.append(ncell)
return result
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f if line.strip() != '']
height = len(lines)
width = len(lines[0]) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.getCell(x, y)
if ch == '#':
cell.isWall = True
elif ch == 'S':
cell.isStart = True
maze.start = cell
elif ch == 'E':
cell.isExit = True
maze.exit = cell
elif ch == ' ':
pass
else:
if ch.isdigit():
cell.weight = int(ch)
else:
raise ValueError(f"err '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("not e or/and s")
return maze
class PathFindingStrategy(ABC):
def __init__(self):
self.visited_count = 0
@abstractmethod
def findPath(self, maze, start, exit_cell):
pass
class BFSPathFinding(PathFindingStrategy):
def findPath(self, maze, start, exit_cell):
self.visited_count = 0
queue = deque()
queue.append(start)
parent = {start: None}
while queue:
current = queue.popleft()
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(parent, exit_cell)
for neighbor in maze.getNeighbors(current):
if neighbor not in parent:
parent[neighbor] = current
queue.append(neighbor)
return []
def _reconstruct_path(self, parent, end):
path = []
cur = end
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path
class DFSPathFinding(PathFindingStrategy):
def findPath(self, maze, start, exit_cell):
self.visited_count = 0
stack = [start]
parent = {start: None}
while stack:
current = stack.pop()
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(parent, exit_cell)
for neighbor in maze.getNeighbors(current):
if neighbor not in parent:
parent[neighbor] = current
stack.append(neighbor)
return []
def _reconstruct_path(self, parent, end):
path = []
cur = end
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path
class AStarPathFinding(PathFindingStrategy):
def findPath(self, maze, start, exit_cell):
self.visited_count = 0
def heuristic(cell):
return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
open_set = []
counter = itertools.count() #
heapq.heappush(open_set, (0 + heuristic(start), 0, next(counter), start))
parent = {start: None}
g_score = {start: 0}
closed = set()
while open_set:
_, cost, _, current = heapq.heappop(open_set)
self.visited_count += 1
if current in closed:
continue
if current == exit_cell:
return self._reconstruct_path(parent, exit_cell)
closed.add(current)
for neighbor in maze.getNeighbors(current):
tentative_g = g_score[current] + neighbor.weight
if neighbor not in g_score or tentative_g < g_score[neighbor]:
g_score[neighbor] = tentative_g
f = tentative_g + heuristic(neighbor)
heapq.heappush(open_set, (f, tentative_g, next(counter), neighbor))
parent[neighbor] = current
return []
def _reconstruct_path(self, parent, end):
path = []
cur = end
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path
class SearchStats:
def __init__(self, time_ms, visited, path_length, path):
self.time_ms = time_ms
self.visited = visited
self.path_length = path_length
self.path = path
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
start = self.maze.start
exit_cell = self.maze.exit
t0 = time.perf_counter()
path = self.strategy.findPath(self.maze, start, exit_cell)
t1 = time.perf_counter()
ms = (t1 - t0) * 1000
visited = self.strategy.visited_count
stats = SearchStats(ms, visited, len(path), path)
self.notify("path_found", stats)
return stats
def addObserver(self, observer):
self.observers.append(observer)
def notify(self, event, data=None):
for obs in self.observers:
obs.update(event, data)
class Observer(ABC):
@abstractmethod
def update(self, event, data):
pass
class ConsoleView(Observer):
def __init__(self, maze):
self.maze = maze
def update(self, event, data):
if event == "path_found":
self.render(data.path, data)
def render(self, path, stats=None):
os.system('cls' if os.name == 'nt' else 'clear')
path_set = set(path) if path else set()
for y in range(self.maze.height):
line = ""
for x in range(self.maze.width):
cell = self.maze.getCell(x, y)
if cell == self.maze.start:
line += "S"
elif cell == self.maze.exit:
line += "E"
elif cell.isWall:
line += "#"
elif cell in path_set:
line += "."
else:
line += " "
print(line)
if stats:
print(f"\npath: {stats.path_length}, visit: {stats.visited}, time: {stats.time_ms:.2f} ms")
class Player:
def __init__(self, start_cell):
self.current = start_cell
self.history = []
def move(self, dx, dy, maze):
nx, ny = self.current.x + dx, self.current.y + dy
ncell = maze.getCell(nx, ny)
if ncell and ncell.isPassable():
self.history.append(self.current)
self.current = ncell
return True
return False
def undo(self):
if self.history:
self.current = self.history.pop()
return True
return False
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player, maze, dx, dy):
self.player = player
self.maze = maze
self.dx = dx
self.dy = dy
self.executed = False
def execute(self):
if not self.executed:
success = self.player.move(self.dx, self.dy, self.maze)
self.executed = success
return success
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
return True
return False

View File

@ -0,0 +1,114 @@
import random
from collections import deque
from maze import Maze, Cell
def empty_maze(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.grid[x][y].isWall = False
maze.start = maze.getCell(0, 0)
maze.start.isStart = True
maze.exit = maze.getCell(width-1, height-1)
maze.exit.isExit = True
return maze
def random_maze(width, height, wall_prob=0.3, ensure_path=True):
while True:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < wall_prob:
maze.grid[x][y].isWall = True
else:
maze.grid[x][y].isWall = False
start_cell = maze.getCell(0, 0)
exit_cell = maze.getCell(width-1, height-1)
start_cell.isWall = False
start_cell.isStart = True
exit_cell.isWall = False
exit_cell.isExit = True
maze.start = start_cell
maze.exit = exit_cell
if not ensure_path:
return maze
if _path_exists(maze, start_cell, exit_cell):
return maze
def no_path_maze(width, height):
maze = empty_maze(width, height)
exit_cell = maze.exit
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = exit_cell.x + dx, exit_cell.y + dy
neighbor = maze.getCell(nx, ny)
if neighbor:
neighbor.isWall = True
return maze
def recursive_division_maze(width, height):
if width % 2 == 0:
width += 1
if height % 2 == 0:
height += 1
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.grid[x][y].isWall = False
maze.start = maze.getCell(0, 0)
maze.start.isStart = True
maze.exit = maze.getCell(width-1, height-1)
maze.exit.isExit = True
for x in range(width):
maze.getCell(x, 0).isWall = True
maze.getCell(x, height-1).isWall = True
for y in range(height):
maze.getCell(0, y).isWall = True
maze.getCell(width-1, y).isWall = True
maze.start.isWall = False
maze.exit.isWall = False
def divide(x1, y1, x2, y2):
if x2 - x1 < 2 or y2 - y1 < 2:
return
vertical = (x2 - x1) > (y2 - y1) and (x2 - x1) >= 2
if vertical:
wall_x = random.randrange(x1 + 1, x2, 2)
hole_y = random.randrange(y1, y2 + 1, 2) if (y2 - y1) > 0 else y1
for y in range(y1, y2 + 1):
if y != hole_y:
maze.getCell(wall_x, y).isWall = True
divide(x1, y1, wall_x - 1, y2)
divide(wall_x + 1, y1, x2, y2)
else:
wall_y = random.randrange(y1 + 1, y2, 2)
hole_x = random.randrange(x1, x2 + 1, 2) if (x2 - x1) > 0 else x1
for x in range(x1, x2 + 1):
if x != hole_x:
maze.getCell(x, wall_y).isWall = True
divide(x1, y1, x2, wall_y - 1)
divide(x1, wall_y + 1, x2, y2)
divide(0, 0, width-1, height-1)
return maze
def _path_exists(maze, start, exit_cell):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
cur = queue.popleft()
if cur == exit_cell:
return True
for n in maze.getNeighbors(cur):
if n not in visited:
visited.add(n)
queue.append(n)
return False

View File

@ -0,0 +1,6 @@
#######
#S #
# ### #
# # E #
# # #
#######

BIN
kornevma/docs/2/mermaid.png Normal file

Binary file not shown.

View File

@ -0,0 +1,16 @@
maze,algorithm,time_ms,visited,path_length
small_random,BFS,0.2025800000410527,79.0,31.0
small_random,DFS,0.18165999208576977,75.0,35.0
small_random,A*,0.24926000041887164,62.0,31.0
medium_recursive_div,BFS,0.003279995871707797,1.0,0.0
medium_recursive_div,DFS,0.002820009831339121,1.0,0.0
medium_recursive_div,A*,0.004719995195046067,1.0,0.0
large_empty,BFS,28.160699998261407,10000.0,199.0
large_empty,DFS,16.872200003126636,5149.0,4951.0
large_empty,A*,47.75527999736369,10000.0,199.0
large_random,BFS,20.68703998811543,7396.0,201.0
large_random,DFS,18.394460005220026,6029.0,615.0
large_random,A*,10.62775999889709,2215.0,201.0
no_path,BFS,1.0112400050275028,397.0,0.0
no_path,DFS,1.0159599944017828,397.0,0.0
no_path,A*,1.6842399956658483,397.0,0.0
Internal Server Error - DRE.lab repo

Internal Server Error

Gitea Version: 1.22.0