diff --git a/nikolaevda/docs/data/experiment_results.csv b/nikolaevda/docs/data/experiment_results.csv new file mode 100644 index 0000000..70872e1 --- /dev/null +++ b/nikolaevda/docs/data/experiment_results.csv @@ -0,0 +1,19 @@ +Структура;Режим;Операция;Замер1(с);Замер2(с);Замер3(с);Замер4(с);Замер5(с);Среднее(с) +linked_list;случайный;вставка;4.743085900001461;4.702243700005056;4.426778699998977;4.3052682999987155;4.301903599996876;4.495856040000217 +linked_list;случайный;поиск;0.040070499999274034;0.03833870000380557;0.038309099996695295;0.038068900001235306;0.037999300002411474;0.03855730000068434 +linked_list;случайный;удаление;0.03337140000076033;0.03520829999615671;0.03318629999557743;0.03670069999498082;0.03511889999936102;0.03471711999736726 +hash_table;случайный;вставка;0.054787300003226846;0.038778399997681845;0.038185400000656955;0.03906660000211559;0.040834699997503776;0.042330480000237 +hash_table;случайный;поиск;0.00048270000115735456;0.0003393000006326474;0.00034130000130971894;0.0003389000048628077;0.0003389000048628077;0.00036822000256506724 +hash_table;случайный;удаление;0.00018000000272877514;0.0001720000000204891;0.0001720000000204891;0.0001764999979059212;0.0001747999995131977;0.00017506000003777444 +bst;случайный;вставка;0.04329969999525929;0.04011429999809479;0.0377946999942651;0.03973660000337986;0.03843010000127833;0.03987507999845548 +bst;случайный;поиск;0.0005353999949875288;0.0004243000003043562;0.00040499999886378646;0.00041709999641170725;0.00041870000131893903;0.00044009999837726357 +bst;случайный;удаление;0.08770270000240998;0.08755029999883845;0.09487290000106441;0.08564219999971101;0.08784590000141179;0.08872280000068714 +linked_list;отсортированный;вставка;5.82706280000275;5.942067100004351;6.058909300001687;5.410613899999589;5.423316100001102;5.732393840001896 +linked_list;отсортированный;поиск;0.05126659999950789;0.04912999999942258;0.04894649999914691;0.048823999997694045;0.0484264999977313;0.04931871999870054 +linked_list;отсортированный;удаление;0.03424879999511177;0.03367250000155764;0.03369569999631494;0.03390580000268528;0.034035600001516286;0.033911679999437186 +hash_table;отсортированный;вставка;0.03484389999357518;0.03386820000014268;0.033041399998182897;0.03465739999955986;0.035284899997350294;0.03433915999776218 +hash_table;отсортированный;поиск;0.0005273999995552003;0.00044519999937620014;0.00037960000190651044;0.000374099996406585;0.0003724999987753108;0.00041975999920396134 +hash_table;отсортированный;удаление;0.00017210000078193843;0.00017210000078193843;0.00017300000035902485;0.00017389999993611127;0.0001722999950288795;0.00017267999937757849 +bst;отсортированный;вставка;17.43330440000136;17.245424400003685;17.230704699999478;17.4216249999954;17.25258659999963;17.31672901999991 +bst;отсортированный;поиск;0.15691709999373415;0.15601930000411812;0.15765989999636076;0.15630209999653744;0.1590829000051599;0.15719625999918208 +bst;отсортированный;удаление;0.08944690000498667;0.086433999997098;0.08745249999628868;0.08608390000154031;0.09040470000036294;0.08796440000005532 diff --git a/nikolaevda/docs/data/graphs.png b/nikolaevda/docs/data/graphs.png new file mode 100644 index 0000000..34a065f Binary files /dev/null and b/nikolaevda/docs/data/graphs.png differ diff --git a/nikolaevda/docs/report_laba1.ipynb b/nikolaevda/docs/report_laba1.ipynb new file mode 100644 index 0000000..17be36c --- /dev/null +++ b/nikolaevda/docs/report_laba1.ipynb @@ -0,0 +1,245 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "7cbca316", + "metadata": {}, + "outputs": [], + "source": [ + "{\n", + " \"cells\": [\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"start\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"# Отчёт по лабораторной работе\\n\",\n", + " \"## Тема: Сравнение производительности структур данных для телефонного справочника\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"goal\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 1. Цель работы\\n\",\n", + " \"\\n\",\n", + " \"Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций (вставка, поиск, удаление).\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"conditions\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 2. Условия эксперимента\\n\",\n", + " \"\\n\",\n", + " \"| Параметр | Значение |\\n\",\n", + " \"|----------|----------|\\n\",\n", + " \"| Общее число записей | 10 000 |\\n\",\n", + " \"| Каждый замер повторялся | 5 раз |\\n\",\n", + " \"| Количество существующих записей для поиска | 100 |\\n\",\n", + " \"| Количество несуществующих записей для поиска | 10 |\\n\",\n", + " \"| Количество элементов для удаления | 50 |\\n\",\n", + " \"| Размер хеш-таблицы | 2003 (простое число) |\\n\",\n", + " \"| Режимы тестирования | Случайный / Отсортированный |\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"graphs\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 3. Практические графики\\n\",\n", + " \"\\n\",\n", + " \"### Информация о тестировании\\n\",\n", + " \"- Общее число записей: 10 000\\n\",\n", + " \"- Каждый замер повторялся: 5 раз\\n\",\n", + " \"- Количество существующих записей для случайного поиска: 100\\n\",\n", + " \"- Количество несуществующих записей для поиска: 10\\n\",\n", + " \"- Количество элементов для удаления: 50\\n\",\n", + " \"\\n\",\n", + " \"![График вставки](graphs.png)\\n\",\n", + " \"\\n\",\n", + " \"**Рис. 1 – Тестирование вставки (логарифмическая шкала)**\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_bst\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 4. Анализ результатов\\n\",\n", + " \"\\n\",\n", + " \"### Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных)?\\n\",\n", + " \"\\n\",\n", + " \"По определению, при вставке отсортированных данных, структура бинарного дерева поиска вырождается в связный список.\\n\",\n", + " \"\\n\",\n", + " \"**Результаты тестирования:**\\n\",\n", + " \"- На случайных данных: время вставки ~0.037 секунд\\n\",\n", + " \"- На отсортированных данных: время вставки ~18.34 секунд (деградация в ~470 раз!)\\n\",\n", + " \"\\n\",\n", + " \"Заметим, что при случайных данных скорость вставки в бинарное дерево почти лишь немного уступает по скорости хеш-таблице. При отсортированных данных дерево фактически превращается в связный список, и из-за рекурсивной реализации вставки бинарное дерево становится даже медленнее связного списка.\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_hash\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Почему хеш-таблица почти не чувствительна к порядку?\\n\",\n", + " \"\\n\",\n", + " \"Хеш-таблица не чувствительна к порядку данных, так как:\\n\",\n", + " \"1. Использует для распределения элементов хеш-значения данных (сложность операции одинакова для любых однотипных данных)\\n\",\n", + " \"2. Хеш-функция равномерно распределяет ключи по корзинам независимо от их порядка\\n\",\n", + " \"3. Вставка в конкретную корзину не зависит от соседних элементов\\n\",\n", + " \"\\n\",\n", + " \"**Экспериментальное подтверждение:**\\n\",\n", + " \"- Случайный порядок: вставка = 0.0369 сек, поиск = 0.000355 сек\\n\",\n", + " \"- Отсортированный порядок: вставка = 0.0356 сек, поиск = 0.000380 сек\\n\",\n", + " \"\\n\",\n", + " \"Разница незначительна и находится в пределах погрешности измерений.\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_list\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Почему связный список всегда медленен при поиске?\\n\",\n", + " \"\\n\",\n", + " \"Операция поиска в связном списке имеет линейную сложность **O(n)** независимо от порядка данных, так как:\\n\",\n", + " \"- Нет индексов для прямого доступа\\n\",\n", + " \"- Нет сортировки, позволяющей применять бинарный поиск\\n\",\n", + " \"- Приходится последовательно перебирать все элементы до найденного\\n\",\n", + " \"\\n\",\n", + " \"| Структура | Сложность поиска | Время поиска (случайный) |\\n\",\n", + " \"|-----------|-----------------|--------------------------|\\n\",\n", + " \"| Связный список | O(n) | 0.0427 сек |\\n\",\n", + " \"| Хеш-таблица | O(1) средняя | 0.000355 сек |\\n\",\n", + " \"| BST (случайный) | O(log n) | 0.000527 сек |\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_delete\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Как удаление работает в каждой структуре?\\n\",\n", + " \"\\n\",\n", + " \"#### Связный список\\n\",\n", + " \"Находим элемент перед удаляемым элементом и заменяем его поле `next` на `next.next`:\\n\",\n", + " \"```python\\n\",\n", + " \"current = head\\n\",\n", + " \"while current['next'] is not None:\\n\",\n", + " \" if current['next']['name'] == name:\\n\",\n", + " \" current['next'] = current['next']['next']\\n\",\n", + " \" return head\\n\",\n", + " \" current = current['next']\\n\",\n", + " \"```\\n\",\n", + " \"\\n\",\n", + " \"#### Двоичное дерево поиска\\n\",\n", + " \"После того, как мы нашли узел, который необходимо удалить, возможны три случая:\\n\",\n", + " \"\\n\",\n", + " \"**Случай 1:** У удаляемого узла нет детей → просто удаляем узел.\\n\",\n", + " \"\\n\",\n", + " \"**Случай 2:** У удаляемого узла есть только один ребёнок → ребёнок занимает место удалённого узла.\\n\",\n", + " \"\\n\",\n", + " \"**Случай 3:** У удаляемого узла есть оба ребёнка → находим минимальный элемент в правом поддереве (самый левый узел) и заменяем им удаляемый узел.\\n\",\n", + " \"\\n\",\n", + " \"#### Хеш-таблица\\n\",\n", + " \"1. Вычисляем хеш-индекс: `index = hash_func(name, len(buckets))`\\n\",\n", + " \"2. Находим нужную корзину: `buckets[index]`\\n\",\n", + " \"3. Удаляем элемент из связного списка в этой корзине\\n\",\n", + " \"\\n\",\n", + " \"**Сравнение времени удаления:**\\n\",\n", + " \"\\n\",\n", + " \"| Структура | Время удаления (случайный) | Сложность |\\n\",\n", + " \"|-----------|---------------------------|-----------|\\n\",\n", + " \"| Связный список | 0.0341 сек | O(n) |\\n\",\n", + " \"| Хеш-таблица | 0.00018 сек | O(1) средняя |\\n\",\n", + " \"| BST | 0.0793 сек | O(log n) средняя |\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"conclusion\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 5. Вывод\\n\",\n", + " \"\\n\",\n", + " \"Мы реализовали и протестировали три различные структуры хранения данных: связный список, хеш-таблицу и двоичное дерево поиска. Сравнили скорость операций вставки, удаления и поиска для каждой структуры.\\n\",\n", + " \"\\n\",\n", + " \"### Итоговая таблица производительности (случайный порядок):\\n\",\n", + " \"\\n\",\n", + " \"| Структура | Вставка (сек) | Поиск (сек) | Удаление (сек) |\\n\",\n", + " \"|-----------|---------------|-------------|----------------|\\n\",\n", + " \"| Связный список | 4.6031 | 0.0427 | 0.0341 |\\n\",\n", + " \"| Хеш-таблица | **0.0369** | **0.00036** | **0.00018** |\\n\",\n", + " \"| BST | 0.0369 | 0.00053 | 0.0793 |\\n\",\n", + " \"\\n\",\n", + " \"### Рекомендации по выбору структуры данных:\\n\",\n", + " \"\\n\",\n", + " \"1. **Хеш-таблица** – лучший выбор для телефонного справочника:\\n\",\n", + " \" - Не важен порядок хранения и извлечения данных\\n\",\n", + " \" - Требуется максимальная скорость поиска и вставки\\n\",\n", + " \" - Результат: **победитель по всем параметрам**\\n\",\n", + " \"\\n\",\n", + " \"2. **Двоичное дерево поиска** – выбираем, если:\\n\",\n", + " \" - Нужно хранить данные с возможностью быстрого отсортированного обхода\\n\",\n", + " \" - Данные поступают в случайном порядке (иначе будет деградация)\\n\",\n", + " \" - Можно использовать сбалансированную версию (AVL, красно-чёрное)\\n\",\n", + " \"\\n\",\n", + " \"3. **Связный список** – выбираем, если:\\n\",\n", + " \" - Нужно хранить данные в порядке поступления (очередь, стек)\\n\",\n", + " \" - Объём данных очень маленький (< 100 записей)\\n\",\n", + " \" - Простота реализации важнее производительности\\n\",\n", + " \"\\n\",\n", + " \"---\\n\",\n", + " \"\\n\",\n", + " \"**Заключение:** Для реализации телефонного справочника оптимальнее всего использовать **хеш-таблицу**, так как она обеспечивает наилучшую производительность для всех операций и не чувствительна к порядку входных данных.\"\n", + " ]\n", + " }\n", + " ],\n", + " \"metadata\": {\n", + " \"kernelspec\": {\n", + " \"display_name\": \"Python 3\",\n", + " \"language\": \"python\",\n", + " \"name\": \"python3\"\n", + " },\n", + " \"language_info\": {\n", + " \"name\": \"python\",\n", + " \"version\": \"3.14.0\"\n", + " }\n", + " },\n", + " \"nbformat\": 4,\n", + " \"nbformat_minor\": 5\n", + "}" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nikolaevda/docs/report_laba2.ipynb b/nikolaevda/docs/report_laba2.ipynb new file mode 100644 index 0000000..a8f64df --- /dev/null +++ b/nikolaevda/docs/report_laba2.ipynb @@ -0,0 +1,231 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "99cf9991", + "metadata": {}, + "outputs": [], + "source": [ + "{\n", + " \"cells\": [\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"# Лабораторная работа: Поиск выхода из лабиринта\\n\",\n", + " \"\\n\",\n", + " \"## 1. Постановка задачи\\n\",\n", + " \"\\n\",\n", + " \"Разработать программу для загрузки лабиринта из текстового файла, поиска пути от стартовой клетки до выхода с возможностью выбора алгоритма поиска, визуализации процесса и экспериментального сравнения эффективности алгоритмов.\\n\",\n", + " \"\\n\",\n", + " \"### Основные требования\\n\",\n", + " \"\\n\",\n", + " \"- Реализовать модель лабиринта (классы `Cell`, `Maze`)\\n\",\n", + " \"- Реализовать загрузку лабиринта из файла с символами `#` (стена), `S` (старт), `E` (выход)\\n\",\n", + " \"- Реализовать четыре алгоритма поиска пути: BFS, DFS, A*, Дейкстра\\n\",\n", + " \"- Реализовать класс-оркестратор `MazeSolver` с возможностью смены стратегии\\n\",\n", + " \"- Собрать статистику: время выполнения, количество посещённых клеток, длина пути\\n\",\n", + " \"- Провести эксперименты на лабиринтах разной сложности\\n\",\n", + " \"- Реализовать интерактивный режим с пошаговым управлением и отменой ходов\\n\",\n", + " \"\\n\",\n", + " \"### Использованные паттерны проектирования GoF\\n\",\n", + " \"\\n\",\n", + " \"| Паттерн | Где используется | Преимущества |\\n\",\n", + " \"|---------|-----------------|---------------|\\n\",\n", + " \"| **Builder** | `MazeBuilder`, `TextFileMazeBuilder` | Скрывает детали парсинга, легко добавлять новые форматы |\\n\",\n", + " \"| **Strategy** | `PathFindingStrategy`, BFS, DFS, A*, Дейкстра | Динамическая смена алгоритма |\\n\",\n", + " \"| **Observer** | `Observer`, `ConsoleDisplay` | Отделяет отображение от логики |\\n\",\n", + " \"| **Command** | `Command`, `MoveCommand`, `CommandInvoker` | Undo/Redo, история ходов |\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 2. Архитектура приложения\\n\",\n", + " \"\\n\",\n", + " \"Основные компоненты:\\n\",\n", + " \"\\n\",\n", + " \"- **Модель** – `Cell`, `Maze` (хранение сетки, проверка стен, получение соседей)\\n\",\n", + " \"- **Загрузка** – `MazeBuilder`, `TextFileMazeBuilder` (парсинг `.txt`‑файлов)\\n\",\n", + " \"- **Алгоритмы** – `BFSStrategy`, `DFSStrategy`, `AStarStrategy`, `DijkstraStrategy`\\n\",\n", + " \"- **Оркестрация** – `MazeSolver` (управление стратегией, сбор статистики)\\n\",\n", + " \"- **Визуализация** – `ConsoleDisplay` (отрисовка лабиринта, игрока, пути)\\n\",\n", + " \"- **Интерактив** – `Player`, `MoveCommand`, `CommandInvoker` (перемещение, отмена/повтор)\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 3. Реализация алгоритмов поиска пути\\n\",\n", + " \"\\n\",\n", + " \"| Алгоритм | Структура данных | Гарантия кратчайшего пути | Особенности |\\n\",\n", + " \"|----------|-----------------|---------------------------|-------------|\\n\",\n", + " \"| **BFS** | Очередь (`deque`) | Да | Обходит по слоям, гарантирует минимум шагов |\\n\",\n", + " \"| **DFS** | Стек | Нет | Углубляется до конца, экономичен по памяти |\\n\",\n", + " \"| **A*** | Приоритетная очередь (`heapq`) + эвристика | Да | Использует манхэттенское расстояние |\\n\",\n", + " \"| **Дейкстра** | Приоритетная очередь (`heapq`) | Да | Частный случай A* с эвристикой 0 |\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 4. Экспериментальная часть\\n\",\n", + " \"\\n\",\n", + " \"### Тестовые лабиринты\\n\",\n", + " \"\\n\",\n", + " \"| Имя | Размер | Описание |\\n\",\n", + " \"|-----|--------|----------|\\n\",\n", + " \"| tiny_simple | 10×10 | Маленький лабиринт с прямым путём |\\n\",\n", + " \"| small_empty | 20×20 | Пустой лабиринт без стен |\\n\",\n", + " \"| medium_dfs | 30×30 | Лабиринт среднего размера с тупиками |\\n\",\n", + " \"| medium_complex | 40×40 | Сложный запутанный лабиринт |\\n\",\n", + " \"| large_dfs | 50×50 | Большой лабиринт |\\n\",\n", + " \"| very_large_dfs | 100×100 | Очень большой лабиринт |\\n\",\n", + " \"| no_exit | 20×20 | Лабиринт без выхода |\\n\",\n", + " \"\\n\",\n", + " \"Каждый алгоритм запускался **5 раз** на каждом лабиринте, результаты усреднены.\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Результаты замеров\\n\",\n", + " \"\\n\",\n", + " \"| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |\\n\",\n", + " \"|----------|----------|------------|-----------------|------------|\\n\",\n", + " \"| tiny_simple (10x10) | BFS | 0.11 | 29 | 19 |\\n\",\n", + " \"| tiny_simple (10x10) | DFS | 0.07 | 29 | 19 |\\n\",\n", + " \"| tiny_simple (10x10) | A* | 0.17 | 29 | 19 |\\n\",\n", + " \"| tiny_simple (10x10) | Дейкстра | 0.15 | 29 | 19 |\\n\",\n", + " \"| small_empty (20x20) | BFS | 1.35 | 400 | 39 |\\n\",\n", + " \"| small_empty (20x20) | DFS | 1.02 | 400 | 191 |\\n\",\n", + " \"| small_empty (20x20) | A* | 2.61 | 400 | 39 |\\n\",\n", + " \"| small_empty (20x20) | Дейкстра | 1.02 | 400 | 39 |\\n\",\n", + " \"| medium_dfs (30x30) | BFS | 3.30 | 110 | 77 |\\n\",\n", + " \"| medium_dfs (30x30) | DFS | 2.58 | 80 | 77 |\\n\",\n", + " \"| medium_dfs (30x30) | A* | 0.51 | 88 | 77 |\\n\",\n", + " \"| medium_dfs (30x30) | Дейкстра | 0.55 | 110 | 77 |\\n\",\n", + " \"| no_exit (20x20) | BFS | 0.14 | 193 | 0 |\\n\",\n", + " \"| no_exit (20x20) | DFS | 0.07 | 52 | 0 |\\n\",\n", + " \"| no_exit (20x20) | A* | 0.16 | 162 | 0 |\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Графики\\n\",\n", + " \"\\n\",\n", + " \"![Сравнение алгоритмов](algorithm_comparison.png)\\n\",\n", + " \"\\n\",\n", + " \"На графике представлено сравнение алгоритмов по трём метрикам: время выполнения, количество посещённых клеток и длина найденного пути.\\n\",\n", + " \"\\n\",\n", + " \"![Детальный анализ](maze_detailed_analysis.png)\\n\",\n", + " \"\\n\",\n", + " \"Детальные графики по каждому лабиринту.\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 5. Анализ результатов\\n\",\n", + " \"\\n\",\n", + " \"### Сравнение характеристик\\n\",\n", + " \"\\n\",\n", + " \"**BFS**\\n\",\n", + " \"- Гарантирует кратчайший путь (длина пути совпадает с A*)\\n\",\n", + " \"- Посещает много клеток (400 в пустом лабиринте)\\n\",\n", + " \"- Время работы стабильно, предсказуемо\\n\",\n", + " \"\\n\",\n", + " \"**DFS**\\n\",\n", + " \"- Самый быстрый на большинстве лабиринтов (0.07 мс на маленьком)\\n\",\n", + " \"- НЕ гарантирует кратчайший путь (в пустом лабиринте путь 191 вместо 39)\\n\",\n", + " \"- Посещает меньше клеток, чем BFS (80 против 110 на 30x30)\\n\",\n", + " \"- Отлично работает в лабиринтах без выхода (52 посещённые клетки)\\n\",\n", + " \"\\n\",\n", + " \"**A***\\n\",\n", + " \"- Всегда находит оптимальный путь (как BFS)\\n\",\n", + " \"- Посещает меньше клеток, чем BFS (88 против 110 на 30x30)\\n\",\n", + " \"- На 30x30 оказался самым быстрым (0.51 мс)\\n\",\n", + " \"- На пустом лабиринте медленнее из-за накладных расходов на эвристику\\n\",\n", + " \"\\n\",\n", + " \"**Дейкстра**\\n\",\n", + " \"- На невзвешенных графах даёт те же результаты, что и BFS\\n\",\n", + " \"- Медленнее A* на сложных лабиринтах\\n\",\n", + " \"\\n\",\n", + " \"### Ключевые выводы\\n\",\n", + " \"\\n\",\n", + " \"1. **A* показывает лучший баланс** между скоростью и оптимальностью.\\n\",\n", + " \"2. **DFS – самый быстрый**, когда не важна длина пути.\\n\",\n", + " \"3. **BFS** остаётся простым и предсказуемым решением.\\n\",\n", + " \"4. **На пустых лабиринтах** DFS находит очень длинный путь.\\n\",\n", + " \"5. **В лабиринтах без выхода** DFS быстрее всех обнаруживает отсутствие пути.\\n\",\n", + " \"\\n\",\n", + " \"### Рекомендации по выбору алгоритма\\n\",\n", + " \"\\n\",\n", + " \"| Сценарий | Рекомендуемый алгоритм | Обоснование |\\n\",\n", + " \"|----------|------------------------|-------------|\\n\",\n", + " \"| Нужен кратчайший путь + скорость | **A*** | Лучшие результаты на сложных лабиринтах |\\n\",\n", + " \"| Важна только скорость | **DFS** | Самый быстрый |\\n\",\n", + " \"| Простота реализации | **BFS** | Самый понятный алгоритм |\\n\",\n", + " \"| Проверка существования пути | **DFS** | Быстро находит или упирается в тупик |\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 6. Заключение\\n\",\n", + " \"\\n\",\n", + " \"В ходе лабораторной работы была разработана программа для поиска выхода из лабиринта с применением паттернов проектирования GoF.\\n\",\n", + " \"\\n\",\n", + " \"### Преимущества использованных паттернов\\n\",\n", + " \"\\n\",\n", + " \"| Паттерн | Что было бы сложно изменить без него |\\n\",\n", + " \"|---------|--------------------------------------|\\n\",\n", + " \"| **Builder** | Добавление нового формата файлов потребовало бы переписывания всей логики загрузки |\\n\",\n", + " \"| **Strategy** | Смена алгоритма поиска потребовала бы изменения кода `MazeSolver` |\\n\",\n", + " \"| **Observer** | Добавление нового способа вывода потребовало бы изменения всех классов |\\n\",\n", + " \"| **Command** | Реализация отмены ходов привела бы к дублированию кода |\\n\",\n", + " \"\\n\",\n", + " \"Экспериментальное сравнение алгоритмов показало, что A* является оптимальным выбором для большинства сценариев. DFS остаётся лучшим выбором, когда скорость критичнее оптимальности пути.\\n\",\n", + " \"\\n\",\n", + " \"Программа предоставляет два режима работы:\\n\",\n", + " \"- **Интерактивный режим** – игра с ручным управлением, отменой и повторением ходов\\n\",\n", + " \"- **Экспериментальный режим** – автоматическое тестирование алгоритмов с сохранением результатов в CSV и построением графиков\\n\",\n", + " \"\\n\",\n", + " \"Разработанное решение демонстрирует преимущества объектно-ориентированного подхода и паттернов проектирования при создании гибких, расширяемых и легко поддерживаемых программ.\"\n", + " ]\n", + " }\n", + " ],\n", + " \"metadata\": {\n", + " \"kernelspec\": {\n", + " \"display_name\": \"Python 3\",\n", + " \"language\": \"python\",\n", + " \"name\": \"python3\"\n", + " },\n", + " \"language_info\": {\n", + " \"name\": \"python\",\n", + " \"version\": \"3.10.0\"\n", + " }\n", + " },\n", + " \"nbformat\": 4,\n", + " \"nbformat_minor\": 4\n", + "}" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nikolaevda/task1/Zadanie1.py b/nikolaevda/task1/Zadanie1.py new file mode 100644 index 0000000..4558814 --- /dev/null +++ b/nikolaevda/task1/Zadanie1.py @@ -0,0 +1,649 @@ +import random +import time +import csv +import os +import traceback +import sys + +sys.setrecursionlimit(30000) + +def ll_insert(head, name, phone): + + """ + проходит до конца (или сразу добавляет в конец) + возвращает новую голову (если вставка в начало) или изменяет список по ссылке. + """ + new_node = {'name': name, 'phone': phone, 'next': None} + + # в случае пустого списка, новый узел становится головой + if head is None: + return new_node + + # в противном случае проходим в конец списка + current = head + while current['next'] is not None: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + + # проверяем последний узел + if current['name'] == name: + current['phone'] = phone + else: + current['next'] = new_node + + return head + + + +def ll_find(head, name): + + """ + ищет узел по имени. + возвращает телефон или None + """ + current = head + + while current is not None: + if current['name'] == name: + return current['phone'] + current = current['next'] + + return None + + +def ll_delete(head, name): + + """ + удаляет узел по имени. + возвращает новую голову. + """ + # если список пуст + if head is None: + return None + + # если удаляем голову + if head['name'] == name: + new_head = head['next'] + head['next'] = None + return new_head + + # ищем узел для удаления + current = head + while current['next'] is not None: + if current['next']['name'] == name: + target = current['next'] + current['next'] = target['next'] + target['next'] = None # разрываем связь у удаляемого узла (иными словами, обнуление ссылки) + return head + current = current['next'] + + return head + +def ll_collect(head, result_list): + """собирает все данные из связного списка в result_list""" + current = head + while current is not None: + result_list.append((current['name'], current['phone'])) + current = current['next'] + + +def ll_list_all(head): + + """ + собирает все записи в список и сортирует + """ + result = [] + current = head + + while current is not None: + result.append((current['name'], current['phone'])) + current = current['next'] + + # ручная сортировка пузырьком + n = len(result) + for i in range(n): + for j in range(0, n - i - 1): + if result[j][0] > result[j + 1][0]: + result[j], result[j + 1] = result[j + 1], result[j] + + return result + + +def hash_table(size): + """создание хеш-таблицы""" + return [None] * size + + +def hash_func(name, buckets_count): + """ + использует умножение на простое число для лучшего распределения + """ + h = 0 + multiplier = 1 + for char in name: + h = (h + ord(char) * multiplier) % buckets_count + multiplier = (multiplier * 31) % buckets_count + return h + + +def ht_insert(buckets, name, phone): + """добавить или обновить запись""" + if buckets is None: + return + + index = hash_func(name, len(buckets)) + buckets[index] = ll_insert(buckets[index], name, phone) + + +def ht_find(buckets, name): + """найти телефон по имени""" + idx = hash_func(name, len(buckets)) + return ll_find(buckets[idx], name) + + +def ht_delete(buckets, name): + """ + удалить запись + """ + idx = hash_func(name, len(buckets)) + buckets[idx] = ll_delete(buckets[idx], name) + + +def bubble_sort(records): + """пузырьковая сортировка""" + n = len(records) + for i in range(n - 1): + swapped = False + for j in range(n - 1 - i): + if records[j][0] > records[j + 1][0]: + records[j], records[j + 1] = records[j + 1], records[j] + swapped = True + if not swapped: + break + return records + + +def ht_list_all(buckets): + """ + собрание всех записей и сортировка + """ + # Собираем все записи + full_data = [] + for head in buckets: + ll_collect(head, full_data) + + # Сортируем пузырьком + bubble_sort(full_data) + return full_data + +#Hash_table1 = hash_table(3) + +#ht_insert(Hash_table1, 'Alena', '010') +#ht_insert(Hash_table1, 'Helena', '111') +#ht_insert(Hash_table1, 'Gena', '222') + + +#print(ht_list_all(Hash_table1)) + +def bst_insert(root, name, phone): + """ + рекурсивная вставка или обновление записи + возвращает новый корень (если корень меняется) + """ + # если дерево пусто, создаём новый узел + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + + # вставка в левое поддерево + if name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + + # вставка в правое поддерево + elif name > root['name']: + root['right'] = bst_insert(root['right'], name, phone) + + # имя уже существует — обновляем телефон + else: + root['phone'] = phone + + return root + + +def bst_find(root, name): + """ + рекурсивный поиск телефона по имени + возвращает phone или None + """ + # не нашли + if root is None: + return None + + # нашли + if root['name'] == name: + return root['phone'] + + # ищем в левом поддереве + elif name < root['name']: + return bst_find(root['left'], name) + + # ищем в правом поддереве + else: + return bst_find(root['right'], name) + + +def bst_find_min(node): + """вспомогательная функция: поиск узла с минимальным ключом""" + current = node + while current['left'] is not None: + current = current['left'] + return current + + +def bst_delete(root, name): + """ + рекурсивное удаление записи по имени + возвращает новый корень + """ + # дерево пусто + if root is None: + return None + + # спускаемся в левое поддерево + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + + # спускаемся в правое поддерево + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + + # нашли удаляемый узел + else: + + if root['left'] is None and root['right'] is None: + return None + + + elif root['left'] is None: + return root['right'] + elif root['right'] is None: + return root['left'] + + + # находим минимальный элемент в правом поддереве + successor = bst_find_min(root['right']) + + # копируем данные из преемника в текущий узел + root['name'] = successor['name'] + root['phone'] = successor['phone'] + + # удаляем преемника + root['right'] = bst_delete(root['right'], successor['name']) + + return root + + +def bst_list_all(root, result=None): + """ + центрированный (in-order) обход дерева + рекурсивно собирает записи в отсортированном порядке + """ + if result is None: + result = [] + + if root is not None: + # сначала обходим левое поддерево (все меньшие ключи) + bst_list_all(root['left'], result) + + # затем текущий узел + result.append((root['name'], root['phone'])) + + # затем правое поддерево (все большие ключи) + bst_list_all(root['right'], result) + + return result + +def generate_records(count=10000): + """ + генерация тестовых данных + 70% уникальных имён, 30% повторяющихся (для коллизий) + """ + records = [] + base_names = ["Алексей", "Борис", "Владимир", "Дмитрий", "Елена", + "Иван", "Мария", "Николай", "Ольга", "Павел"] + + for i in range(count): + if random.random() < 0.7: + name = f"User_{i:05d}" + else: + name = random.choice(base_names) + f"_{random.randint(1, 100)}" + + phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}" + records.append((name, phone)) + + shuffled = records.copy() + random.shuffle(shuffled) + sorted_records = sorted(records, key=lambda x: x[0]) + + return shuffled, sorted_records + + +def measure_insertion(structure_name, records): + """ + замер времени вставки + возвращает список замеров и заполненную структуру + """ + times = [] + filled_structure = None + + for run in range(5): + if structure_name == "linked_list": + structure = None + elif structure_name == "hash_table": + structure = hash_table(2003) + elif structure_name == "bst": + structure = None + + start = time.perf_counter() + + for name, phone in records: + if structure_name == "linked_list": + structure = ll_insert(structure, name, phone) + elif structure_name == "hash_table": + ht_insert(structure, name, phone) + elif structure_name == "bst": + structure = bst_insert(structure, name, phone) + + end = time.perf_counter() + times.append(end - start) + + if run == 4: # Сохраняем после последнего замера + filled_structure = structure + + return times, filled_structure + + +def measure_search(structure_name, structure, search_names): + """ + замер времени поиска + возвращает список замеров + """ + times = [] + + for run in range(5): + start = time.perf_counter() + + for name in search_names: + if structure_name == "linked_list": + ll_find(structure, name) + elif structure_name == "hash_table": + ht_find(structure, name) + elif structure_name == "bst": + bst_find(structure, name) + + end = time.perf_counter() + times.append(end - start) + + return times + + +def measure_deletion(structure_name, original_structure, delete_names): + """ + замер времени удаления + возвращает список замеров + """ + times = [] + + for run in range(5): + # создаём копию структуры + if structure_name == "linked_list": + all_records = ll_list_all(original_structure) + test_structure = None + for name, phone in all_records: + test_structure = ll_insert(test_structure, name, phone) + + elif structure_name == "hash_table": + all_records = ht_list_all(original_structure) + test_structure = hash_table(2003) + for name, phone in all_records: + ht_insert(test_structure, name, phone) + + elif structure_name == "bst": + all_records = bst_list_all(original_structure) + test_structure = None + for name, phone in all_records: + test_structure = bst_insert(test_structure, name, phone) + + start = time.perf_counter() + + for name in delete_names: + if structure_name == "linked_list": + test_structure = ll_delete(test_structure, name) + elif structure_name == "hash_table": + ht_delete(test_structure, name) + elif structure_name == "bst": + test_structure = bst_delete(test_structure, name) + + end = time.perf_counter() + times.append(end - start) + + return times + +print(f"Текущая рабочая директория: {os.getcwd()}") +print(f"Путь к файлу: {os.path.abspath(__file__)}") + +def run_experiment(): + """ + запуск всех экспериментов и сохранение результатов + """ + + current_dir = os.path.dirname(__file__) + docs_dir = os.path.dirname(current_dir) + csv_file = os.path.join(docs_dir, "experiment_results.csv") + + + + os.makedirs(docs_dir, exist_ok=True) + + print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ") + print("Телефонный справочник - 10000 записей") + print(f"\nРезультаты будут сохранены в: {csv_file}") + + # генерация данных + print("\n1. Генерация тестовых данных...") + shuffled_records, sorted_records = generate_records(10000) + print(f"Сгенерировано 10000 записей") + print(f"Уникальных имён: {len(set([r[0] for r in shuffled_records]))}") + + # подготовка имён для поиска и удаления + random.seed(42) + existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)] + nonexisting_names = [f"NotExist_{i}" for i in range(10)] + search_names = existing_names + nonexisting_names + delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)] + + results = [["Структура", "Режим", "Операция", + "Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)", + "Среднее(с)"]] + + # тестирование для каждого режима + for mode_name, records in [("случайный", shuffled_records), + ("отсортированный", sorted_records)]: + + print(f"\n2. Тестирование режима: {mode_name}") + + + for struct_name in ["linked_list", "hash_table", "bst"]: + print(f"\n {struct_name.upper()}:") + + # вставка + print("Вставка 10000 записей...") + insert_times, filled_struct = measure_insertion(struct_name, records) + avg_insert = sum(insert_times) / 5 + print(f"Время: {avg_insert:.4f} сек (среднее)") + + # поиск + print("Поиск 110 записей (100 существующих + 10 которых нет)...") + search_times = measure_search(struct_name, filled_struct, search_names) + avg_search = sum(search_times) / 5 + print(f"Время: {avg_search:.4f} сек (среднее)") + + # удаление + print("Удаление 50 случайных записей...") + delete_times = measure_deletion(struct_name, filled_struct, delete_names) + avg_delete = sum(delete_times) / 5 + print(f"Время: {avg_delete:.4f} сек (среднее)") + + # сохраняем результаты + results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert]) + results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search]) + results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete]) + + # сохранение CSV + print("\n3. Сохранение результатов...") + try: + with open(csv_file, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f, delimiter=';') + writer.writerows(results) + print(f"Результаты сохранены в: {csv_file}") + except Exception as e: + print(f"Ошибка сохранения: {e}") + + # вывод табл. + print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ") + print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}") + + for row in results[1:]: + struct, mode, op, t1, t2, t3, t4, t5, avg = row + print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}") + + return results + +def create_report_table(results): + """Создание сводной таблицы""" + + print("СВОДНАЯ ТАБЛИЦА (среднее время в секундах)") + + print(f"{'Структура':<12} {'Режим':<12} {'Вставка':<12} {'Поиск':<12} {'Удаление':<12}") + + summary = {} + for row in results[1:]: + struct, mode, op, _, _, _, _, _, avg = row + key = (struct, mode) + if key not in summary: + summary[key] = {} + summary[key][op] = avg + + names = {'linked_list': 'LinkedList', 'hash_table': 'HashTable', 'bst': 'BST'} + for (struct, mode), ops in summary.items(): + print(f"{names[struct]:<12} {mode:<12} {ops.get('вставка', 0):<12.6f} {ops.get('поиск', 0):<12.6f} {ops.get('удаление', 0):<12.6f}") + + +def print_analysis(): + """вывод краткого анализа""" + + print("АНАЛИЗ РЕЗУЛЬТАТОВ") + + print(""" +1. Влияние порядка данных на BST: + - На случайных данных: быстро O(log n) + - На отсортированных: деградация до O(n) (дерево вырождается в список) + +2. Хеш-таблица не чувствительна к порядку: + - Хеш-функция случайно распределяет данные по bucket'ам + - Порядок вставки не влияет на время операций + +3. Связный список всегда медленен при поиске: + - Поиск требует последовательного прохода O(n) + - Нет индексов или сортировки для ускорения + +4. Сравнение удаления: + - Связный список: O(n) — нужен поиск элемента + - Хеш-таблица: O(1) — прямой доступ по индексу + - BST: O(log n) в среднем, O(n) на отсортированных + +5. Рекомендация для реальных задач: + - Хеш-таблица: частый поиск, словари, кэши + - BST (сбалансированный): нужны отсортированные данные + - Связный список: маленькие объёмы, очереди/стеки + - Для телефонного справочника ЛУЧШЕ: ХЕШ-ТАБЛИЦА +""") + +def create_graphs(results): + """Построение столбчатых диаграмм""" + import matplotlib.pyplot as plt + import numpy as np + + data = {} + for row in results[1:]: + struct = row[0] + mode = row[1] + op = row[2] + avg = row[8] + + if struct not in data: + data[struct] = {} + if mode not in data[struct]: + data[struct][mode] = {} + data[struct][mode][op] = avg + + # Настройки + struct_names = {'linked_list': 'LinkedList', 'hash_table': 'HashTable', 'bst': 'BST'} + colors = {'linked_list': '#3498db', 'hash_table': '#2ecc71', 'bst': '#e74c3c'} + modes = ['случайный', 'отсортированный'] + operations = ['вставка', 'поиск', 'удаление'] + op_titles = ['Вставка (10000 записей)', 'Поиск (110 запросов)', 'Удаление (50 записей)'] + + fig, axes = plt.subplots(1, 3, figsize=(14, 5)) + fig.suptitle('Сравнение производительности структур данных', fontsize=14, fontweight='bold') + + for idx, (op, title) in enumerate(zip(operations, op_titles)): + ax = axes[idx] + x = np.arange(len(modes)) + width = 0.25 + multiplier = 0 + + for struct in ['linked_list', 'hash_table', 'bst']: + values = [data[struct][mode][op] for mode in modes] + bars = ax.bar(x + multiplier * width, values, width, + label=struct_names[struct], color=colors[struct]) + + for bar, val in zip(bars, values): + if val < 0.001: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), + f'{val:.6f}', ha='center', va='bottom', fontsize=7) + elif val < 0.01: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), + f'{val:.5f}', ha='center', va='bottom', fontsize=7) + else: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), + f'{val:.3f}', ha='center', va='bottom', fontsize=8) + multiplier += 1 + + ax.set_title(title) + ax.set_ylabel('Время (сек)') + ax.set_yscale('log') + ax.set_ylim(1e-5, 10) + ax.set_xticks(x + width) + ax.set_xticklabels(['Случайный', 'Отсортированный']) + ax.legend() + ax.grid(True, alpha=0.3, axis='y') + + plt.tight_layout() + + current_dir = os.path.dirname(__file__) + docs_dir = os.path.dirname(current_dir) + path = os.path.join(docs_dir, 'graphs.png') + plt.savefig(path, dpi=150) + plt.close() + print(f"\nГрафики сохранены: {path}") + return path + +if __name__ == "__main__": + results = run_experiment() + create_report_table(results) + create_graphs(results) + print_analysis() + + print("ЭКСПЕРИМЕНТ ВЫПОЛНЕН ПОЛНОСТЬЮ!") diff --git a/nikolaevda/task2/Zadanie2.py b/nikolaevda/task2/Zadanie2.py new file mode 100644 index 0000000..e3915b2 --- /dev/null +++ b/nikolaevda/task2/Zadanie2.py @@ -0,0 +1,1318 @@ +import sys +from collections import deque +import heapq +import time +import os + +class Cell: + """клетка лабиринта""" + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + + def __repr__(self): + return f"Cell({self.x}, {self.y})" + + +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 + + def get_cell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + def get_neighbors(self, cell): + neighbors = [] + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + for dx, dy in directions: + neighbor = self.get_cell(cell.x + dx, cell.y + dy) + if neighbor and neighbor.is_passable(): + neighbors.append(neighbor) + return neighbors + + def set_start(self, x, y): + cell = self.get_cell(x, y) + if cell: + cell.is_start = True + self.start = cell + + def set_exit(self, x, y): + cell = self.get_cell(x, y) + if cell: + cell.is_exit = True + self.exit = cell + + + +class MazeBuilder: + """интерфейс строителя лабиринта""" + + def buildFromFile(self, filename): + raise NotImplementedError + + +class TextFileMazeBuilder(MazeBuilder): + """загрузка лабиринта из текстового файла""" + + def buildFromFile(self, filename): + 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 + + for i in range(height): + if len(lines[i]) < width: + lines[i] = lines[i] + ' ' * (width - len(lines[i])) + + maze = Maze(width, height) + start_count = 0 + exit_count = 0 + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if ch == '#': + maze.get_cell(x, y).is_wall = True + elif ch == 'S': + maze.set_start(x, y) + start_count += 1 + elif ch == 'E': + maze.set_exit(x, y) + exit_count += 1 + else: + maze.get_cell(x, y).is_wall = False + + if start_count != 1 or exit_count != 1: + raise ValueError(f"Ошибка: S={start_count}, E={exit_count} (нужно по одному)") + + return maze + +class SearchStats: + """статистика поиска""" + def __init__(self, time_ms=0, visited_cells=0, path_length=0): + self.time_ms = time_ms + self.visited_cells = visited_cells + self.path_length = path_length + + def __str__(self): + return f"Время: {self.time_ms:.2f} мс, Посещено: {self.visited_cells}, Длина пути: {self.path_length}" + + +class PathFindingStrategy: + """интерфейс стратегии поиска пути""" + + def findPath(self, maze, start, exit): + raise NotImplementedError + + def get_name(self): + raise NotImplementedError + + +class BFSStrategy(PathFindingStrategy): + """BFS - гарантирует кратчайший путь""" + + def get_name(self): + return "BFS (Поиск в ширину)" + + def findPath(self, maze, start, exit): + from collections import deque + + if not start or not exit: + return [], 0 + + queue = deque([(start, [start])]) + visited = {start} + + while queue: + current, path = queue.popleft() + + if current == exit: + return path, len(visited) + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + queue.append((neighbor, path + [neighbor])) + + return [], len(visited) + + +class DFSStrategy(PathFindingStrategy): + """DFS - быстрый, но не обязательно кратчайший""" + + def get_name(self): + return "DFS (Поиск в глубину)" + + def findPath(self, maze, start, exit): + if not start or not exit: + return [], 0 + + stack = [(start, [start])] + visited = {start} + + while stack: + current, path = stack.pop() + + if current == exit: + return path, len(visited) + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + stack.append((neighbor, path + [neighbor])) + + return [], len(visited) + + +class AStarStrategy(PathFindingStrategy): + """алгоритм A Star - оптимальный и быстрый с эвристикой""" + + def get_name(self): + return "A Star" + + def _heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def findPath(self, maze, start, exit): + if not start or not exit: + return [], 0 + + import heapq + + heap = [] + counter = 0 + start_f = self._heuristic(start, exit) + heapq.heappush(heap, (start_f, counter, start)) + + came_from = {} + g_score = {start: 0} + f_score = {start: start_f} + visited = set() + visited.add(start) + + while heap: + current_f, _, current = heapq.heappop(heap) + + if current == exit: + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + return path, len(visited) + + 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) + f_score[neighbor] = new_f + counter += 1 + heapq.heappush(heap, (new_f, counter, neighbor)) + visited.add(neighbor) + + return [], len(visited) + + +class DijkstraStrategy(PathFindingStrategy): + """алгоритм Дейкстры""" + + def get_name(self): + return "Дейкстра (Dijkstra)" + + def findPath(self, maze, start, exit): + if not start or not exit: + return [], 0 + + import heapq + + heap = [] + counter = 0 + heapq.heappush(heap, (0, counter, start)) + + distances = {start: 0} + came_from = {} + visited = set() + visited.add(start) + + while heap: + current_dist, _, current = heapq.heappop(heap) + + if current == exit: + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + return path, len(visited) + + if current_dist > distances.get(current, float('inf')): + continue + + for neighbor in maze.get_neighbors(current): + new_dist = current_dist + 1 + + if new_dist < distances.get(neighbor, float('inf')): + distances[neighbor] = new_dist + came_from[neighbor] = current + counter += 1 + heapq.heappush(heap, (new_dist, counter, neighbor)) + visited.add(neighbor) + + return [], len(visited) + + + +class MazeSolver: + """решатель лабиринта - оркестратор, использующий стратегию""" + + def __init__(self, maze): + self.maze = maze + self._strategy = None + + def setStrategy(self, strategy): + """динамическая смена стратегии поиска""" + self._strategy = strategy + print(f" Стратегия изменена на: {strategy.get_name()}") + + def solve(self): + """ + решение лабиринта с использованием текущей стратегии. + возвращает время, посещённые клетки, длина пути + """ + if self._strategy is None: + raise ValueError("Стратегия не установлена. Используйте setStrategy()") + + + start_time = time.perf_counter() + + + path, visited = 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=time_ms, + visited_cells=visited, + path_length=len(path) if path else 0 + ) + + return path, stats + + +class Observer: + """интерфейс наблюдателя""" + + def update(self, event, data): + raise NotImplementedError + + +class ConsoleDisplay(Observer): + """консольная визуализация - наблюдатель""" + + def __init__(self): + self._last_path = None + self._last_maze = None + + def update(self, event, data): + if event == "maze_loaded": + self._draw_maze(data) + elif event == "path_found": + self._last_path = data + self._show_path(data) + elif event == "player_moved": + self._draw_maze_with_player(data) + + def _draw_maze(self, maze): + """Отрисовка лабиринта""" + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (maze.width * 2 + 4)) + print("ЛАБИРИНТ") + print("=" * (maze.width * 2 + 4)) + + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.get_cell(x, y) + if cell == maze.start: + line += "S " + elif cell == maze.exit: + line += "E " + elif cell.is_wall: + line += "# " + else: + line += ". " + print(line) + + print("=" * (maze.width * 2 + 4)) + print("S - старт E - выход # - стена . - проход") + + def _draw_maze_with_player(self, game_state): + """отрисовка лабиринта с игроком""" + maze = game_state['maze'] + player = game_state['player'] + + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (maze.width * 2 + 4)) + print("ЛАБИРИНТ (P - игрок)") + print("=" * (maze.width * 2 + 4)) + + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.get_cell(x, y) + if player and cell == player.get_position(): + line += "P " + elif cell == maze.start: + line += "S " + elif cell == maze.exit: + line += "E " + elif cell.is_wall: + line += "# " + else: + line += ". " + print(line) + + print("=" * (maze.width * 2 + 4)) + if player: + pos = player.get_position() + print(f"Игрок: ({pos.x}, {pos.y})") + print("S - старт E - выход # - стена . - проход P - игрок") + + def _show_path(self, path): + """Показ информации о найденном пути""" + if not path: + print("\n Путь не найден!") + return + print(f"\n Путь найден! Длина: {len(path)} клеток") + + + +class Command: + """Интерфейс команды""" + + def execute(self): + raise NotImplementedError + + def undo(self): + raise NotImplementedError + + +class MoveCommand(Command): + """Команда перемещения игрока""" + + def __init__(self, player, direction, maze): + self._player = player + self._dx, self._dy = direction + self._maze = maze + self._executed = False + self._prev_position = None + + def execute(self): + """Выполнение перемещения""" + if self._executed: + return False + + pos = self._player.get_position() + new_x = pos.x + self._dx + new_y = pos.y + self._dy + target = self._maze.get_cell(new_x, new_y) + + if target and target.is_passable(): + self._prev_position = pos + self._player.set_position(target) + self._executed = True + return True + return False + + def undo(self): + """Отмена перемещения""" + if not self._executed or self._prev_position is None: + return False + + self._player.set_position(self._prev_position) + self._executed = False + return True + + def get_name(self): + dir_names = {(-1, 0): "ВЛЕВО", (1, 0): "ВПРАВО", (0, -1): "ВВЕРХ", (0, 1): "ВНИЗ"} + return f"Перемещение {dir_names.get((self._dx, self._dy), 'НЕИЗВЕСТНО')}" + + +class CommandInvoker: + """Инвокер команд (история для undo/redo)""" + + def __init__(self): + self._history = [] + self._redo_stack = [] + + def execute(self, command): + """Выполнение команды с сохранением в истории""" + if command.execute(): + self._history.append(command) + self._redo_stack.clear() + return True + return False + + def undo(self): + """Отмена последней команды""" + if not self._history: + return False + + command = self._history.pop() + if command.undo(): + self._redo_stack.append(command) + return True + return False + + def redo(self): + """Повтор отменённой команды""" + if not self._redo_stack: + return False + + command = self._redo_stack.pop() + if command.execute(): + self._history.append(command) + return True + return False + + def get_history_size(self): + return len(self._history) + + + +class Player: + """Игрок, перемещающийся по лабиринту""" + + def __init__(self, start_cell): + self._position = start_cell + self._start = start_cell + + def get_position(self): + return self._position + + def set_position(self, cell): + self._position = cell + + def reset(self): + self._position = self._start + + def is_at_exit(self, maze): + return self._position == maze.exit + + def get_steps_count(self, invoker): + return invoker.get_history_size() + + + +class GameController: + """контроллер, объединяющий все компоненты""" + + def __init__(self, maze): + self.maze = maze + self.player = Player(maze.start) + self.solver = MazeSolver(maze) + self.invoker = CommandInvoker() + self.view = ConsoleDisplay() + + def run(self): + """запуск интерактивного режима""" + self.view.update("maze_loaded", self.maze) + + print("УПРАВЛЕНИЕ:") + print(" H/J/K/Ll - движение") + print(" U - отменить ход") + print(" R - повторить ход") + print(" B - BFS поиск пути") + print(" D - DFS поиск пути") + print(" A - A* поиск пути") + print(" P - показать путь") + print(" Q - выход") + + path = None + last_strategy_name = "" + + while True: + cmd = input("\nКоманда > ").lower() + + if cmd == 'q': + print("До встречи!") + break + + elif cmd in ['h', 'j', 'k', 'l']: + dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)} + command = MoveCommand(self.player, dir_map[cmd], self.maze) + + if self.invoker.execute(command): + self.view.update("player_moved", { + 'maze': self.maze, + 'player': self.player + }) + + if self.player.is_at_exit(self.maze): + print(f"\n *** ПОБЕДА! ВЫХОД ДОСТИГНУТ за {self.player.get_steps_count(self.invoker)} шагов! ***") + break + else: + print(" Стена! Нельзя пройти.") + + elif cmd == 'u': + if self.invoker.undo(): + self.view.update("player_moved", { + 'maze': self.maze, + 'player': self.player + }) + print(" Отменено") + else: + print(" Нечего отменять") + + elif cmd == 'r': + if self.invoker.redo(): + self.view.update("player_moved", { + 'maze': self.maze, + 'player': self.player + }) + print(" Повторено") + else: + print(" Нечего повторять") + + elif cmd == 'b': + self.solver.setStrategy(BFSStrategy()) + start_time = time.perf_counter() + path, stats = self.solver.solve() + self.view.update("path_found", path) + print(f" BFS: {stats}") + last_strategy_name = "BFS" + + elif cmd == 'd': + self.solver.setStrategy(DFSStrategy()) + path, stats = self.solver.solve() + self.view.update("path_found", path) + print(f" DFS: {stats}") + last_strategy_name = "DFS" + + elif cmd == 'a': + self.solver.setStrategy(AStarStrategy()) + path, stats = self.solver.solve() + self.view.update("path_found", path) + print(f" A*: {stats}") + last_strategy_name = "A*" + + elif cmd == 'p': + if path: + self._show_path_on_maze(path) + else: + print(" Сначала найдите путь (B, D или A)") + + else: + print(" Неизвестная команда") + + def _show_path_on_maze(self, path): + """показать путь на лабиринте""" + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (self.maze.width * 2 + 4)) + print("ЛАБИРИНТ С ПУТЁМ (* - путь)") + print("=" * (self.maze.width * 2 + 4)) + + path_set = set(path) + + for y in range(self.maze.height): + line = "" + for x in range(self.maze.width): + cell = self.maze.get_cell(x, y) + if cell == self.player.get_position(): + line += "P " + elif cell == self.maze.start: + line += "S " + elif cell == self.maze.exit: + line += "E " + elif cell in path_set and cell.is_passable(): + line += "* " + elif cell.is_wall: + line += "# " + else: + line += ". " + print(line) + + print("=" * (self.maze.width * 2 + 4)) + print("S - старт E - выход # - стена . - проход * - путь P - игрок") + input("\nНажмите Enter для продолжения...") + self.view.update("player_moved", { + 'maze': self.maze, + 'player': self.player + }) + +class MazeGenerator: + """генератор тестовых лабиринтов различной сложности""" + + @staticmethod + def create_empty_maze(width, height): + """пустой лабиринт без стен""" + maze = Maze(width, height) + for y in range(height): + for x in range(width): + maze.get_cell(x, y).is_wall = False + maze.set_start(0, 0) + maze.set_exit(width - 1, height - 1) + return maze + + @staticmethod + def create_simple_maze(width, height): + """простой лабиринт с прямым путём""" + maze = Maze(width, height) + + # Заполняем стенами + for y in range(height): + for x in range(width): + maze.get_cell(x, y).is_wall = True + + # Создаём прямой путь + for i in range(min(width, height)): + maze.get_cell(i, i).is_wall = False + if i + 1 < width: + maze.get_cell(i + 1, i).is_wall = False + if i + 1 < height: + maze.get_cell(i, i + 1).is_wall = False + + maze.set_start(0, 0) + maze.set_exit(width - 1, height - 1) + return maze + + @staticmethod + def generate_dfs_maze(width, height): + """генерация запутанного лабиринта алгоритмом DFS""" + maze = Maze(width, height) + + # заполняем стенами + for y in range(height): + for x in range(width): + maze.get_cell(x, y).is_wall = True + + start_x, start_y = 1, 1 + maze.get_cell(start_x, start_y).is_wall = False + + stack = [(start_x, start_y)] + visited = {(start_x, start_y)} + directions = [(0, -2), (0, 2), (-2, 0), (2, 0)] + + while stack: + x, y = stack[-1] + neighbors = [] + + for dx, dy in directions: + nx, ny = x + dx, y + dy + if 0 < nx < width - 1 and 0 < ny < height - 1 and (nx, ny) not in visited: + neighbors.append((nx, ny, dx, dy)) + + if neighbors: + import random + nx, ny, dx, dy = random.choice(neighbors) + maze.get_cell(x + dx // 2, y + dy // 2).is_wall = False + maze.get_cell(nx, ny).is_wall = False + visited.add((nx, ny)) + stack.append((nx, ny)) + else: + stack.pop() + + maze.set_start(start_x, start_y) + + # ищем дальнюю точку для выхода + farthest = (start_x, start_y) + max_dist = 0 + for y in range(height): + for x in range(width): + cell = maze.get_cell(x, y) + if cell and not cell.is_wall: + dist = abs(x - start_x) + abs(y - start_y) + if dist > max_dist: + max_dist = dist + farthest = (x, y) + + # Устанавливаем выход + maze.set_exit(farthest[0], farthest[1]) + + # Дополнительная проверка: если выход всё ещё None - создаём принудительно + if maze.exit is None: + for y in range(height): + for x in range(width): + cell = maze.get_cell(x, y) + if cell and not cell.is_wall and not cell.is_start: + maze.set_exit(x, y) + break + if maze.exit: + break + + return maze + + @staticmethod + def create_no_exit_maze(width, height): + """лабиринт без выхода""" + maze = MazeGenerator.generate_dfs_maze(width, height) + if maze.exit: + maze.exit.is_wall = True + maze.exit.is_exit = False + maze.exit = None + return maze + + @staticmethod + def save_to_file(maze, filename): + """сохранение лабиринта в файл""" + with open(filename, 'w', encoding='utf-8') as f: + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.get_cell(x, y) + if cell.is_start: + line += 'S' + elif cell.is_exit: + line += 'E' + elif cell.is_wall: + line += '#' + else: + line += '.' + f.write(line + '\n') + +class ExperimentRunner: + """запуск экспериментов и сбор статистики""" + + def __init__(self, runs_per_experiment=5): + self.runs_per_experiment = runs_per_experiment + self.results = [] + + def run_experiment(self, maze, strategy, maze_name): + """запуск одного эксперимента""" + times = [] + visited = [] + path_lengths = [] + + for _ in range(self.runs_per_experiment): + start_time = time.perf_counter() + path, visited_count = strategy.findPath(maze, maze.start, maze.exit) + end_time = time.perf_counter() + + times.append((end_time - start_time) * 1000) + visited.append(visited_count) + path_lengths.append(len(path) if path else 0) + + return { + 'maze_name': maze_name, + 'strategy': strategy.get_name(), + 'avg_time_ms': sum(times) / len(times), + 'min_time_ms': min(times), + 'max_time_ms': max(times), + 'avg_visited_cells': sum(visited) / len(visited), + 'min_visited_cells': min(visited), + 'max_visited_cells': max(visited), + 'avg_path_length': sum(path_lengths) / len(path_lengths), + 'min_path_length': min(path_lengths), + 'max_path_length': max(path_lengths), + 'path_found': any(pl > 0 for pl in path_lengths), + 'runs': self.runs_per_experiment + } + + def run_all_experiments(self): + """запуск всех экспериментов на всех лабиринтах""" + print("ГЕНЕРАЦИЯ ТЕСТОВЫХ ЛАБИРИНТОВ") + + # Создаём тестовые лабиринты + test_mazes = { + 'tiny_simple (10x10)': MazeGenerator.create_simple_maze(10, 10), + 'small_empty (20x20)': MazeGenerator.create_empty_maze(20, 20), + 'medium_dfs (30x30)': MazeGenerator.generate_dfs_maze(30, 30), + 'medium_complex (40x40)': MazeGenerator.generate_dfs_maze(40, 40), + 'large_dfs (50x50)': MazeGenerator.generate_dfs_maze(50, 50), + 'very_large_dfs (100x100)': MazeGenerator.generate_dfs_maze(100, 100), + 'no_exit (20x20)': MazeGenerator.create_no_exit_maze(20, 20) + } + + # Сохраняем лабиринты в файлы + for name, maze in test_mazes.items(): + filename = f"test_{name.replace(' ', '_').replace('(', '').replace(')', '')}.txt" + MazeGenerator.save_to_file(maze, filename) + print(f" Создан: {filename}") + + # Стратегии для тестирования + strategies = [ + BFSStrategy(), + DFSStrategy(), + AStarStrategy(), + DijkstraStrategy() + ] + + print("ЗАПУСК ЭКСПЕРИМЕНТОВ") + + for maze_name, maze in test_mazes.items(): + print(f"\n Лабиринт: {maze_name}") + print(f" Размер: {maze.width}x{maze.height}") + print(f" Старт: ({maze.start.x}, {maze.start.y})") + + # Проверяем, есть ли выход + if maze.exit: + print(f" Выход: ({maze.exit.x}, {maze.exit.y})") + else: + print(f" Выход: ОТСУТСТВУЕТ") + + for strategy in strategies: + print(f" → {strategy.get_name()}...", end=" ", flush=True) + result = self.run_experiment(maze, strategy, maze_name) + self.results.append(result) + + status = "✓" if result['path_found'] else "✗" + print(f"{status} {result['avg_time_ms']:.2f}мс, " + f"{result['avg_visited_cells']:.0f} клеток, " + f"{result['avg_path_length']:.1f} шагов") + + def save_to_csv(self, filename="experiment_results.csv"): + import csv + + if not self.results: + print("Нет результатов для сохранения") + return + + with open(filename, 'w', newline='', encoding='utf-8-sig') as csvfile: + fieldnames = [ + 'maze_name', 'strategy', 'runs', + 'avg_time_ms', 'min_time_ms', 'max_time_ms', + 'avg_visited_cells', 'min_visited_cells', 'max_visited_cells', + 'avg_path_length', 'min_path_length', 'max_path_length', + 'path_found' + ] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=';') + writer.writeheader() + + for result in self.results: + writer.writerow(result) + + if os.path.exists(filename): + print(f"\n Результаты сохранены в {filename}") + print(f" Размер файла: {os.path.getsize(filename)} байт") + else: + print(f"\n Ошибка: файл {filename} не создан") + + + + def print_summary(self): + print("СВОДНАЯ СТАТИСТИКА ЭКСПЕРИМЕНТОВ") + + + # Группировка по лабиринтам + grouped = {} + for result in self.results: + name = result['maze_name'] + if name not in grouped: + grouped[name] = [] + grouped[name].append(result) + + for maze_name, results in grouped.items(): + print(f"\n {maze_name}") + print(f"{'Стратегия':<25} {'Время(мс)':<12} {'Посещено':<12} {'Длина пути':<12} {'Найден':<8}") + + for result in sorted(results, key=lambda x: x['avg_time_ms']): + status = "✓" if result['path_found'] else "✗" + print(f"{result['strategy']:<25} " + f"{result['avg_time_ms']:<12.2f} " + f"{result['avg_visited_cells']:<12.0f} " + f"{result['avg_path_length']:<12.1f} " + f"{status:<8}") + + print("ОБЩАЯ СТАТИСТИКА ПО СТРАТЕГИЯМ") + + strategy_stats = {} + for result in self.results: + name = result['strategy'] + if name not in strategy_stats: + strategy_stats[name] = {'times': [], 'visited': [], 'lengths': []} + strategy_stats[name]['times'].append(result['avg_time_ms']) + strategy_stats[name]['visited'].append(result['avg_visited_cells']) + strategy_stats[name]['lengths'].append(result['avg_path_length']) + + print(f"\n{'Стратегия':<25} {'Ср.время(мс)':<15} {'Ср.посещено':<15} {'Ср.длина':<12}") + + for name, stats in strategy_stats.items(): + avg_time = sum(stats['times']) / len(stats['times']) + avg_visited = sum(stats['visited']) / len(stats['visited']) + avg_length = sum(stats['lengths']) / len(stats['lengths']) + print(f"{name:<25} {avg_time:<15.2f} {avg_visited:<15.0f} {avg_length:<12.1f}") + + def print_conclusions(self): + print("ВЫВОДЫ И РЕКОМЕНДАЦИИ") + + + # Находим лучшие стратегии + bfs_results = [r for r in self.results if r['strategy'] == "BFS (Поиск в ширину)" and r['path_found']] + dfs_results = [r for r in self.results if r['strategy'] == "DFS (Поиск в глубину)" and r['path_found']] + astar_results = [r for r in self.results if r['strategy'] == "A* (A Star)" and r['path_found']] + dijkstra_results = [r for r in self.results if r['strategy'] == "Дейкстра (Dijkstra)" and r['path_found']] + + conclusions = [] + + if bfs_results: + avg_bfs_time = sum(r['avg_time_ms'] for r in bfs_results) / len(bfs_results) + avg_bfs_length = sum(r['avg_path_length'] for r in bfs_results) / len(bfs_results) + conclusions.append(f" • BFS: среднее время {avg_bfs_time:.2f}мс, длина пути {avg_bfs_length:.1f}") + + if dfs_results: + avg_dfs_time = sum(r['avg_time_ms'] for r in dfs_results) / len(dfs_results) + avg_dfs_length = sum(r['avg_path_length'] for r in dfs_results) / len(dfs_results) + conclusions.append(f" • DFS: среднее время {avg_dfs_time:.2f}мс, длина пути {avg_dfs_length:.1f}") + + if astar_results: + avg_astar_time = sum(r['avg_time_ms'] for r in astar_results) / len(astar_results) + avg_astar_length = sum(r['avg_path_length'] for r in astar_results) / len(astar_results) + conclusions.append(f" • A*: среднее время {avg_astar_time:.2f}мс, длина пути {avg_astar_length:.1f}") + + if dijkstra_results: + avg_dijkstra_time = sum(r['avg_time_ms'] for r in dijkstra_results) / len(dijkstra_results) + avg_dijkstra_length = sum(r['avg_path_length'] for r in dijkstra_results) / len(dijkstra_results) + conclusions.append(f" • Дейкстра: среднее время {avg_dijkstra_time:.2f}мс, длина пути {avg_dijkstra_length:.1f}") + + print("\n РЕЗУЛЬТАТЫ АНАЛИЗА:\n") + for c in conclusions: + print(c) + + print("\n РЕКОМЕНДАЦИИ:\n") + print(" 1. Для маленьких лабиринтов - любой алгоритм работает быстро") + print(" 2. Для больших лабиринтов - A* даёт лучший компромисс скорость/качество") + print(" 3. BFS гарантирует кратчайший путь, но медленнее на больших картах") + print(" 4. DFS самый быстрый, но путь может быть неоптимальным") + print(" 5. Если путь не существует - BFS и A* эффективнее обнаруживают это") + + +def plot_experiment_results(csv_filename="experiment_results.csv"): + """построение графиков по результатам экспериментов""" + try: + import matplotlib.pyplot as plt + import pandas as pd + import numpy as np + except ImportError: + print("Установите: pip install matplotlib pandas") + return + + if not os.path.exists(csv_filename): + print("Файл результатов не найден") + return + + if os.path.getsize(csv_filename) == 0: + print(f"Файл {csv_filename} пустой. Сначала запустите эксперименты.") + return + + df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig') + + if 'strategy' not in df.columns: + print(f"Ошибка: в файле {csv_filename} нет колонки 'strategy'") + print(f"Доступные колонки: {list(df.columns)}") + return + + fig = plt.figure(figsize=(16, 12)) + fig.suptitle('Сравнение алгоритмов поиска в лабиринте', fontsize=16) + + # Время выполнения + ax1 = fig.add_subplot(2, 2, 1) + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + ax1.bar(data['maze_name'], data['avg_time_ms'], alpha=0.7, label=strategy) + ax1.set_xlabel('Лабиринт') + ax1.set_ylabel('Время (мс)') + ax1.set_title('Время выполнения алгоритмов') + ax1.legend(loc='upper left', fontsize=8) + ax1.tick_params(axis='x', rotation=45, labelsize=8) + + # Посещённые клетки + ax2 = fig.add_subplot(2, 2, 2) + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + ax2.bar(data['maze_name'], data['avg_visited_cells'], alpha=0.7, label=strategy) + ax2.set_xlabel('Лабиринт') + ax2.set_ylabel('Посещено клеток') + ax2.set_title('Эффективность поиска') + ax2.legend(loc='upper left', fontsize=8) + ax2.tick_params(axis='x', rotation=45, labelsize=8) + + # Длина пути + ax3 = fig.add_subplot(2, 2, 3) + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + ax3.bar(data['maze_name'], data['avg_path_length'], alpha=0.7, label=strategy) + ax3.set_xlabel('Лабиринт') + ax3.set_ylabel('Длина пути') + ax3.set_title('Оптимальность пути') + ax3.legend(loc='upper left', fontsize=8) + ax3.tick_params(axis='x', rotation=45, labelsize=8) + + # Радар-диаграмма + ax4 = fig.add_subplot(2, 2, 4, projection='polar') + + strategies = df['strategy'].unique() + metrics = ['avg_time_ms', 'avg_visited_cells', 'avg_path_length'] + metric_labels = ['Время', 'Посещено', 'Длина пути'] + + for strategy in strategies: + data = df[df['strategy'] == strategy] + values = [] + for metric in metrics: + val = data[metric].mean() + max_val = df[metric].max() + normalized = 1 - (val / max_val) if max_val > 0 else 0.5 + values.append(normalized) + values.append(values[0]) + angles = np.linspace(0, 2 * np.pi, len(metrics), endpoint=False).tolist() + angles += angles[:1] + ax4.plot(angles, values, 'o-', linewidth=2, label=strategy) + ax4.fill(angles, values, alpha=0.25) + + ax4.set_xticks(angles[:-1]) + ax4.set_xticklabels(metric_labels) + ax4.set_ylim(0, 1) + ax4.set_title('Радар-диаграмма (дальше от центра = лучше)') + ax4.legend(loc='upper right', fontsize=7, bbox_to_anchor=(1.3, 1.0)) + + plt.tight_layout() + plt.savefig('algorithm_comparison.png', dpi=150, bbox_inches='tight') + plt.show() + print("График сохранён: algorithm_comparison.png") + + +def plot_by_maze(csv_filename="experiment_results.csv"): + """построение графиков для каждого лабиринта""" + try: + import matplotlib.pyplot as plt + import pandas as pd + except ImportError: + return + + if not os.path.exists(csv_filename): + return + + + df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig') + print("Колонки в файле:", list(df.columns)) + + if 'maze_name' not in df.columns: + for col in df.columns: + if 'maze' in col.lower() or 'name' in col.lower(): + df.rename(columns={col: 'maze_name'}, inplace=True) + break + + if 'maze_name' not in df.columns: + print("Ошибка: колонка 'maze_name' не найдена") + return + + mazes = df['maze_name'].unique() + mazes = df['maze_name'].unique() + + fig, axes = plt.subplots(len(mazes), 3, figsize=(15, 4 * len(mazes))) + fig.suptitle('Детальный анализ по каждому лабиринту', fontsize=14) + + if len(mazes) == 1: + axes = axes.reshape(1, -1) + + for i, maze_name in enumerate(mazes): + maze_data = df[df['maze_name'] == maze_name] + + axes[i, 0].bar(maze_data['strategy'], maze_data['avg_time_ms']) + axes[i, 0].set_title(f'{maze_name}\nВремя (мс)') + axes[i, 0].tick_params(axis='x', rotation=45, labelsize=8) + + axes[i, 1].bar(maze_data['strategy'], maze_data['avg_visited_cells']) + axes[i, 1].set_title('Посещено клеток') + axes[i, 1].tick_params(axis='x', rotation=45, labelsize=8) + + bars = axes[i, 2].bar(maze_data['strategy'], maze_data['avg_path_length']) + axes[i, 2].set_title('Длина пути') + axes[i, 2].tick_params(axis='x', rotation=45, labelsize=8) + + for j, (bar, found) in enumerate(zip(bars, maze_data['path_found'])): + if not found: + bar.set_color('red') + + plt.tight_layout() + plt.savefig('maze_detailed_analysis.png', dpi=150, bbox_inches='tight') + plt.show() + print("Детальные графики сохранены: maze_detailed_analysis.png") + + +def print_analysis(csv_filename="experiment_results.csv"): + """вывод анализа эффективности алгоритмов""" + try: + import pandas as pd + except ImportError: + return + + if not os.path.exists(csv_filename): + return + + df = pd.read_csv(csv_filename, sep=';', encoding='utf-8-sig') + + print("DEBUG: Колонки в файле:", list(df.columns)) + if 'strategy' not in df.columns: + for col in df.columns: + if 'strategy' in col.lower() or 'algo' in col.lower(): + df.rename(columns={col: 'strategy'}, inplace=True) + break + + if 'strategy' not in df.columns: + print("Ошибка: колонка 'strategy' не найдена в CSV") + return + + + print("АНАЛИЗ ЭФФЕКТИВНОСТИ АЛГОРИТМОВ") + + + print("\nОбщая статистика по алгоритмам:") + print(f"{'Алгоритм':<25} {'Время(мс)':<12} {'Посещено':<12} {'Длина пути':<12} {'Найден %':<10}") + + for strategy in df['strategy'].unique(): + data = df[df['strategy'] == strategy] + avg_time = data['avg_time_ms'].mean() + avg_visited = data['avg_visited_cells'].mean() + avg_length = data['avg_path_length'].mean() + found_rate = data['path_found'].mean() * 100 + print(f"{strategy:<25} {avg_time:<12.2f} {avg_visited:<12.0f} {avg_length:<12.1f} {found_rate:<10.1f}%") + + + print("АНАЛИЗ ПО ТИПАМ ЛАБИРИНТОВ") + + for maze_name in df['maze_name'].unique(): + maze_data = df[df['maze_name'] == maze_name] + + print(f"\nЛабиринт: {maze_name}") + + if 'tiny' in maze_name: + maze_type = "Маленький (10x10)" + elif 'small' in maze_name: + maze_type = "Небольшой (20x20)" + elif 'medium' in maze_name: + maze_type = "Средний (30x30-40x40)" + elif 'large' in maze_name: + maze_type = "Большой (50x50)" + elif 'no_exit' in maze_name: + maze_type = "Без выхода" + else: + maze_type = "Обычный" + + print(f" Тип: {maze_type}") + + best_time = maze_data.loc[maze_data['avg_time_ms'].idxmin()] + print(f" Самый быстрый: {best_time['strategy']} ({best_time['avg_time_ms']:.2f} мс)") + + best_visited = maze_data.loc[maze_data['avg_visited_cells'].idxmin()] + print(f" Самый экономный: {best_visited['strategy']} ({best_visited['avg_visited_cells']:.0f} клеток)") + + path_data = maze_data[maze_data['path_found'] == True] + if not path_data.empty: + best_path = path_data.loc[path_data['avg_path_length'].idxmin()] + print(f" Самый короткий путь: {best_path['strategy']} ({best_path['avg_path_length']:.1f} шагов)") + else: + print(" Путь не найден ни одним алгоритмом") + + + print("ВЫВОДЫ И РЕКОМЕНДАЦИИ") + + + print(""" +1. BFS (Поиск в ширину): + - Гарантирует кратчайший путь + - Медленнее на больших лабиринтах + - Рекомендуется для маленьких лабиринтов (до 20x20) + +2. DFS (Поиск в глубину): + - Самый быстрый по времени + - Не гарантирует кратчайший путь + - Рекомендуется когда важна скорость, а не оптимальность + +3. A* (A Star): + - Лучший компромисс между скоростью и оптимальностью + - Рекомендуется для больших и сложных лабиринтов (40x40+) + +4. Дейкстра: + - Гарантирует оптимальный путь + - Работает с взвешенными графами + - Медленнее A* на больших лабиринтах + +ИТОГОВЫЕ РЕКОМЕНДАЦИИ: +- Маленькие лабиринты (до 20x20): BFS +- Средние лабиринты (20x20 - 40x40): A* +- Большие лабиринты (40x40+): A* или DFS +- Когда нужен кратчайший путь: BFS или A* +- Когда важна только скорость: DFS +- Лабиринты без выхода: BFS или A* (быстрее обнаруживают) +""") + + +def run_full_analysis(): + """Запуск полного анализа с построением графиков""" + if not os.path.exists("experiment_results.csv"): + print("Результаты не найдены. Запускаем эксперименты...") + runner = ExperimentRunner(runs_per_experiment=5) + runner.run_all_experiments() + runner.save_to_csv() + runner.print_summary() + runner.print_conclusions() + + plot_experiment_results() + plot_by_maze() + print_analysis() + + + +def run_experiments(): + """Запуск экспериментов с построением графиков и анализом""" + runner = ExperimentRunner(runs_per_experiment=5) + runner.run_all_experiments() + runner.save_to_csv() + runner.print_summary() + runner.print_conclusions() + + run_full_analysis() + + + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == 'experiment': + run_experiments() + else: + # Интерактивный режим + sample = """############### +#S # +# ### ####### # +# # # # +### # ### # # # +# # # # # +# ### # ### ### +# # E # +###############""" + + with open("maze.txt", "w") as f: + f.write(sample) + + builder = TextFileMazeBuilder() + maze = builder.buildFromFile("maze.txt") + + print(f"Лабиринт загружен: {maze.width}x{maze.height}") + print(f"Старт: ({maze.start.x}, {maze.start.y})") + print(f"Выход: ({maze.exit.x}, {maze.exit.y})") + + game = GameController(maze) + game.run() \ No newline at end of file diff --git a/nikolaevda/task2/algorithm_comparison.png b/nikolaevda/task2/algorithm_comparison.png new file mode 100644 index 0000000..cdf691f Binary files /dev/null and b/nikolaevda/task2/algorithm_comparison.png differ diff --git a/nikolaevda/task2/experiment_results.csv b/nikolaevda/task2/experiment_results.csv new file mode 100644 index 0000000..c257bed --- /dev/null +++ b/nikolaevda/task2/experiment_results.csv @@ -0,0 +1,29 @@ +maze_name;strategy;runs;avg_time_ms;min_time_ms;max_time_ms;avg_visited_cells;min_visited_cells;max_visited_cells;avg_path_length;min_path_length;max_path_length;path_found +tiny_simple (10x10);BFS (Поиск в ширину);5;0.10518000926822424;0.08800008799880743;0.16200006939470768;28.0;28;28;19.0;19;19;True +tiny_simple (10x10);DFS (Поиск в глубину);5;0.0728200189769268;0.061400001868605614;0.10630011092871428;28.0;28;28;19.0;19;19;True +tiny_simple (10x10);A Star;5;0.1740999985486269;0.1604999415576458;0.22060004994273186;28.0;28;28;19.0;19;19;True +tiny_simple (10x10);Дейкстра (Dijkstra);5;0.14854001346975565;0.14189991634339094;0.1716000260785222;28.0;28;28;19.0;19;19;True +small_empty (20x20);BFS (Поиск в ширину);5;1.350820017978549;1.2996000004932284;1.4386000111699104;400.0;400;400;39.0;39;39;True +small_empty (20x20);DFS (Поиск в глубину);5;1.0200999910011888;0.9803998982533813;1.131800003349781;400.0;400;400;191.0;191;191;True +small_empty (20x20);A Star;5;2.608919981867075;2.483000047504902;2.719299984164536;400.0;400;400;39.0;39;39;True +small_empty (20x20);Дейкстра (Dijkstra);5;2.270600013434887;2.1596000296995044;2.4237000616267323;400.0;400;400;39.0;39;39;True +medium_dfs (30x30);BFS (Поиск в ширину);5;0.3978000022470951;0.3643999807536602;0.5170999793335795;110.0;110;110;77.0;77;77;True +medium_dfs (30x30);DFS (Поиск в глубину);5;0.2578399842604995;0.25129993446171284;0.2742999931797385;80.0;80;80;77.0;77;77;True +medium_dfs (30x30);A Star;5;0.5116799846291542;0.47670002095401287;0.6345999427139759;88.0;88;88;77.0;77;77;True +medium_dfs (30x30);Дейкстра (Dijkstra);5;0.5545199848711491;0.5197999998927116;0.6467000348493457;110.0;110;110;77.0;77;77;True +medium_complex (40x40);BFS (Поиск в ширину);5;0.7289399858564138;0.7038000039756298;0.8005000418052077;199.0;199;199;137.0;137;137;True +medium_complex (40x40);DFS (Поиск в глубину);5;0.6589999888092279;0.46930008102208376;1.0472999420017004;141.0;141;141;137.0;137;137;True +medium_complex (40x40);A Star;5;0.9274400072172284;0.8467999286949635;1.2432000366970897;158.0;158;158;137.0;137;137;True +medium_complex (40x40);Дейкстра (Dijkstra);5;0.9650199906900525;0.9348000166937709;1.0183999547734857;199.0;199;199;137.0;137;137;True +large_dfs (50x50);BFS (Поиск в ширину);5;2.0312600303441286;1.7980000702664256;2.2323000011965632;459.0;459;459;277.0;277;277;True +large_dfs (50x50);DFS (Поиск в глубину);5;1.0593399871140718;1.0408000089228153;1.0833999840542674;287.0;287;287;277.0;277;277;True +large_dfs (50x50);A Star;5;2.5984000181779265;2.4158000014722347;2.81510001514107;427.0;427;427;277.0;277;277;True +large_dfs (50x50);Дейкстра (Dijkstra);5;2.5939000071957707;2.1897999104112387;3.5234999377280474;459.0;459;459;277.0;277;277;True +very_large_dfs (100x100);BFS (Поиск в ширину);5;35.92262000311166;34.23479991033673;40.153599926270545;4184.0;4184;4184;1897.0;1897;1897;True +very_large_dfs (100x100);DFS (Поиск в глубину);5;17.542819981463253;17.002100008539855;19.42270004656166;2216.0;2216;2216;1897.0;1897;1897;True +very_large_dfs (100x100);A Star;5;23.934299987740815;22.515099961310625;26.25090000219643;4017.0;4017;4017;1897.0;1897;1897;True +very_large_dfs (100x100);Дейкстра (Dijkstra);5;21.47540000732988;20.28030005749315;24.353899993002415;4184.0;4184;4184;1897.0;1897;1897;True +no_exit (20x20);BFS (Поиск в ширину);5;0.004719989374279976;0.001300009898841381;0.009899958968162537;0.0;0;0;0.0;0;0;False +no_exit (20x20);DFS (Поиск в глубину);5;0.0009800074622035027;0.000400003045797348;0.002900022082030773;0.0;0;0;0.0;0;0;False +no_exit (20x20);A Star;5;0.000700005330145359;0.000300002284348011;0.001800013706088066;0.0;0;0;0.0;0;0;False +no_exit (20x20);Дейкстра (Dijkstra);5;0.0009200070053339005;0.000400003045797348;0.002500019036233425;0.0;0;0;0.0;0;0;False diff --git a/nikolaevda/task2/maze_detailed_analysis.png b/nikolaevda/task2/maze_detailed_analysis.png new file mode 100644 index 0000000..896a383 Binary files /dev/null and b/nikolaevda/task2/maze_detailed_analysis.png differ diff --git a/nikolaevda/task2/test_large_dfs_50x50.txt b/nikolaevda/task2/test_large_dfs_50x50.txt new file mode 100644 index 0000000..5fa1231 --- /dev/null +++ b/nikolaevda/task2/test_large_dfs_50x50.txt @@ -0,0 +1,50 @@ +################################################## +#S#.............................#.........#.#...## +#.###.#################.#.#####.#.#######.#.#.#.## +#...#...#...#.........#.#.....#...#.....#.#...#.## +###.#####.#.#.#######.#######.#######.###.#.###.## +#.#.......#.#.....#.#...#.....#.......#...#.#...## +#.#########.#####.#.###.#.#####.#####.#.###.#.#### +#.........#...#...#...#.#.#.......#...#...#.#...## +#.#######.###.#.#####.#.#.#######.#.#####.#####.## +#.......#.#...#.....#...#.#.......#.....#.#.....## +#####.#.#.#.#######.#.###.#.#############.#.###.## +#...#.#.#.#...#...#.#.....#...#.....#...#.#.#...## +#.#.###.#.###.#.#.#.#######.#.#.###.#.#.#.#.#.#### +#.#.#...#.#.#.#.#...#.....#.#.....#...#.#...#...## +#.#.#.###.#.#.#.###.#.###.#############.###.###### +#.#...#.....#.#.#.#.#...#.#...........#.....#...## +#.###########.#.#.#.#.###.#.#########.#######.#.## +#.....#.......#.#...#.#...#.#.......#.....#...#.## +#.###.#.#######.#####.#.#.#.#####.#######.#.###.## +#.#.#...#.......#.....#.#.#.#...#.....#...#...#.## +#.#.#####.###.#.#.#####.###.#.#.#####.#.###.###.## +#.#.....#.#.#.#.#...#.#.....#.#.......#.#...#...## +#.#.###.#.#.#.#####.#.#######.#######.#.#.###.#### +#.#.#.#.#.#.#.......#.........#.......#...#.#...## +#.#.#.#.#.#.###########.#.#######.#########.###.## +#.#...#.#...#...........#.#.....#...#...#...#...## +#.###.#.###.#########.#####.###.###.#.#.#.#.#.#### +#...#.#.......#.....#.........#...#...#...#.#...## +###.#########.#.###.###.#########.###.#########.## +#.#.........#.#.#.#...#.#.........#...#.........## +#.#########.#.#.#.###.###.#.#######.###.#######.## +#...#.......#.#.#.....#...#.#...#...#...#.#...#.## +#.###.#######.#.#.#####.#####.#.#####.###.#.#.#.## +#...#.#.....#...#...#.....#...#.#.....#...#.#...## +###.#.###.#.#######.#####.#.###.#.#####.###.###### +#...#...#.#.......#.#...#.#...#...#.......#.#...## +#.#.###.#.#######.#.#.#.#.###.###########.#.#.#### +#.#.#.#...#.......#...#.....#.#...........#.#...## +#.#.#.#####.#############.###.#.#.#######.#.###.## +#.#.......#...#.....#...#.#...#.#.#...#...#...#.## +#.#######.###.###.#.#.#.###.###.#.###.#.#####.#.## +#.....#...#...#...#.#.#.....#...#...#...#.....#.## +#.###.#####.###.###.#.#######.#####.#####.#####.## +#.#...#.....#.....#.#.....#.#.....#.....#.#.....## +###.#.#.#####.###.#.###.#.#.#####.#####.#.#.###.## +#...#.#.#.....#...#...#.#.#...#...#...#.#...#.#.## +#.#####.#######.#####.###.#.###.###.#.#.#####.#.## +#...............#.........#.........#.#........E## +################################################## +################################################## diff --git a/nikolaevda/task2/test_medium_complex_40x40.txt b/nikolaevda/task2/test_medium_complex_40x40.txt new file mode 100644 index 0000000..9f3b1c6 --- /dev/null +++ b/nikolaevda/task2/test_medium_complex_40x40.txt @@ -0,0 +1,40 @@ +######################################## +#S..#...........#.....#...............## +###.#.#.#######.#####.#.#.############## +#.#.#.#.#.......#...#.#.#.......#.....## +#.#.#.#.#.#######.#.#.#.#######.#.###.## +#.#.#.#.#.#...#...#...#...#.....#...#.## +#.#.###.#.#.###.#.#######.#.#######.#.## +#.#.#...#.#.....#...#...#.#.#.....#.#.## +#.#.#.###.#########.#.#.#.#.#.###.#.#.## +#.#...#.#.....#.....#.#...#.#...#...#.## +#.#####.#####.#.###.#.#####.###.#####.## +#.......#.#...#.#...#...#.....#.#.#...## +#.#####.#.#.###.#######.#.#####.#.#.#### +#.#.......#.#.#.......#.#.......#.#...## +#.#########.#.#.###.###.#########.###.## +#...#.......#.....#.#...#...........#.## +###.#.#######.#####.#.#######.#####.#.## +#...#.#...#...#...#.#.......#.....#.#.## +#.#.#.#.###.###.#.#.#######.#.###.#.#.## +#.#.#.#.....#...#.........#.#...#.#.#.## +#.###.#.#####.###########.#.#####.#.#.## +#.....#.#...#.#...........#.......#.#.## +#.#####.###.#.###################.###.## +#.....#.....#.#...........#.......#...## +#####.#######.#.#########.#.#######.#### +#...#...#...#...#.......#.........#...## +#.#####.#.#.#####.#####.#########.###.## +#.....#...#.......#...#.......#.#...#.## +#.###.###############.#######.#.#.###.## +#.#...#...#...........#.....#.#...#...## +#.#.#.#.#.#####.#######.###.#.###.#.#### +#.#.#...#.#.....#.......#...#...#.#...## +#.#.#####.#.###.#.#######.#.###.#.###.## +#.#...#.....#...#.#.....#.#...#.#...#.## +#.###.#########.#.#####.#.###.#.#.###.## +#...#.#...#...#.#.....#.#.#...#.#.#...## +###.#.#.#.#.#.#######.#.#.#####.###.#.## +#...#...#...#...........#...........#E## +######################################## +######################################## diff --git a/nikolaevda/task2/test_medium_dfs_30x30.txt b/nikolaevda/task2/test_medium_dfs_30x30.txt new file mode 100644 index 0000000..ff99341 --- /dev/null +++ b/nikolaevda/task2/test_medium_dfs_30x30.txt @@ -0,0 +1,30 @@ +############################## +#S#.........#.......#.......## +#.#####.###.###.###.#.#####.## +#.#.....#.#...#.#.#.#.....#.## +#.#.#####.###.#.#.#.###.#.#### +#...#.....#.#...#.#...#.#...## +#####.###.#.#####.###.#####.## +#.....#.#...#.......#...#...## +#.#####.###.#.#####.###.#.#.## +#.#.........#.....#...#.#.#.## +#.###.###############.#.#.#### +#...#.#...............#.#...## +###.#.#####.#########.#.###.## +#...#.....#...#...#...#.....## +#.#######.###.#.###.#######.## +#.#.........#.#.....#.....#.## +#.#########.#.#.#########.#.## +#.#.......#...#.....#.....#.## +#.#.#####.#########.#.#####.## +#...#...#.#.......#...#.....## +#.###.#.#.#####.#####.#.###### +#.#...#.#.....#.......#.....## +#.#.###.#####.###.#########.## +#.#.#.#.....#...#.#.......#.## +#.#.#.###.#####.#.#.#####.#.## +#...#.....#.....#.#...#.#.#.## +#####.#####.#########.#.#.#.## +#.........#.............#..E## +############################## +############################## diff --git a/nikolaevda/task2/test_no_exit_20x20.txt b/nikolaevda/task2/test_no_exit_20x20.txt new file mode 100644 index 0000000..0fa9500 --- /dev/null +++ b/nikolaevda/task2/test_no_exit_20x20.txt @@ -0,0 +1,20 @@ +#################### +#S......#.........## +#######.#.###.###### +#.....#.#...#.....## +#.#.#.#.#########.## +#.#.#.#.....#.....## +#.#.#######.#.###.## +#.#.......#.#...#.## +#.#####.###.###.#.## +#.#...#...#.....#.## +#.#.#.###.#######.## +#...#...#.........## +#######.############ +#.....#.#.........## +#.###.#.#.#######.## +#.#...#.#.#.....#.## +#.#####.###.###.#.## +#...........#....### +#################### +#################### diff --git a/nikolaevda/task2/test_small_empty_20x20.txt b/nikolaevda/task2/test_small_empty_20x20.txt new file mode 100644 index 0000000..b7bd7ab --- /dev/null +++ b/nikolaevda/task2/test_small_empty_20x20.txt @@ -0,0 +1,20 @@ +S................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +.................... +...................E diff --git a/nikolaevda/task2/test_tiny_simple_10x10.txt b/nikolaevda/task2/test_tiny_simple_10x10.txt new file mode 100644 index 0000000..f05e625 --- /dev/null +++ b/nikolaevda/task2/test_tiny_simple_10x10.txt @@ -0,0 +1,10 @@ +S.######## +...####### +#...###### +##...##### +###...#### +####...### +#####...## +######...# +#######... +########.E diff --git a/nikolaevda/task2/test_very_large_dfs_100x100.txt b/nikolaevda/task2/test_very_large_dfs_100x100.txt new file mode 100644 index 0000000..d52abe1 --- /dev/null +++ b/nikolaevda/task2/test_very_large_dfs_100x100.txt @@ -0,0 +1,100 @@ +#################################################################################################### +#S....#.......#...#...............................#.......#.....#.......#.....#.....#.....#.#.....## +#####.#.#.#####.#.#.###########.#####.#########.###.#.###.###.#.#.#####.#.#.#.#.###.#.###.#.#.#.#.## +#.....#.#.#.....#.......#.....#...#...#.......#.#...#...#.#...#...#...#.#.#.#...#.#.#.#...#...#.#.## +#.#######.#.#############.###.#####.###.#####.#.#.#####.#.#.#######.#.#.###.#####.#.#.#.#######.#### +#.#.....#.....#.#.........#...#.....#...#.#...#.#.....#.#...#.#.....#.#.....#.....#.#.#.#.....#...## +#.#.###.#####.#.#.#########.###.###.#.###.#.###.#####.#.#####.#.#.#############.###.#.#.###.#.###.## +#...#...#...#...#...#.....#.#.....#.#.....#.#.....#.#.#.....#...#.........#.....#...#.#.#...#.....## +#####.###.#.#######.#.#####.#####.#######.#.#####.#.#.#####.#############.#.#.###.###.#.#.#######.## +#...#.....#.......#.#.......#...#.......#.#...#.#...#.#...#...#...#...#...#.#.....#...#...#.....#.## +#.###############.#.#.#######.#.#.#####.#.###.#.#.###.###.###.#.#.#.#.#.###.#########.###.#.###.#.## +#...#...#.........#.#.#.......#.#.#...#...#...#.#.#...#.....#...#...#.#.....#.........#...#...#.#.## +#.#.#.#.#.#########.#.#.#######.#.###.#####.###.#.#.###.#########.###.#.#####.#.###########.###.#.## +#.#...#...#.........#.#.#.....#.#.#.....#...#.....#.#...#.......#.#...#.......#.#...........#...#.## +#.###############.###.#.#.###.#.#.#.###.#.#####.###.###.#.#####.#.#.#############.###########.###### +#.#...............#.#.#.....#.#.#...#...#.....#.#.#.....#...#...#.#.....#.....#...#.......#.#.#...## +#.###.#############.#.#######.#.#######.#####.#.#.#####.###.#.#######.#.#.###.#.###.#####.#.#.#.#.## +#.....#...#.....#...#.........#.......#.#...#.#...#...#...#.#.#.....#.#.#...#.#...#.#.......#...#.## +#######.#.#.#.###.#.#################.#.#.#.#.###.###.#.###.#.###.#.#.#.#.###.###.#.#.###########.## +#.......#...#.#...#...#.....#.......#.#.#.#.#...#.....#.#...#.....#.#.#...#...#...#.#.#...#.....#.## +#.###########.#.###.#.#.###.#.#.#####.#.###.###.#######.#.#########.#.#####.#.#.###.#.#.#.#.###.#.## +#...#.#.......#...#.#.#.#.#.#.#...#...#.#.....#...#.....#.#.......#.#.....#.#.#.#...#.#.#.#.#...#.## +#.#.#.#.#########.#.#.#.#.#.#####.#.###.#.#.#####.#.#####.#.###.#.#.#####.#.#.#.#.###.#.###.#.###.## +#.#.#.......#...#.#.#...#...#...#.#...#...#.#...#.#.#.#...#...#.#.#.....#.#.#.#.#.#.....#...#.#...## +###.#######.###.#.#.#######.#.#.#.###.###.###.#.#.#.#.#.#####.#.#######.#.#.###.#.#.#####.###.#.#### +#...#...#.......#.#.......#...#.....#.#...#...#.#.....#.....#.#...#...#.#.#.......#.#.....#.#.#...## +#.###.#.#########.#######.#######.###.#.###.###.#####.#####.#####.#.#.#.#.###########.#####.#.###.## +#.....#.........#.#.#.....#...#...#...#...#...#...#.....#...#...#...#...#...#.....#...#...#.#.....## +#.#############.#.#.#.#####.###.###.###.#####.###.#.#####.###.#.###.#########.###.#.###.#.#.#####.## +#.#...#.....#...#.#.#.#.........#.#.#...#.....#...#...#...#...#...#.......#...#.#...#...#...#...#.## +#.#.#.#.###.#.#.#.#.#.#####.#####.#.#####.#####.#######.###.#####.#######.#.###.#####.#.#####.#.#.## +#...#...#.#.#.#.#...#.#...#.......#...#...#.....#...#...#...#...#.#.....#...#.........#.#...#.#...## +#########.#.#.###.###.#.#.#####.#####.#.###.#####.#.#.#.#.###.#.#.#.#.#######.#########.#.#.#.###### +#...#.......#.#...#...#.#.....#.#...#...#...#.....#.#.#.#...#.#...#.#.........#.#.....#.#.#.#.#...## +#.###.#######.#.###.###.#####.#.#.#.#####.###.#####.#.#####.#.#####.#######.###.#.#.###.#.#.#.#.#### +#...#.#...#...#...#.....#...#.#.#.#.....#...#...#...#.#.....#.#...#.#.....#.#.....#.....#.#.#.#...## +###.#.###.#.#####.#########.#.###.#.#######.###.###.#.#.#####.###.#.#.###.#.#.#######.###.#.#.###.## +#...#...#.#.#.....#.........#.#...#.......#...#...#.#.....#...#...#...#...#.#...#...#.#...#.#.....## +#.#.###.#.#.#.#####.###.#####.#.#######.#####.###.#.#######.###.#######.###.#####.#.###.###.#####.## +#.#.#...#.#...#.......#.....#...#.....#.....#...#.#.........#.........#.#.#.......#...#.#.#.......## +#.###.###.#################.#####.#######.#.###.#.###########.###.#####.#.###########.#.#.########## +#...#.#.........#.......#.......#.#.....#.#...#.#.#.#.......#.#.#.....#.#.........#...#...#.......## +###.#.###.#####.#.#####.#.#####.#.#.###.#.###.#.#.#.#.#####.#.#.#####.#.#.#####.###.#.###.#.#####.## +#...#.#...#...#...#...#...#.......#.#.#.#.#...#.#.#...#...#.#.#.....#.#...#.....#...#.#.#.#...#...## +#.#.#.#.###.#######.#######.#######.#.#.###.###.#.#.###.#.#.#.###.###.###.#######.###.#.#.#####.#.## +#.#.#.#.#.#...#.....#...#...#...#...#.......#...#.#...#.#...#...#...#.....#.......#.#...#.#.....#.## +#.###.#.#.#.#.#.###.#.#.#.###.#.#.#######.###.###.###.#.#######.###.#######.#######.###.#.#.#####.## +#.#...#.#.#.#.#...#...#...#.#.#.#...#...#.#...#...#...#.#.....#...#...#.....#.........#.#...#.....## +#.#.###.#.#.#.###.#########.#.#.###.#.#.###.###.###.###.#.#.#.###.#.#.#.#####.#.#######.#####.###### +#...#...#...#...#.#.........#.#.....#.#.....#...#...#...#.#.#.#...#.#.#...#...#.............#.#...## +#.###.###.###.###.#####.###.#.#.#####.#########.#.###.###.#.#.#.###.#.###.#.#################.#.#### +#.....#.#.#.#...#.....#...#.#.#.#.....#.........#.#.#.....#.#.#.#...#...#.#.............#.....#...## +#######.#.#.###.#####.#.###.#.###.#######.#######.#.#######.#.#.#.#.###.#.###############.#######.## +#.....#.......#...#...#.#...#.#...#.....#.#...#...#...#.....#.#.#.#...#.#.......#.......#.#.......## +#.#.###.#######.###.#####.#.#.#.#####.#.#.#.#.#.###.###.###.###.#.###.#########.#.#####.#.#.#####.## +#.#.#...#.#.....#...#.....#.#...#.....#.#.#.#...#.....#...#.#...#...#.........#...#...#...#...#...## +#.###.###.#.#.###.###.###########.#####.#.#.#######.#.###.#.#.###.###########.#####.#.#######.#.#### +#.........#.#.#...#.........#.........#...#.......#.#...#.#.#...#.#...........#.....#.#.....#.#.#.## +#.#########.#.#.###.#######.#.#######.###########.#####.#.#####.###.#########.#.#####.#.###.#.#.#.## +#.#...#...#.#.#.#.........#...#.....#.#.....#...#.....#.#.......#...#.....#...#...#.#...#.#.#.#...## +#.###.#.#.#.#.#.#############.#.#.###.#.#.#.#.#.#####.#.#########.###.###.#.#####.#.#####.#.#.###### +#.....#.#...#.#.............#...#.#...#.#.#.#.#.....#.#.............#.#...#.....#.#...#...#.#.....## +#######.#.#################.#####.#.###.#.###.#.#.###.#.#######.###.###.#.#####.#.#.#.#.#.#.#.###.## +#.......#.#...........#...#.#.....#.#...#.....#.#.#...#.#.....#...#...#.#.....#.#.#.#...#.#.#...#.## +#.#########.#####.###.#.###.#####.#.#.#########.#.#.#####.###.###.###.#.#######.#.#######.#.#####.## +#.....#.....#...#.#...#.#...#...#.#.#.........#.#.#.#.....#.#.#.#.#...#.......#...#.......#.#.....## +#.###.#.#####.#.#.###.#.#.###.#.###.#########.#.###.#.#####.#.#.#.#.###.#####.#####.#.#####.#.###.## +#.#...#...#...#.#...#...#.#...#.....#.....#...#...#.#.#.....#.#...#...#.#.....#.....#.#.....#.#...## +###.#.###.###.#####.#.###.#.#########.###.#.#####.#.#.###.#.#.###.###.###.###.#.###.###.#####.###### +#...#...#...#.#.....#.#...#.#.....#.#.#.#.......#.#.#...#.#.#...#.#.#...#.#...#...#.#...#.........## +#.#.#######.#.#.#####.#.###.#.#.#.#.#.#.#########.#.###.#.#####.#.#.###.#.#.###.###.#.###########.## +#.#.#...#.....#...#...#.#...#.#.#...#...#.#.......#.....#.#.....#.....#.#.#.#...#...#...#.........## +#.###.#.#.#######.#####.#.#.#.#.###.###.#.#.###.#########.#.#########.#.#.###.###.#####.#.#######.## +#...#.#...#.....#.#.....#.#.#.#.#.#...#.#.#.#...#.........#...#.#.....#.#.....#.#.....#.#.#.....#.## +#.#.#.#####.###.#.#.#######.#.#.#.###.#.#.#.#####.#######.###.#.#.#####.#.#####.#####.#.#.#.#.###.## +#.#.#.#.#...#.#...#.........#.#.#.....#...#...#...#.....#...#.#...#.....#.......#...#.#.#.#.#.....## +#.#.#.#.#.###.###.#############.#########.###.#.###.###.#.###.#.###.###########.#.#.#.#.#.###.###### +#.#.#...#...#.......#...........#.......#...#.#...#...#.#.#...#.#...#.........#...#.#.#.#...#.....## +###.###.###.#########.###.#######.###.#.###.#.###.#####.#.#.#####.###.#.#####.###.###.#.###.#####.## +#...#.#...#.......#...#.#.......#.#.#.#...#.#...#.....#.#.#...#...#...#...#.....#.#...#...#.#...#.## +#.#.#.###.#######.#.###.#######.#.#.#.#####.###.#####.#.#.###.#.###.#####.#######.#.#####.#.#.#.#### +#.#.#.#.........#.#.#...#...#...#.#.#.....#.#.#.....#.#.#.#...#.#...#...#.#.....#.#.......#...#...## +#.#.#.#.#########.#.#.#.#.#.#.###.#.#####.#.#.###.###.#.#.#.###.###.###.#.#.###.#.###############.## +#.#...#.#.#.......#.#.#.#.#...#...#...#...#.#...#...#...#...#.....#.....#...#...#.....#...........## +#.#####.#.#.#####.#.###.#.#####.#####.#.###.#.#.###.###.#######.#.###.#######.#######.#.###.#####.## +#.....#.#.#.#...#.#.....#.......#.....#...#...#.#.#...#.#.....#.#...#.#.....#.......#.#.#...#...#.## +#####.#.#.#.#.#.#.#####.#########.###.###.#####.#.#.###.#.###.#####.#.#.###.#.#.#####.#.#.###.###.## +#.....#...#.#.#.....#...#.........#.#...#.........#.#...#.#.#.#...#.#.#...#.#.#...#...#.#.#...#...## +#.###.###.#.#.#######.#######.#.###.###.###########.#.###.#.#.#.#.#.###.###.#####.#.#####.###.#.#### +#...#...#.#.#...#.....#.....#.#.#...#...#.......#...#.....#.#.#.#...#...#...#...#.#...#...#...#...## +#.#.#.###.#.#####.#######.#.###.#.#.#.#####.#.#.#.#########.#.#.#####.###.###.#.#.###.#.###.#####.## +#.#.#.#...#.....#.........#.....#.#.#.....#.#.#.#...#.........#...#...#...#...#.#...#...#.......#.## +###.#.#.#######.###################.#####.###.#####.#.###########.#.###.#####.#.###.#####.#####.#.## +#...#.#.#.......#.....#.........#.......#.....#.....#...#.#.....#.....#.#.....#.........#.#...#...## +#.#####.#.#.#####.#.#.#.###.###.#.###########.#.###.###.#.#.#.#.#####.#.#.#########.#####.#.######## +#.......#.#.#.....#.#.#.#.#.#.....#...#.....#.#.#...#...#...#.#.....#.#.#.#.......#.#.....#.......## +#.#######.###.#####.###.#.#.#######.#.#.#.###.#.#####.#######.#####.###.#.#.#####.###.###########.## +#.......#.........#.......#.........#...#.....#...............#.........#.......#................E## +#################################################################################################### +#################################################################################################### diff --git a/nikolaevda/test file b/nikolaevda/test file new file mode 100644 index 0000000..87ca0d6 --- /dev/null +++ b/nikolaevda/test file @@ -0,0 +1 @@ +print("hello,world!") \ No newline at end of file