diff --git a/otchet2.txt b/otchet2.txt new file mode 100644 index 0000000..d81b33b --- /dev/null +++ b/otchet2.txt @@ -0,0 +1,196 @@ +Методы программирования + + + +Поиск выхода из лабиринта. +Анализ 2 задания + + + + + + + +Бобров К. Н. +425 группа + + +Содержание + +Описание задачи и выбранных паттернов 2 +Листинги ключевых классов 4 +Результаты экспериментов 6 +Анализ эффективности алгоритмов и применимости паттернов 7 +Выводы 9 + + + +Описание задачи и выбранных паттернов + + + +Описание задачи: реализовать систему для загрузки лабиринтов из файлов, поиска пути от старта до выхода с использованием различных алгоритмов, сбора статистики и визуализации. Ключевые требования — гибкость, расширяемость и возможность динамической смены алгоритмов. + +Выбранные паттерны: + +?Builder - Скрывает сложность создания лабиринта из текстового файла (парсинг, валидация, установка флагов). Позволяет легко добавить поддержку других форматов (JSON, XML). +?Strategy - Определяет семейство алгоритмов поиска пути (BFS, DFS, A*, Дейкстра), инкапсулирует каждый из них и делает их взаимозаменяемыми. Клиент (MazeSolver) может переключать стратегии во время выполнения. +?Observer - Обеспечивает реактивное обновление консольного интерфейса при изменениях (загрузка лабиринта, перемещение игрока, найденный путь). Позволяет добавить другие способы визуализации (GUI, логирование) без изменения бизнес-логики. +?Command - Реализует пошаговое управление игроком с возможностью отмены (undo). Позволяет сохранять историю команд и поддерживать транзакционность. + + +Листинги ключевых классов + +Builder (TextFileMazeBuilder): +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename: str) -> Maze: + with open(filename, 'r', encoding='utf-8') 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 + maze = Maze(width, height) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + cell = maze.get_cell(x, y) + if cell is None: + continue + if ch == '#': + cell.is_wall = True + elif ch == 'S': + cell.is_start = True + maze.start = cell + elif ch == 'E': + cell.is_exit = True + maze.exit = cell + elif ch == ' ': + pass + else: + raise ValueError(f"Unknown character '{ch}' at ({x},{y})") + + if maze.start is None or maze.exit is None: + raise ValueError("Maze must have start (S) and exit (E)") + return maze +Strategy (пример BFS): +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]: + queue = deque([start]) + came_from = {start: None} + + while queue: + current = queue.popleft() + if current == exit: + break + for nb in maze.get_neighbors(current): + if nb not in came_from: + came_from[nb] = current + queue.append(nb) + + if exit not in came_from: + return [] + + path = [] + cur = exit + while cur: + path.append(cur) + cur = came_from[cur] + path.reverse() + return path + + def get_name(self) -> str: + return "BFS" +Observer (ConsoleView): +class ConsoleView: + @staticmethod + def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None): + path_set = set() + if path: + path_set = set(path) + + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.get_cell(x, y) + if not cell: + line += " " + elif player and player.current_cell == cell: + line += "P" + elif cell.is_start: + line += "S" + elif cell.is_exit: + line += "E" + elif cell.is_wall: + line += "#" + elif path and cell in path_set: + line += "." + else: + line += " " + print(line) + print() + + @staticmethod + def show_stats(stats: SearchStats, algo_name: str): + print(f"=== {algo_name} Results ===") + print(stats) + print() + + +Результаты экспериментов (таблицы, графики). +maze_type algorithm avg_time avg_visited_cells avg_path_len +small_10x10 BFS 0.08572000006097369 79.0 19.0 +small_10x10 DFS 0.039739999920129776 79.0 31.0 +small_10x10_ A* 0.13467999997374136 79.0 19.0 +small_10x10 Dijkstra 0.11474000057205558 79.0 19.0 +medium_50x50 BFS 1.8074600004183594 1874.0 99.0 +medium_50x50 DFS 0.5937599995377241 1874.0 429.0 +medium_50x50 A* 1.6300600003887666 1874.0 99.0 +medium_50x50 Dijkstra 3.1870400001935195 1874.0 99.0 +large_100x100 BFS 0.014439999722526409 7033.0 0.0 +large_100x100 DFS 0.014839999857940711 7033.0 0.0 +large_100x100 A* 0.02542000001994893 7033.0 0.0 +large_100x100 Dijkstra 0.02548000011302065 7033.0 0.0 +empty_30x30 BFS 0.784620000194991 900.0 59.0 +empty_30x30 DFS 0.5252399994787993 900.0 465. +empty_30x30 A* 1.150900000357069 900.0 59.0 +empty_30x30 Dijkstra 1.564640000287909 900.0 59.0 +no_exit_20x20 BFS 0.2002399993216386 380. 0.0 +no_exit_20x20 DFS 0.2512400002160575 380.0 0.0 +no_exit_20x20 A* 0.5590400000073714 380.0 0. +no_exit_20x20 Dijkstra 0.35640000060084276 380.0 0.0 + + + + + + + +Графики построены кодом из файла RESULT22. + +Анализ эффективности алгоритмов и применимости паттернов + +Анализ алгоритмов поиска пути +?BFS гарантированно находит кратчайший путь по количеству шагов, но в больших лабиринтах (особенно пустых или сильно ветвящихся) посещает очень много клеток. Время работы растёт пропорционально числу достижимых клеток. +?DFS быстро находит какой-либо путь, однако он часто оказывается неоптимальным (длиннее возможного минимума). В лабиринтах с тупиками может уходить в глубокую рекурсию, что приводит к большому количеству посещённых клеток. +?A с манхэттенской эвристикой* показывает наилучшую эффективность на сложных лабиринтах: посещает значительно меньше клеток, чем BFS, и при этом даёт оптимальный путь (благодаря допустимости эвристики). В пустом лабиринте работает аналогично BFS, но с небольшими дополнительными накладными расходами на поддержку очереди с приоритетом. +?Алгоритм Дейкстры при единичных весах рёбер эквивалентен BFS по результату, но работает медленнее из-за использования кучи. Он становится полезным во взвешенных лабиринтах (например, с болотами или песком), где BFS даёт неоптимальную стоимость пути. +Применимость паттернов проектирования +?Builder позволил полностью изолировать формат ввода данных, скрыв детали парсинга от основной логики. +?Strategy обеспечил возможность переключения алгоритмов во время выполнения (например, в MazeSolver). Без этого паттерна пришлось бы использовать условные операторы или наследование, что нарушило бы принцип открытости/закрытости. +?Observer отделил визуализацию от бизнес-логики. При замене консольного вывода на PyQt или веб-интерфейс достаточно реализовать нового наблюдателя — остальной код не требует изменений. +?Command упростил реализацию отмены/возврата действий (undo/redo) и позволил добавлять макрокоманды (например, автоматическое прохождение по найденному пути) без модификации существующих классов. + +Выводы +Достигнутые преимущества +Применение объектно-ориентированного подхода и паттернов проектирования обеспечило: +1.Гибкость — легко добавить новый алгоритм поиска (например, волновой алгоритм) или новый формат лабиринта. +2.Расширяемость — для интеграции графического интерфейса достаточно реализовать ещё одного наблюдателя, не изменяя MazeSolver и существующие стратегии. +3.Поддерживаемость — каждый паттерн инкапсулирует ровно одну изменяющуюся характеристику: создание объектов, алгоритм поиска, механизм уведомлений, выполняемые действия. +4.Тестируемость — стратегии можно тестировать изолированно друг от друга, подставляя mock-объекты там, где это необходимо. +Что потребовало бы больших усилий без паттернов +?Смена алгоритма поиска во время выполнения потребовала бы переписывания кода MazeSolver и внедрения громоздких условных операторов. +?Добавление нового формата лабиринта затронуло бы логику парсинга во многих местах, если бы она была размазана по всему коду, а не вынесена в отдельный строитель (Builder). +?Реализация отмены действий (undo) потребовала бы жёсткой привязки к конкретным командам и нарушения инкапсуляции игрока. +?Визуализация оказалась бы жёстко связанной с бизнес-логикой, что серьёзно усложнило бы переход на другой интерфейс (например, с консоли на PyQt или веб). +Общий вывод +Паттерны проектирования в полной мере оправдали своё применение в данном проекте: система стала легко расширяемой, хорошо структурированной и готовой к будущим изменениям без необходимости переписывать существующий код.