diff --git a/KislyuninED/docks/data/2-st-exercize/maze-plots.py b/KislyuninED/docks/data/2-st-exercize/maze-plots.py new file mode 100644 index 0000000..22e7625 --- /dev/null +++ b/KislyuninED/docks/data/2-st-exercize/maze-plots.py @@ -0,0 +1,402 @@ +import sys +import csv +from collections import deque +import heapq +import time +import matplotlib.pyplot as plt +import numpy as np + + +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 + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + @property + def is_wall(self): + return self._is_wall + + @is_wall.setter + def is_wall(self, value): + self._is_wall = value + + @property + def is_start(self): + return self._is_start + + @is_start.setter + def is_start(self, value): + self._is_start = value + + @property + def is_exit(self): + return self._is_exit + + @is_exit.setter + def is_exit(self, value): + self._is_exit = value + + def is_passable(self): + return not self._is_wall + + +class Maze: + def __init__(self, width, height): + self._width = width + self._height = height + self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)] + self._start = None + self._exit = None + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def start(self): + return self._start + + @property + def exit(self): + return self._exit + + def get_cell(self, x, y): + if 0 <= x < self._width and 0 <= y < self._height: + return self._cells[y][x] + return None + + def set_cell(self, x, y, cell_type): + cell = self.get_cell(x, y) + if cell is None: + return + + if cell_type == 'wall': + cell.is_wall = True + elif cell_type == 'start': + if self._start: + self._start.is_start = False + cell.is_start = True + cell.is_wall = False + self._start = cell + elif cell_type == 'exit': + if self._exit: + self._exit.is_exit = False + cell.is_exit = True + cell.is_wall = False + self._exit = cell + elif cell_type == 'path': + cell.is_wall = False + + def get_neighbors(self, 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.get_cell(nx, ny) + if neighbor and neighbor.is_passable(): + neighbors.append(neighbor) + return neighbors + + +class MazeBuilder: + def build_from_file(self, filename): + raise NotImplementedError + + +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename): + with open(filename, 'r') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + height = len(lines) + width = max(len(line) for line in lines) if height > 0 else 0 + start_en = 0 + exit_en = 0 + maze = Maze(width, height) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if ch == "#": + maze.set_cell(x, y, "wall") + elif ch == "S": + maze.set_cell(x, y, "start") + start_en += 1 + elif ch == "E": + maze.set_cell(x, y, "exit") + exit_en += 1 + else: + maze.set_cell(x, y, 'path') + if start_en != 1 or exit_en != 1: + raise ValueError(f"Invalid maze: S={start_en}, E={exit_en}") + return maze + + +class PathFindingStrategy: + def find_path(self, maze, start, exit_cell): + raise NotImplementedError + + 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() + return path + + def get_visited_count(self): + return getattr(self, '_visited_count', 0) + + +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit_cell): + queue = deque() + queue.append(start) + came_from = {start: None} + visited = {start} + + while queue: + current = queue.popleft() + if current == exit_cell: + self._visited_count = len(visited) + return self._reconstruct_path(came_from, start, exit_cell) + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + came_from[neighbor] = current + queue.append(neighbor) + self._visited_count = len(visited) + return [] + + +class DFSStrategy(PathFindingStrategy): + def find_path(self, maze, start, exit_cell): + stack = [start] + came_from = {start: None} + visited = {start} + + while stack: + current = stack.pop() + if current == exit_cell: + self._visited_count = len(visited) + return self._reconstruct_path(came_from, start, exit_cell) + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + came_from[neighbor] = current + stack.append(neighbor) + self._visited_count = len(visited) + return [] + + +class AStarStrategy(PathFindingStrategy): + 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 = [] + counter = 0 + start_f = self._heuristic(start, exit_cell) + heapq.heappush(heap, (start_f, counter, start)) + counter += 1 + + came_from = {} + g_score = {start: 0} + f_score = {start: start_f} + visited = set() + + while heap: + current_f, _, current = heapq.heappop(heap) + visited.add(current) + + if current == exit_cell: + self._visited_count = len(visited) + return self._reconstruct_path(came_from, start, exit_cell) + if current_f > f_score.get(current, float('inf')): + continue + for neighbor in maze.get_neighbors(current): + tentative_g = g_score[current] + 1 + if tentative_g < g_score.get(neighbor, float('inf')): + came_from[neighbor] = current + g_score[neighbor] = tentative_g + new_f = tentative_g + self._heuristic(neighbor, exit_cell) + f_score[neighbor] = new_f + heapq.heappush(heap, (new_f, counter, neighbor)) + counter += 1 + self._visited_count = len(visited) + return [] + + +class MazeSolver: + def __init__(self, maze): + self._maze = maze + self._strategy = None + + def set_strategy(self, strategy): + self._strategy = strategy + + def solve(self): + if self._strategy is None: + 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 + + return { + 'time_ms': time_ms, + 'visited_cells': self._strategy.get_visited_count(), + 'path_length': len(path) + } + + +def run_experiment(maze_file, strategy, runs=5): + builder = TextFileMazeBuilder() + maze = builder.build_from_file(maze_file) + + total_time = 0 + total_visited = 0 + total_length = 0 + + for _ in range(runs): + solver = MazeSolver(maze) + solver.set_strategy(strategy) + stats = solver.solve() + if stats: + total_time += stats['time_ms'] + total_visited += stats['visited_cells'] + total_length += stats['path_length'] + + return { + 'time_ms': total_time / runs, + 'visited_cells': total_visited / runs, + 'path_length': total_length / runs + } + + +def generate_plots(results): + mazes = list(set([r['maze'] for r in results])) + strategies = ['BFS', 'DFS', 'AStar'] + + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + + x = np.arange(len(mazes)) + width = 0.25 + + for i, strat in enumerate(strategies): + times = [] + for maze in mazes: + val = next((r['time_ms'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0) + times.append(val) + axes[0].bar(x + i*width, times, width, label=strat) + + axes[0].set_xlabel('Maze') + axes[0].set_ylabel('Time (ms)') + axes[0].set_title('Execution Time Comparison') + axes[0].set_xticks(x + width) + axes[0].set_xticklabels(mazes, rotation=45, ha='right') + axes[0].legend() + axes[0].grid(True, alpha=0.3) + + for i, strat in enumerate(strategies): + visited = [] + for maze in mazes: + val = next((r['visited_cells'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0) + visited.append(val) + axes[1].bar(x + i*width, visited, width, label=strat) + + axes[1].set_xlabel('Maze') + axes[1].set_ylabel('Visited Cells') + axes[1].set_title('Visited Cells Comparison') + axes[1].set_xticks(x + width) + axes[1].set_xticklabels(mazes, rotation=45, ha='right') + axes[1].legend() + axes[1].grid(True, alpha=0.3) + + for i, strat in enumerate(strategies): + lengths = [] + for maze in mazes: + val = next((r['path_length'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0) + lengths.append(val) + axes[2].bar(x + i*width, lengths, width, label=strat) + + axes[2].set_xlabel('Maze') + axes[2].set_ylabel('Path Length') + axes[2].set_title('Path Length Comparison') + axes[2].set_xticks(x + width) + axes[2].set_xticklabels(mazes, rotation=45, ha='right') + axes[2].legend() + axes[2].grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig('performance_comparison_2-nd-exercise.png', dpi=150, bbox_inches='tight') + plt.show() + + +if __name__ == "__main__": + mazes = [ + ("maze1.txt", "Small 10x6"), + ("maze10x10.txt", "Medium 10x10"), + ("maze20x20.txt", "Large 20x20"), + ("maze_empty.txt", "Empty 15x15"), + ("maze_no_exit.txt", "No exit 10x10") + ] + + strategies = [ + ("BFS", BFSStrategy()), + ("DFS", DFSStrategy()), + ("AStar", AStarStrategy()) + ] + + results = [] + + for maze_file, maze_name in mazes: + print(f"Testing {maze_name}...") + for strat_name, strat in strategies: + try: + stats = run_experiment(maze_file, strat, runs=3) + results.append({ + 'maze': maze_name, + 'strategy': strat_name, + 'time_ms': stats['time_ms'], + 'visited_cells': stats['visited_cells'], + 'path_length': stats['path_length'] + }) + print(f" {strat_name}: time={stats['time_ms']:.3f}ms, visited={stats['visited_cells']:.0f}, length={stats['path_length']:.0f}") + except Exception as e: + print(f" {strat_name}: ERROR - {e}") + results.append({ + 'maze': maze_name, + 'strategy': strat_name, + 'time_ms': -1, + 'visited_cells': -1, + 'path_length': -1 + }) + + valid_results = [r for r in results if r['time_ms'] >= 0] + + with open('experiment_results_2-nd-exercise.csv', 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length']) + writer.writeheader() + writer.writerows(valid_results) + + if valid_results: + generate_plots(valid_results) + + print("\nResults saved to experiment_results_2-nd-exercise.csv") + print("Plot saved to performance_comparison_2-nd-exercise.png") \ No newline at end of file