diff --git a/MylnikovAS/task_2/docs/data/results_all.csv b/MylnikovAS/task_2/docs/data/results_all.csv new file mode 100644 index 0000000..8ec170b --- /dev/null +++ b/MylnikovAS/task_2/docs/data/results_all.csv @@ -0,0 +1,21 @@ +labyrint,strategy,time_ms,passed_cells,path_length +Small (10x10),BFS,0.1973,59,27 +Small (10x10),DFS,0.1615,49,27 +Small (10x10),AStar,0.2375,50,27 +Small (10x10),Dijkstra,0.2487,60,27 +Empty (50x50),BFS,8.3316,2500,99 +Empty (50x50),DFS,4.6278,1275,1275 +Empty (50x50),AStar,17.5549,2500,99 +Empty (50x50),Dijkstra,14.7128,2500,99 +Middle with dead ends (50x50),BFS,4.8741,1420,1083 +Middle with dead ends (50x50),DFS,4.3282,1275,1275 +Middle with dead ends (50x50),AStar,7.1452,1404,1083 +Middle with dead ends (50x50),Dijkstra,6.0643,1420,1083 +Big (100x100),BFS,17.0733,5590,199 +Big (100x100),DFS,15.1471,4933,4519 +Big (100x100),AStar,31.4644,5140,199 +Big (100x100),Dijkstra,26.7258,5590,199 +Without exit (10x10),BFS,0.0140,5,0 +Without exit (10x10),DFS,0.0124,5,0 +Without exit (10x10),AStar,0.0164,5,0 +Without exit (10x10),Dijkstra,0.0143,5,0 diff --git a/MylnikovAS/task_2/docs/data/steps_123.py b/MylnikovAS/task_2/docs/data/steps_123.py new file mode 100644 index 0000000..fffb5a4 --- /dev/null +++ b/MylnikovAS/task_2/docs/data/steps_123.py @@ -0,0 +1,190 @@ +import time +import csv +from collections import deque +import heapq +from abc import ABC, abstractmethod + + +class Cell: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + self.isWall = False + self.isStart = False + self.isExit = False + self.weight = 1 + + def isPassable(self) -> bool: + return not self.isWall + + def __lt__(self, other): + return (self.x, self.y) < (other.x, other.y) + + +class Maze: + def __init__(self, width: int, height: int): + self.width = width + self.height = height + self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)] + self.start = None + self.exit = None + + def getCell(self, x: int, y: int) -> Cell: + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[x][y] + return None + + def getNeighbors(self, cell: Cell): + neighbors = [] + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] + for dx, dy in directions: + nx, ny = cell.x + dx, cell.y + dy + neighbor = self.getCell(nx, ny) + if neighbor and neighbor.isPassable(): + neighbors.append(neighbor) + return neighbors + + +class MazeBuilder(ABC): + @abstractmethod + def buildFromStringList(self, lines: list) -> Maze: + pass + + +class TextMazeBuilder(MazeBuilder): + + def buildFromStringList(self, lines: list) -> Maze: + height = len(lines) + width = len(lines[0]) if height > 0 else 0 + maze = Maze(width, height) + + for y, line in enumerate(lines): + for x, char in enumerate(line): + cell = maze.getCell(x, y) + if char == '#': + cell.isWall = True + elif char == 'S': + cell.isStart = True + maze.start = cell + elif char == 'E': + cell.isExit = True + maze.exit = cell + elif char == 'W': + cell.weight = 3 + elif char == 'D': + cell.weight = 2 + return maze + + +class PathFindingStrategy(ABC): + def __init__(self): + self.visited_count = 0 + + @abstractmethod + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: + pass + + def _reconstruct_path(self, came_from: dict, start: Cell, exit: Cell) -> list: + if exit not in came_from: + return [] + path = [] + current = exit + while current != start: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + return path + + +class BFSStrategy(PathFindingStrategy): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: + self.visited_count = 0 + queue = deque([start]) + came_from = {start: None} + + while queue: + current = queue.popleft() + self.visited_count += 1 + if current == exit: + break + for neighbor in maze.getNeighbors(current): + if neighbor not in came_from: + queue.append(neighbor) + came_from[neighbor] = current + return self._reconstruct_path(came_from, start, exit) + + +class DFSStrategy(PathFindingStrategy): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: + self.visited_count = 0 + stack = [start] + came_from = {start: None} + + while stack: + current = stack.pop() + self.visited_count += 1 + if current == exit: + break + for neighbor in maze.getNeighbors(current): + if neighbor not in came_from: + stack.append(neighbor) + came_from[neighbor] = current + return self._reconstruct_path(came_from, start, exit) + + +class AStarStrategy(PathFindingStrategy): + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: + self.visited_count = 0 + + def heuristic(a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + pq = [] + heapq.heappush(pq, (0, start)) + came_from = {start: None} + g_score = {start: 0} + + while pq: + _, current = heapq.heappop(pq) + self.visited_count += 1 + if current == exit: + break + + for neighbor in maze.getNeighbors(current): + tentative_g_score = g_score[current] + neighbor.weight + + if neighbor not in g_score or tentative_g_score < g_score[neighbor]: + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score = tentative_g_score + heuristic(neighbor, exit) + heapq.heappush(pq, (f_score, neighbor)) + + return self._reconstruct_path(came_from, start, exit) + + +class DijkstraStrategy(PathFindingStrategy): + + def findPath(self, maze: Maze, start: Cell, exit: Cell) -> list: + self.visited_count = 0 + pq = [] + heapq.heappush(pq, (0, start)) + came_from = {start: None} + g_score = {start: 0} + + while pq: + current_g, current = heapq.heappop(pq) + self.visited_count += 1 + if current == exit: + break + + for neighbor in maze.getNeighbors(current): + tentative_g_score = g_score[current] + neighbor.weight + + if neighbor not in g_score or tentative_g_score < g_score[neighbor]: + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + heapq.heappush(pq, (tentative_g_score, neighbor)) + + return self._reconstruct_path(came_from, start, exit) + diff --git a/MylnikovAS/task_2/docs/data/steps_4_and_exp.py b/MylnikovAS/task_2/docs/data/steps_4_and_exp.py new file mode 100644 index 0000000..101f773 --- /dev/null +++ b/MylnikovAS/task_2/docs/data/steps_4_and_exp.py @@ -0,0 +1,192 @@ +class SearchStats: + def __init__(self, timeMs: float, visitedCells: int, pathLength: int): + self.timeMs = timeMs + self.visitedCells = visitedCells + self.pathLength = pathLength + + +class MazeSolver: + def __init__(self, maze: Maze, strategy: PathFindingStrategy): + self.maze = maze + self.strategy = strategy + self.observers = [] + + def setStrategy(self, strategy: PathFindingStrategy): + self.strategy = strategy + + def addObserver(self, observer): + self.observers.append(observer) + + def _notify(self, event: str): + for obs in self.observers: + obs.update(event) + + def solve(self) -> tuple: + self._notify("Поиск начат") + start_time = time.perf_counter() + + path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit) + + end_time = time.perf_counter() + time_ms = (end_time - start_time) * 1000 + + stats = SearchStats(time_ms, self.strategy.visited_count, len(path)) + self._notify("Поиск завершен") + return path, stats + + +class Observer(ABC): + @abstractmethod + def update(self, event: str): + pass + + +class ConsoleView(Observer): + def update(self, event: str): + pass + + def render(self, maze: Maze, path: list): + path_set = set(path) + for y in range(maze.height): + row = "" + for x in range(maze.width): + cell = maze.getCell(x, y) + if cell == maze.start: + row += "S" + elif cell == maze.exit: + row += "E" + elif cell in path_set: + row += "*" + elif cell.isWall: + row += "#" + elif cell.weight == 3: + row += "W" # Болото + elif cell.weight == 2: + row += "D" # Песок + else: + row += "." + print(row) + + +def get_test_mazes(): + builder = TextMazeBuilder() + mazes = {} + + small = [ + "S.........", + "#####.####", + "..........", + "####.#####", + "..........", + "#.#######.", + "..........", + "######.###", + "..........", + "........XE" + ] + small[-1] = small[-1].replace('X', '.') + mazes["Small (10x10)"] = builder.buildFromStringList(small) + + empty = ["." * 50 for _ in range(50)] + empty_list = list(empty) + empty_list[0] = "S" + empty_list[0][1:] + empty_list[-1] = empty_list[-1][:-1] + "E" + mazes["Empty (50x50)"] = builder.buildFromStringList(empty_list) + + medium = [] + for y in range(50): + if y == 0: + row = "S" + "." * 49 + elif y == 49: + row = "." * 49 + "E" + elif y % 2 == 1: + row = "#" * 45 + "." * 5 if y % 4 == 1 else "." * 5 + "#" * 45 + else: + row = "." * 50 + medium.append(row) + mazes["Middle with dead ends (50x50)"] = builder.buildFromStringList(medium) + + large = [] + for y in range(100): + if y == 0: row = "S" + "." * 99 + elif y == 99: row = "." * 99 + "E" + elif y % 2 == 1: + row = ("#" * 9 + ".") * 10 + else: + row = "." * 100 + large.append(row) + mazes["Big (100x100)"] = builder.buildFromStringList(large) + + no_exit = [ + "S....#....", + "##########", + "##########", + "##########", + "##########", + "##########", + "##########", + "##########", + "##########", + "######...E" + ] + mazes["Without exit (10x10)"] = builder.buildFromStringList(no_exit) + + + return mazes + + +def main(): + mazes = get_test_mazes() + + strategies = { + "BFS": BFSStrategy(), + "DFS": DFSStrategy(), + "AStar": AStarStrategy(), + "Dijkstra": DijkstraStrategy() + } + + output_rows = [] + + print("Запуск всех тестов...") + print("-" * 70) + + for maze_name, maze in mazes.items(): + print(f"Testing: {maze_name}") + + for strat_name, strategy in strategies.items(): + + solver = MazeSolver(maze, strategy) + + runs = 5 + total_time = 0 + path = [] + stats = None + + for _ in range(runs): + path, stats = solver.solve() + total_time += stats.timeMs + + avg_time = total_time / runs + + output_rows.append([ + maze_name, + strat_name, + f"{avg_time:.4f}", + stats.visitedCells, + stats.pathLength + ]) + + print( + f" -> {strat_name}: Time: {avg_time:.3f}ms | Passed cells: {stats.visitedCells} | Path length: {stats.pathLength}") + print("-" * 70) + + with open("results_all.csv", "w", newline="", encoding="utf-8") as csvfile: + writer = csv.writer(csvfile) + writer.writerow(["labyrint", "strategy", "time_ms", "passed_cells", "path_length"]) + writer.writerows(output_rows) + + print("Все замеры успешно выполнены! Результаты сохранены в 'results_all.csv'") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/MylnikovAS/task_2/docs/Отчёт по лабораторной работе.docx b/MylnikovAS/task_2/docs/Отчёт по лабораторной работе.docx new file mode 100644 index 0000000..d34e71b Binary files /dev/null and b/MylnikovAS/task_2/docs/Отчёт по лабораторной работе.docx differ