diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..272833f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Игнорируемые файлы по умолчанию +/shelf/ +/workspace.xml +# HTTP-клиент на основе редактора +/httpRequests/ diff --git a/.idea/2026-rff_mp.iml b/.idea/2026-rff_mp.iml new file mode 100644 index 0000000..c03f621 --- /dev/null +++ b/.idea/2026-rff_mp.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..590a59e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a150c6b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 7b46480..79f7c6b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,110 @@ # 2026-MP +## Проверка работ: + +| имя | 1 работа | 2 работа | зачёт | +|-------------------|----------|----------|-------| +| agafonovdm | — | — | — | +| anikinvd | — | — | — | +| BolonkinNM | — | — | — | +| BoriskovaDV | — | — | — | +| BorisovMI | — | — | — | +| BudakovIS | — | — | — | +| chizhikovaSM | — | — | — | +| DerbenevRY | — | — | — | +| duznb | — | — | — | +| dyachenkoas | — | — | — | +| Ezhovnd | — | — | — | +| famutdinovmd | — | — | — | +| filippovavm | — | — | — | +| fomichevks | — | — | — | +| GorkinMM | — | — | — | +| groshevava | — | — | — | +| GutovVM | — | — | — | +| ivanchenkoam | — | — | — | +| ivantsovma | — | — | — | +| kalinovskiymi | — | — | — | +| KislyuninED | — | — | — | +| KolbasovPD | — | — | — | +| kolesovve | — | — | — | +| komissarovgo | — | — | — | +| konnovaea | — | — | — | +| kornevma | — | — | — | +| KorotkinSE | — | — | — | +| krasnovia | — | — | — | +| KuzminskiyAA | — | — | — | +| KuznetsovAS | — | — | — | +| KuznetsovMA | — | — | — | +| kuznetsovTD | — | — | — | +| KuznetsovYuM | — | — | — | +| LarikovaAA | — | — | — | +| lomakinae | — | — | — | +| LukovnikovDE | — | — | — | +| MalkinMV | — | — | — | +| MarkinAM | — | — | — | +| MashinDD | — | — | — | +| meosyam | — | — | — | +| MininaVD | — | — | — | +| MochalovAE | — | — | — | +| morozovns | — | — | — | +| MusinAA | — | — | — | +| MylnikovAS | — | — | — | +| nehoroshevaa | — | — | — | +| nikitovie | — | — | — | +| nikolaevda | — | — | — | +| novikovsd | — | — | — | +| osininyai | — | — | — | +| osipovamd | — | — | — | +| petryaninyas | — | — | — | +| pogodinda | — | — | — | +| pomelovsd | — | — | — | +| ProninVV | — | — | — | +| raskatovia | — | — | — | +| romanovpv | — | — | — | +| rybakovaa | — | — | — | +| SavelevMI | — | — | — | +| semyanovra | — | — | — | +| shahovaa | — | — | — | +| shalovsa | — | — | — | +| shapovalovka | — | — | — | +| shekurovaa | — | — | — | +| ShulpinIN | — | — | — | +| SimonovaMS | — | — | — | +| skorohodovsa | — | — | — | +| smirnovad | — | — | — | +| Smirnovvs | — | — | — | +| sobininaas | — | — | — | +| SobolevNS | — | — | — | +| SokolovEN | — | — | — | +| SokolovNE | — | — | — | +| soldatkinao | — | — | — | +| SolovevDD | — | — | — | +| SolovevDS | — | — | — | +| soninrv | — | — | — | +| SorokinAD | — | — | — | +| sorokinfi | — | — | — | +| starikovta | — | — | — | +| stepinim | — | — | — | +| stepushovgs | — | — | — | +| svetlakovkyu | — | — | — | +| talantsevgi | — | — | — | +| tseremonnikovaaa | — | — | — | +| VaravinVV | — | — | — | +| VarnakovAA | — | — | — | +| victorovaas | — | — | — | +| VildyaevAV | — | — | — | +| volkovim | — | — | — | +| VolkovVA | — | — | — | +| YanyaevAA | — | — | — | +| YaroslavtsevAS | — | — | — | +| zaharoves | — | — | — | +| ZelentsovAV | — | — | — | +| zhigalovrd | — | — | — | +| ZhuravlevDV | — | — | — | +| zverevem | — | — | — | + + + Практика по курсам "Методы программирования" и "Программная инженерия" РФФ ННГУ [Презентация по курсу (обновляемая)](https://docs.google.com/presentation/d/1wmYjy5QDoYECEHi7NAAINPulU9pLsaIi-aLaUppspps/edit?usp=sharing) @@ -14,9 +119,11 @@ **Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.** -### Крайний срок приема работ 25.05.2026 до 14:00 +### Крайний срок приема работ 27.05.2026 в 10:00 ~~25.05.2026 до 14:00~~ +#### (поправочный на $\pi$, 19:00 26.05.2026, и на следующий рабочий день) -## Задание 1 -- репозиторий + +## Задание 0 -- репозиторий [отдельный срок на создание PR с папкой: 28.02.2026] 0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе). @@ -43,12 +150,340 @@ 6. Отправь ветку **в свой форк** на Gitea: ```bash - git push origin IvanovII + git push origin ``` + +если просит, перед этим сделать git push --set-upstream origin 7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что: - Базовый репозиторий: **учебный** (преподавателя) - Базовая ветка: **develop** - Сравниваемая ветка: **свой форк / IvanovII** -8. Отправь PR. \ No newline at end of file +8. Отправь PR. + +## Задание 1 -- структуры данных +***Напоминание: под каждое задание вы создаете отдельную ветку*** + +>Для оформления результатов заведи папку **docs** в своей папке и сохраняй туда отчет (в любом формате от .doc до .md, а то и .jpnb). Вспомогательные файлы клади в подпапку **data** внутри **docs** + +**Цель работы** + +Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Вы должны собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике. + +**!! Задание выполнять в структурной (процедурной) парадигме, не используя классы. Главное реализовать структуры данных «руками» и сравнить их производительность.** + +### Базовые операции (обязательны для всех): + +`insert(name, phone)` -- добавить или обновить запись. + +`find(name)` -- phone или None. + +`delete(name)` -- удалить запись, игнорировать отсутствие. + +`list_all()` -- список всех записей, отсортированный по имени (для BST in‑order обход; для списка и хеш‑таблицы — собрать и отсортировать явно). + +#### 1. Связный список (LinkedListPhoneBook) + +Узел представляется словарём: `{'name': 'Имя', 'phone': '123', 'next': None}.` + +**Функции:** + +`def ll_insert(head, name, phone)` — проходит до конца (или сразу добавляет в конец) и возвращает новую голову (если вставка в начало) или изменяет список по ссылке. Удобнее возвращать новую голову, если вставка может быть в начало. + +`def ll_find(head, name)` — ищет узел, возвращает телефон или None. + +`def ll_delete(head, name)` — удаляет узел, возвращает новую голову. + +`def ll_list_all(head)` — собирает все записи в список и сортирует (сортировка вынесена отдельно). + +#### 2. Хеш-таблица +Хранится как список buckets фиксированной длины, каждый элемент — голова связного списка (или None). + +**Функции:** + +`def ht_insert(buckets, name, phone)` — вычисляет индекс, вызывает ll_insert для соответствующего бакета. + +Аналогично `ht_find, ht_delete, ht_list_all` (последняя собирает все записи из всех бакетов и сортирует). + +#### 3. Двоичное дерево поиска +Узел — словарь: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.` + +**Функции:** + +`def bst_insert(root, name, phone)` — рекурсивно или итеративно вставляет, возвращает новый корень (если корень меняется). + +`def bst_find(root, name)` — поиск. + +`def bst_delete(root, name)` — удаление, возвращает новый корень. + +`def bst_list_all(root)` — центрированный обход (рекурсивно собирает записи в отсортированном порядке). + +### Экспериментальная часть (подробно об измерении времени) +#### 1. Генерация тестовых данных +Создайте список records из N элементов (например, N = 10000). Каждый элемент — кортеж (name, phone). + +Имена генерируйте как `f"User_{i:05d}"` (равномерное распределение) или случайные слова из небольшого набора (чтобы были повторения и коллизии). Для проверки влияния порядка подготовьте два варианта одного и того же набора: + +`records_shuffled` — случайный порядок. + +`records_sorted` — отсортированный по имени (по алфавиту). + +#### 2. Инструменты замера времени +Используйте модуль **time**: + +```python +import time + +start = time.perf_counter() +# ... операции ... +end = time.perf_counter() +elapsed = end - start # время в секундах +``` + +Для многократных замеров удобен `timeit`, но в этой задаче достаточно просто обернуть код в цикл и усреднить. + +#### 3. Проведение замеров +Для каждой структуры данных и для каждого режима входных данных (случайный / отсортированный) выполните: + +- А. Вставка всех записей + +Создайте пустую структуру. + +Засеките время, выполните insert для каждой записи из входного списка. + +Зафиксируйте общее время вставки. + +- Б. Поиск 100 случайных записей + +Возьмите 100 случайных имён из того же набора (гарантированно существующих) и 10 имён, которых нет (например, "None_{i}"). + +Засеките время на выполнение всех 110 вызовов find. + +- В. Удаление 50 случайных записей + +Выберите 50 случайных имён из набора. + +Засеките время на выполнение delete для каждого. + + +**!! Важно: после вставки структура остаётся заполненной, поиск и удаление выполняются на ней же. Если нужно повторить замер для другого порядка данных — создавайте новую структуру и заполняйте заново.** + +#### 4. Сохранение результатов + +**!! Каждый эксперимент повторить минимум 5 раз и записывать и среднее время, и все замеры.** + +Соберите все замеры в словарь или список, затем сохраните в CSV-файл: + +```python +import csv + +results = [ + ["Структура", "Режим", "Операция", "Время (сек)"], + ["LinkedList", "случайный", "вставка", 0.123], + ... +] + +with open("results.csv", "w", newline="") as f: + writer = csv.writer(f) + writer.writerows(results) +``` + + +#### 5. Анализ результатов +Постройте график (столбчатая диаграмма или линейный график) — можно в Excel, Google Sheets или с помощью matplotlib в Python. + +Сравните: + +- Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных). + +- Почему хеш-таблица почти не чувствительна к порядку. + +- Почему связный список всегда медленен при поиске. + +- Как удаление работает в каждой структуре. + +* Вывод должен содержать ответ на вопрос: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни.* + +## Задание: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами) + +### Цель работы +Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры. + +### Общая схема приложения (пример) + +```mermaid +classDiagram + class Maze { + -Cell[] cells + -int width, height + -Cell start + -Cell exit + +getCell(x,y): Cell + +getNeighbors(cell): List~Cell~ + } + + class Cell { + -int x, y + -bool isWall + -bool isStart + -bool isExit + +isPassable(): bool + } + + class MazeBuilder { + <> + +buildFromFile(filename): Maze + } + + class TextFileMazeBuilder { + +buildFromFile(filename): Maze + } + + class PathFindingStrategy { + <> + +findPath(maze, start, exit): List~Cell~ + } + + class BFSStrategy + class DFSStrategy + class AStarStrategy + class DijkstraStrategy + + class SearchStats { + +timeMs: float + +visitedCells: int + +pathLength: int + } + + class MazeSolver { + -Maze maze + -PathFindingStrategy strategy + +setStrategy(strategy) + +solve(): SearchStats + } + + class Command { + <> + +execute() + +undo() + } + + class MoveCommand { + -Player player + -Direction dir + -Cell previousCell + +execute() + +undo() + } + + class Player { + -Cell currentCell + +moveTo(cell) + } + + class Observer { + <> + +update(event) + } + + class ConsoleView { + +update(event) + +render(maze, player, path) + } + + MazeBuilder <|.. TextFileMazeBuilder + MazeBuilder --> Maze : creates + PathFindingStrategy <|.. BFSStrategy + PathFindingStrategy <|.. DFSStrategy + PathFindingStrategy <|.. AStarStrategy + PathFindingStrategy <|.. DijkstraStrategy + MazeSolver --> PathFindingStrategy : uses + MazeSolver --> Maze : uses + Command <|.. MoveCommand + MoveCommand --> Player + Player --> Cell + Observer <|.. ConsoleView + MazeSolver --> Observer : notifies +``` + +### Выполнение + +#### Этап 1. Модель лабиринта (без паттернов, просто классы) +**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта. +- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена). +- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` – возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена). + +**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем. + +#### Этап 2. Загрузка лабиринта из файла – применение паттерна **Builder** +**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` – стена, ` ` (пробел) – проход, `S` – старт, `E` – выход. +- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`. +- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`. + +Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`. + +#### Этап 3. Стратегии поиска пути – паттерн **Strategy** +**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода. +- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет. +- Реализовать минимум 3 стратегии: + - **BFS** (поиск в ширину) – гарантирует кратчайший путь по количеству шагов. + - **DFS** (поиск в глубину) – быстрый, но не обязательно кратчайший. + - **A*** (с эвристикой, например, манхэттенское расстояние) – компромисс между скоростью и оптимальностью. + - (Опционально) **Дейкстра** – полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS. + +Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A* – приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток. + +Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс. + +#### Этап 4. Класс-оркестратор – **MazeSolver** (использует Strategy) +**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику. +- `MazeSolver` содержит поля `maze` и `strategy`. +- Метод `setStrategy(strategy)` для динамической смены алгоритма. +- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути). +- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии. + +#### Этап 5. Визуализация и пошаговое управление – паттерны **Observer** и **Command** (по желанию) +**5.1. Наблюдатель (Observer)** – обновление консольного интерфейса. +- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`). +- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли. +- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния. + +**5.2. Команда (Command)** – для пошагового перемещения игрока по найденному пути (или ручного управления). +- Создать интерфейс `Command` с методами `execute()` и `undo()`. +- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены. +- Создать класс `Player`, хранящий текущую клетку. +- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн. + +*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command – для демонстрации undo при ручном исследовании лабиринта.* + +#### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных) +**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности. +1. **Подготовка тестовых лабиринтов:** + - Маленький (10×10) с простым путём. + - Средний (50×50) с тупиками. + - Большой (100×100) с запутанной структурой. + - «Пустой» лабиринт (без стен) – для демонстрации максимальной производительности. + - «Без выхода» – чтобы проверить обработку отсутствия пути. +2. **Замеры:** + - Для каждого лабиринта и каждой стратегии запустить `solve()` 5–10 раз, усреднить время, количество посещённых клеток, длину пути. + - Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`. +3. **Анализ:** + - Построить графики для каждого лабиринта. + - Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях). + +4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото – вес 3, песок – вес 2, асфальт – вес 1) и сравнить Дейкстру с A* на взвешенном графе. + +#### Этап 7. Отчёт +**Структура отчёта:** +1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid). +2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий. +3. Результаты экспериментов (таблицы, графики). +4. Анализ эффективности алгоритмов и применимости паттернов. +5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них. + +### Советы +- Для A* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`. +- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь. +- Для BFS/DFS используй `deque` (очередь) и `list` (стек). +- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки. diff --git a/stepinim/lab1_structure/docs/data/lab1_graph.png b/stepinim/lab1_structure/docs/data/lab1_graph.png new file mode 100644 index 0000000..7989d6b Binary files /dev/null and b/stepinim/lab1_structure/docs/data/lab1_graph.png differ diff --git a/stepinim/lab1_structure/docs/data/lab1_results.csv b/stepinim/lab1_structure/docs/data/lab1_results.csv new file mode 100644 index 0000000..208876b --- /dev/null +++ b/stepinim/lab1_structure/docs/data/lab1_results.csv @@ -0,0 +1,55 @@ +Структура,Режим,Повтор,Операция,Время (сек) +LinkedList,shuffled,1,insert,0.5023735999711789 +LinkedList,shuffled,1,search,0.022223800013307482 +LinkedList,shuffled,1,delete,0.010106799949426204 +LinkedList,shuffled,2,insert,0.5151404999778606 +LinkedList,shuffled,2,search,0.023844500014092773 +LinkedList,shuffled,2,delete,0.010028599994257092 +LinkedList,shuffled,3,insert,0.5328615000471473 +LinkedList,shuffled,3,search,0.020557800016831607 +LinkedList,shuffled,3,delete,0.012162799946963787 +LinkedList,sorted,1,insert,0.4577932999818586 +LinkedList,sorted,1,search,0.017212599981576204 +LinkedList,sorted,1,delete,0.012185800005681813 +LinkedList,sorted,2,insert,0.43183969997335225 +LinkedList,sorted,2,search,0.01829650002764538 +LinkedList,sorted,2,delete,0.012130599992815405 +LinkedList,sorted,3,insert,0.436789300001692 +LinkedList,sorted,3,search,0.017460400005802512 +LinkedList,sorted,3,delete,0.012465099978726357 +HashTable,shuffled,1,insert,0.0032562999986112118 +HashTable,shuffled,1,search,9.469996439293027e-05 +HashTable,shuffled,1,delete,5.15999854542315e-05 +HashTable,shuffled,2,insert,0.0031429000082425773 +HashTable,shuffled,2,search,9.000004502013326e-05 +HashTable,shuffled,2,delete,4.360004095360637e-05 +HashTable,shuffled,3,insert,0.003212600015103817 +HashTable,shuffled,3,search,0.00010830000974237919 +HashTable,shuffled,3,delete,4.650000482797623e-05 +HashTable,sorted,1,insert,0.0030796999926678836 +HashTable,sorted,1,search,8.420000085607171e-05 +HashTable,sorted,1,delete,4.730001091957092e-05 +HashTable,sorted,2,insert,0.0030180999892763793 +HashTable,sorted,2,search,9.079999290406704e-05 +HashTable,sorted,2,delete,5.299999611452222e-05 +HashTable,sorted,3,insert,0.0029779999749734998 +HashTable,sorted,3,search,8.510000770911574e-05 +HashTable,sorted,3,delete,6.589997792616487e-05 +BST,shuffled,1,insert,0.011618499993346632 +BST,shuffled,1,search,0.00031289999606087804 +BST,shuffled,1,delete,0.0002456999500282109 +BST,shuffled,2,insert,0.021565500006545335 +BST,shuffled,2,search,0.00032350001856684685 +BST,shuffled,2,delete,0.0002101999707520008 +BST,shuffled,3,insert,0.011865400010719895 +BST,shuffled,3,search,0.0003497999859973788 +BST,shuffled,3,delete,0.0002114999806508422 +BST,sorted,1,insert,1.961912199971266 +BST,sorted,1,search,0.025325599999632686 +BST,sorted,1,delete,0.03309909999370575 +BST,sorted,2,insert,1.8450072000268847 +BST,sorted,2,search,0.025074300006963313 +BST,sorted,2,delete,0.03284020000137389 +BST,sorted,3,insert,1.8502263000118546 +BST,sorted,3,search,0.028948499995749444 +BST,sorted,3,delete,0.040639499959070235 diff --git a/stepinim/lab1_structure/docs/otchet_1lab.md b/stepinim/lab1_structure/docs/otchet_1lab.md new file mode 100644 index 0000000..6bf4dba --- /dev/null +++ b/stepinim/lab1_structure/docs/otchet_1lab.md @@ -0,0 +1,15 @@ +В ходе экспериментов было показано, что производительность структуры данных сильно зависит +от её внутреннего устройства и характера входных данных. + +BST работает быстро на случайных данных, но при отсортированном порядке деградирует почти до +связного списка, из-за чего время вставки и удаления резко увеличивается. Хеш-таблица +практически не зависит от порядка входных данных, так как доступ к элементам происходит через +хеш-функцию, поэтому она показала лучшие результаты при поиске и вставке. Связный список +оказался самым медленным при поиске, так как требует последовательного обхода элементов. + +Удаление также работает по-разному: в связном списке и BST сначала требуется поиск элемента, +а в хеш-таблице удаление обычно выполняется быстрее за счёт обращения к нужному бакету. + +На практике хеш-таблицы лучше подходят для частого поиска и вставки данных, BST — когда +важно хранить элементы в отсортированном виде, а связные списки полезны в более простых +задачах, где структура данных часто изменяется и не требуется быстрый поиск. \ No newline at end of file diff --git a/stepinim/lab1_structure/test.py b/stepinim/lab1_structure/test.py new file mode 100644 index 0000000..eae9bf8 --- /dev/null +++ b/stepinim/lab1_structure/test.py @@ -0,0 +1,376 @@ +import sys + +sys.setrecursionlimit(30000) # Увеличиваю лимит рекурсии для BST + +# Связный список +def ll_insert(head, name, phone): + new_node = {'name': name, 'phone': phone, 'next': None} # Создаю новый узел + if head is None: # Если список пуст + return new_node # Возвращаю узел как голову + + curr = head # Указатель для обхода + prev = None # Храню предыдущий узел + while curr is not None: # Иду по списку + if curr['name'] == name: # Если нашел такое же имя + curr['phone'] = phone # Обновляю телефон + return head + prev = curr + curr = curr['next'] + prev['next'] = new_node # Добавляю в конец + return head + + +def ll_find(head, name): + curr = head # Начинаю с головы + while curr: # Иду по всему списку + if curr['name'] == name: # Сравниваю имена + return curr['phone'] # Возвращаю телефон + curr = curr['next'] # Перехожу к следующему + return None # Не нашел + + +def ll_delete(head, name): + if head is None: # Пустой список + return None + if head['name'] == name: # Удаляю голову + return head['next'] # Возвращаю второй элемент + curr = head + while curr['next']: # Иду пока есть следующий + if curr['next']['name'] == name: # Нашел элемент для удаления + curr['next'] = curr['next']['next'] # Перепрыгиваю через него + return head + curr = curr['next'] + return head + + +def ll_list_all(head): + result = [] + curr = head + while curr: # Собираю все элементы + result.append((curr['name'], curr['phone'])) + curr = curr['next'] + result.sort(key=lambda x: x[0]) # Сортирую по имени + return result + + +# Хэш-таблица +HASH_SIZE = 1009 # Размер таблицы - простое число + + +def _hash_name(name): + return hash(name) % HASH_SIZE # Беру остаток от деления - это индекс корзины + + +def ht_insert(buckets, name, phone): + idx = _hash_name(name) # Вычисляю индекс корзины + buckets[idx] = ll_insert(buckets[idx], name, phone) # Метод цепочек - вставляю в список + + +def ht_find(buckets, name): + idx = _hash_name(name) # Нахожу корзину + return ll_find(buckets[idx], name) # Ищу в цепочке + + +def ht_delete(buckets, name): + idx = _hash_name(name) # Нахожу корзину + buckets[idx] = ll_delete(buckets[idx], name) # Удаляю из цепочки + + +def ht_list_all(buckets): + all_entries = [] + for bucket in buckets: # Прохожу по всем корзинам + if bucket is not None: + curr = bucket + while curr: # Собираю всю цепочку + all_entries.append((curr['name'], curr['phone'])) + curr = curr['next'] + all_entries.sort(key=lambda x: x[0]) + return all_entries + + +# Двоичное дерево поиска +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): + curr = root + while curr: # Итеративный спуск по дереву + if name == curr['name']: # Нашел + return curr['phone'] + elif name < curr['name']: # Искомое меньше - налево + curr = curr['left'] + else: # Искомое больше - направо + curr = curr['right'] + return None + + +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: # Нет левого ребенка + return root['right'] # Заменяю правым + if root['right'] is None: # Нет правого ребенка + return root['left'] # Заменяю левым + # Есть оба ребенка - ищу минимальный в правом поддереве + min_node = root['right'] + while min_node['left']: # Иду до самого левого + min_node = min_node['left'] + root['name'] = min_node['name'] # Копирую данные преемника + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) # Удаляю преемника + return root + + +def bst_list_all(root): + result = [] + + def inorder(node): # Симметричный обход + if node: + inorder(node['left']) # Сначала левое + result.append((node['name'], node['phone'])) # Потом корень + inorder(node['right']) # Потом правое + + inorder(root) + return result + + +# ============================================================ +# TECT +# ============================================================ + +import os +import random +import time +import csv +import pandas as pd +import matplotlib.pyplot as plt + +# ============================================================ +# ПОДГОТОВКА ПАПОК +# ============================================================ + +DATA_DIR = os.path.join("docs", "data") +os.makedirs(DATA_DIR, exist_ok=True) + +csv_path = os.path.join(DATA_DIR, "lab1_results.csv") +graph_path = os.path.join(DATA_DIR, "lab1_graph.png") + +# ============================================================ +# ТЕСТОВЫЕ ДАННЫЕ +# ============================================================ + +random.seed(42) # Фиксирую seed для повторяемости + +N = 3000 # 3000 записей + +base_records = [ + (f"User_{i:05d}", f"123-{i:05d}") + for i in range(N) +] + +records_shuffled = base_records.copy() +random.shuffle(records_shuffled) # Перемешанный порядок + +records_sorted = sorted(base_records, key=lambda x: x[0]) # Отсортированный порядок + +# Данные для поиска +search_existing = [ + name for name, _ in random.sample(base_records, 100) # 100 существующих имен +] + +search_nonexist = [ + f"None_{i}" + for i in range(10) # 10 несуществующих имен +] + +# Данные для удаления +delete_names = [ + name for name, _ in random.sample(base_records, 50) # 50 имен для удаления +] + + +# ============================================================ +# СОЗДАНИЕ СТРУКТУР +# ============================================================ + +def build_structure(records, struct_type): + if struct_type == "ll": + structure = None + for name, phone in records: + structure = ll_insert(structure, name, phone) # Последовательная вставка + return structure + + elif struct_type == "ht": + structure = [None] * HASH_SIZE + for name, phone in records: + ht_insert(structure, name, phone) # Вставка с хэшированием + return structure + + elif struct_type == "bst": + structure = None + for name, phone in records: + structure = bst_insert(structure, name, phone) # Вставка с ветвлением + return structure + + +# ============================================================ +# INSERT +# ============================================================ + +def measure_insert(records, struct_type): + start = time.perf_counter() + build_structure(records, struct_type) # Замеряю время построения структуры + end = time.perf_counter() + return end - start + + +# ============================================================ +# SEARCH +# ============================================================ + +def measure_search(records, struct_type): + structure = build_structure(records, struct_type) # Строю структуру + start = time.perf_counter() + + if struct_type == "ll": + for name in search_existing + search_nonexist: + ll_find(structure, name) # Поиск перебором + elif struct_type == "ht": + for name in search_existing + search_nonexist: + ht_find(structure, name) # Поиск через хэш + elif struct_type == "bst": + for name in search_existing + search_nonexist: + bst_find(structure, name) # Поиск спуском по дереву + + end = time.perf_counter() + return end - start + + +# ============================================================ +# DELETE +# ============================================================ + +def measure_delete(records, struct_type): + structure = build_structure(records, struct_type) # Строю структуру + start = time.perf_counter() + + if struct_type == "ll": + for name in delete_names: + structure = ll_delete(structure, name) # Удаление со сдвигом + elif struct_type == "ht": + for name in delete_names: + ht_delete(structure, name) # Удаление из цепочки + elif struct_type == "bst": + for name in delete_names: + structure = bst_delete(structure, name) # Удаление с ребалансировкой + + end = time.perf_counter() + return end - start + + +# ============================================================ +# ЗАМЕРЫ +# ============================================================ + +all_data = [] + +experiments = [ + ("LinkedList", "ll"), + ("HashTable", "ht"), + ("BST", "bst") +] + +modes = [ + ("shuffled", records_shuffled), # Тест на случайных данных + ("sorted", records_sorted) # Тест на отсортированных данных +] + +for struct_name, struct_type in experiments: + for mode_name, records in modes: + for rep in range(1, 4): # 3 повтора для усреднения + insert_time = measure_insert(records, struct_type) + search_time = measure_search(records, struct_type) + delete_time = measure_delete(records, struct_type) + + all_data.append([struct_name, mode_name, rep, "insert", insert_time]) + all_data.append([struct_name, mode_name, rep, "search", search_time]) + all_data.append([struct_name, mode_name, rep, "delete", delete_time]) + +# ============================================================ +# CSV +# ============================================================ + +with open(csv_path, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerow(["Структура", "Режим", "Повтор", "Операция", "Время (сек)"]) + writer.writerows(all_data) + +print(f"CSV сохранён: {csv_path}") + +# ============================================================ +# ГРАФИК +# ============================================================ + +df = pd.read_csv(csv_path) + +df_avg = ( + df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"] + .mean() + .reset_index() +) + +fig, ax = plt.subplots(figsize=(12, 6)) + +ops = ["insert", "search", "delete"] +x = range(len(ops)) +width = 0.12 + +configs = [ + ("LinkedList", "shuffled"), + ("LinkedList", "sorted"), + ("HashTable", "shuffled"), + ("HashTable", "sorted"), + ("BST", "shuffled"), + ("BST", "sorted") +] + +for i, (struct, mode) in enumerate(configs): + subset = df_avg[ + (df_avg["Структура"] == struct) & + (df_avg["Режим"] == mode) + ] + times = [ + subset[subset["Операция"] == op]["Время (сек)"].values[0] + for op in ops + ] + ax.bar( + [p + i * width for p in x], + times, + width, + label=f"{struct} ({mode})" + ) + +ax.set_xticks([p + 2.5 * width for p in x]) +ax.set_xticklabels(ops) +ax.set_ylabel("Среднее время (сек)") +ax.set_title("Сравнение структур данных") +ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left") + +plt.tight_layout() +plt.savefig(graph_path) +print(f"График сохранён: {graph_path}") +plt.show() \ No newline at end of file diff --git a/stepinim/lab2_oop/docs/data/chart_time_2lab.png b/stepinim/lab2_oop/docs/data/chart_time_2lab.png new file mode 100644 index 0000000..e2ace45 Binary files /dev/null and b/stepinim/lab2_oop/docs/data/chart_time_2lab.png differ diff --git a/stepinim/lab2_oop/docs/data/empty_2lab.txt b/stepinim/lab2_oop/docs/data/empty_2lab.txt new file mode 100644 index 0000000..db11fb1 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/empty_2lab.txt @@ -0,0 +1,20 @@ +S + + + + + + + + + + + + + + + + + + + E diff --git a/stepinim/lab2_oop/docs/data/large_2lab.txt b/stepinim/lab2_oop/docs/data/large_2lab.txt new file mode 100644 index 0000000..a446b35 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/large_2lab.txt @@ -0,0 +1,100 @@ +#################################################################################################### +#S # # ### ## ## # # ### # # ## # # ### ## #### # ## ## # ##### +# # # # ## ## # # # # # # # # # ##### # ## # # ## # ## # # +## # ## ### ## #### ## # # # ## #### # ## #### ## ### # # # ## # # ## # +### # ## ## ### # # #### # # # # ## ## ###### ## ## # # ### # ### +## # # # ### # # ## ### ### # # ## # # # ## # # ####### # # +###### ### ## # # # # # # ### ### ## ### ### ##### # # ## # # ## ## #### +#### # ## # # ## # ## ## ## # ## # # ## ## # # # # ## ### +# # # # # ###### # # ## #### # # # ## # # ### ##### ## # # +# ### # # # # ## ### # # ## #### # # # ###### # # ###### # ## # +## ## ## ### # # ### # # ## # ## ### ### ## ## # ### # # # +## # ## # # ## # ### # ## # ### ## # ### ### ### # ## ## # ### # # # +# # # # # ### # ## # # # ## # # # # # # # # # # ### ## # # ## # # # +## # # # #### ## # # # # # # ### # # ##### # # # # # # # # # +# ## ## # ## # # #### # # ## #### # ## # # # # # # # # # +# # ### # # # ## # # # # ## ## # # # # ## # # # # ## ## #### # ## # # # +# # ### # # ## # ## #### #### ## # #### # ### ## # # # # ## ## # +##### # # # # # ## # ## # ### # # # # # # # # # # ### ## # ## ## #### # # # +# # # #### ## ## ## # # # # # # # # # ## # # ## # ### # # ## # # # ## +### # ### ####### ### # # # # # ## ## # ## # ## # ## ### +# # ### ######## ### ## # # # # # # ## ## # ## ### # # # ### ### ### +# # ## ## # # ### ### ## # # ## # # # # # ## ## ### # # #### #### ### +# ### ## ### #### # ## # # ## # ## # ## ### # # # # ### # ## ### +#### ## # ### # # ## ### ## # # # # # # # ## # ## ## # # # # ## ## # +### ## # # #### ## ## ##### ## # # # ## # ## # # # # # # # # ## # ## # # +# # # # # # # ## ## # ### # ## ## # ## # # # ## # # ## ## # # # # ## # +# # ###### # # # # # # # # ## ## # # # ## # # # # # # ## # # ## # # ## +# # # # # ## # # # # # # # # ## # ## # #### # ### #### ## # # ## ## +## # # ## # ### # #### ## # ### ## # # # # ### # ## # # # # # +# # ### # ### # ## # ## ## # # # ### ##### ## ### # ## ### # # # ## ###### # +## # # # ## # # #### # # ## # ## # # # # # # # # # # # # # +## # # # ## #### # ### ### ## # ## ##### # # # # # # # # # # ##### # # ## +# ## ## # # # # ### ## # # ### ### ## # # # # ## # # ## ### # # # ### +# ## ## ## ### # # #### # ## ## ## ### ### # # ## ## +## # # ##### #### # # ## ## ### # # # # # # # # # # ### # #### # +# # # # # # #### # # ## # # # ## # # # # # # # ### # # ## +## ### ## ### # ## ## ### # ## # # # # # # # # # # #### ## ## +## # ### ## ## # # ### # # #### # # # ### # # # # # # #### # # +# # # ## ### # # # # # # ## ## # # ## ## # ## # # # # ### ## # # +# ### # ### ## # # # # # # ## # ###### # ## ## ## # ## ## ## # ## # # +# # # # # ## # # # # # # ## # # # ###### # # ### # # +# # ## # # ## # # ## ### # ## ## ## # # # # # # # # # ## # # # +## #### ## ### #### # # ### # # # # # ## # ## # # ##### ## ## # # ## # +## # # # ## # ## # # ## # #### # # ### # #### # ## # ##### # # # # +# # # # # ## #### # # ## # # # # ## # ## ## # # # ## # # ## ## ### # # +# # # ## # ## # # ## ## # ## # # ## # ## # # # ##### # +## #### ## ##### # ### # ### # # ## # ## # # # # # # # ## # ### # ## # +# # # # # ## # ## # ## ## # # ### # ####### # # ## # ### ## ## ## ## ## # +### ## # ## ## # ## # ### ### # ### # # # ###### ### ### # # # ## ### # # +## # ## ## # ## ### # # # # ## # ### # ### ## # ## # # # # # +# # ## ## ## # # # # ## # # ## # ## # # # ## # # # # # # # # ## # # # +# # # ## # ## ## ## #### ## ## ## ### ##### ###### ## ### # # # +# # ## # # # # ## # # # # # ## # #### ## # ## # ### # ## # +# # ## # # # ### # # # # ## ### ## # ## # # # # # ## # # # ### ## # # +# ## # # # ## # # # # # ## # # # # ## # # # # ###### ## ### # # +# ## ## # ### # # ## ####### ## # ####### # # # # ## # ### # ## # # # +## ### ## ### # ## # # ## ### # ### ### # # # ## # # ## # ### # # +# # # ### ## # # ## # ## ## # ### # # # ### ###### # # # # +## # ### ## # ### ## ## # ## # ## ## ### ## ### ### ## ## # ## # +# # #### ## # # ## ## # #### # ### # ## ## # # # # # ## # # +# # # # #### # # # ### ## # ## ## ## # # ## # ##### # # # # # ## # #### # # # +# # ### # # ## #### # # # # ## ## # ## ## ### # ## ## ## # ## # +## # ## # # # # # ## ## # ## ## ## # ## # #### ## # ## # +# # # ## # ## # # # # ### # # ## ## # # # # # ### # ### ## # # ## # +# # # ### # # ## ## # # ## # ### # # ### # ## # ### # ## ## ## # # +### # ## # # #### ### ## # ## # ## # ## #### # # ###### # # #### # ## # +# # # # # # ### # # # # ## #### ### # # # # # ## # # # #### # #### # # +## ## # ## # # # # # ## # ### #### # ## ### # ##### ### ## ### # # # +# # # ####### ## ### # ####### # # # ####### # # # ### # # ## # # # # +## # # ## ## ## # ### ## ## ### ## ## # # ## # ## ## # # # # # +### # # #### ### # # ## # ## # # # #### # # # # # # # # ## # ## # +# ##### # ## ### ## #### # # # ## #### # # # # ## # # ## # # +# # # # # ### # ## #### # # # ### # # # # ## # # # # # # # +# # ## ## #### # # ### ## ## ### ### ## ## # # # # # ## # ## ### # +## # # ## # # ## # ## # ### # ### # # # ## # ### #### # ## # +# ## # # # # ## ## # # # # ## # ### ## ###### ### # ## ### # # # # # # +### # # # # #### # # # # ### ##### # ## # ## # # ## # # # ## # +# # # # ##### # # #### # # # ## # ## # # ## ### # # ## # #### # +### ## # # # ## # # ## # # ## # # # ### ## ## # ### # # ## # # # # # +#### # # # # ## # # # ## ### # # # ### # ## # # ## # # # ## # # +# # # ## # ## # # ## ### # ## ## # ## # ## ## # # # ## # # ### ## ## ## # +# # # ## # # # ## # ## ## # ## # ## ## #### #### # ## # # # +## # # #### ### ## # #### ### ### # ## # # ## ## # ## # ### # +# ## ##### # # ## ### ### # # # # ### #### ## ## ## ## ### ## # # +# # ## # ## ## # # ### # # # # ## # ### ## ## ## ## ## # # # # +# #### # # #### # ## ### ###### # # # ## # # # # # # # # # #### ## # # # # +# #### # ### ##### # ## ## # # ## ## # ## ## # ## ### # # # # # # # +# # ## # # # ## # # ## ## # # # # ## # # # ## ### # ## # # +### # ## # # # ## # # # #### ##### # # ## # ## # # # # ### # # +# # # # ### # ## # # # # ## # # # ### # ## # # # # # # # # # # # # +## # # # # ## ## # #### ## # # ## # # # # # ### # # # #### # # ## ## # # # +## # # ## # #### # # # # ## # ### ## ## #### # ## ## # ## ## #### ## ### #### # +# ### ### ### # ### ### ## # # # ###### # # ### ## # # # # # # # # +## ### ## # ## # ## # ## ### # # # #### ### ### # # +# # ## # # # ## ## # # # # ### ### # ### ### # # # # ### ## # # +## # ## # # # # ###### ## ### # ### # # # ## # ## ## # ### # # # ## # ## # +# ## # ## # # # ## ## # ## ## ## # ## # ## # # # # ## # # # # +# ### # ## ## # #### # ##### # ## ## ## ### # # # # ### # +## # ## # # # # ##### # ## # # # ## # ### # # # ## # ## # # # ## E# +#################################################################################################### diff --git a/stepinim/lab2_oop/docs/data/medium_2lab.txt b/stepinim/lab2_oop/docs/data/medium_2lab.txt new file mode 100644 index 0000000..b6b2a97 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/medium_2lab.txt @@ -0,0 +1,50 @@ +################################################## +#S ## ### # # # #### # ## # +# # ## # # # # ##### ### # ### # # +# ## #### # ### ### ## # # #### ### # # # +#### # # # # # ## # #### +# ## # #### ## # # ## # # # # +# ### # # # # ### # ## +# # # # ## # # ## # ## # # # +## # # # #### # # # # # ### ## +## # # ## ## ### # # ## # +## # # # # # # # # # # # # # +# # ## # ## ## ## ## ## # ### +# ### # # # ## ## # # # # ## # ## +## # # # # ## # # # # # +# # # # # ### # ### # # ## +# # # # # # # ##### # ### ## +# # # # ## ## # # # # ### #### # +## # # # ## # # ## # ### ## ### # # +## # ## # # # # # +# ##### ## ## # # # ## # ## # # # +# # # # ### ##### ### # # ## # +## # # # ## # # ## # # # # ## # +#### # # ## # # # ## ## # ## ## # +# # ### ### ## # ## #### # # +# # ### # ## ##### # # # # ## # # +## #### # # # # # # +# ## # # # # ## ## # # ## # # +# # ## # ### # ### ## # ## # +# # # # # ## ## ## # # +# # ## ### ## ## # # # # # ## # # +## # # #### # #### # ## ## # ## # +# # # ## # # # # # # # # # +# ### ### # # # # # # # # +# ## # # # ####### # # # # # # ### # +## # # # # # # # # # ## # ## # +# # # # ## ## # # ## ### # # # # # # # +# ## # ### # # # # # # # # +# # # # # # # ## # ### # # +# # ### # # # ### # ## # # # +# ### # # # # ## # # ## # # +# ## # ### # ## ## ### # # # # +# ## # # ## ## # # # ## # # +# # ## # # # # # # # # ## # +## # # # # # # # # # # # # +# # # # ### ## ### # ## # # # # +## ##### # # # ## # ## ### # # +# ## # ## ##### # # # ## # +# # # # # # ### # # # # # # +### # # # # # # ## ## ### ## #E# +################################################## diff --git a/stepinim/lab2_oop/docs/data/no_exit_2lab.txt b/stepinim/lab2_oop/docs/data/no_exit_2lab.txt new file mode 100644 index 0000000..9cc457c --- /dev/null +++ b/stepinim/lab2_oop/docs/data/no_exit_2lab.txt @@ -0,0 +1,15 @@ +############### +#S # # +# # +## # # +# # # # +## ## # # # # +# # # # +# # # +# # # ## # +## # # # +## # +## # # # # +# # # ## +# # #E# +############### diff --git a/stepinim/lab2_oop/docs/data/results_2lab.csv b/stepinim/lab2_oop/docs/data/results_2lab.csv new file mode 100644 index 0000000..ba69850 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/results_2lab.csv @@ -0,0 +1,21 @@ +maze,strategy,time_ms,visited,path_length +small,BFS,0.1971,28,15 +small,DFS,0.062,16,15 +small,A*,0.1713,28,15 +small,Dijkstra,0.148,28,15 +medium,BFS,5.3354,1377,95 +medium,DFS,0.7772,282,151 +medium,A*,3.8703,500,95 +medium,Dijkstra,8.3548,1363,95 +large,BFS,16.9817,4391,195 +large,DFS,3.414,614,285 +large,A*,5.7519,559,195 +large,Dijkstra,31.018,4380,195 +empty,BFS,2.3012,400,39 +empty,DFS,1.4237,400,191 +empty,A*,3.6105,400,39 +empty,Dijkstra,2.9606,400,39 +no_exit,BFS,0.5791,136,0 +no_exit,DFS,0.5479,136,0 +no_exit,A*,0.9933,136,0 +no_exit,Dijkstra,0.8121,136,0 diff --git a/stepinim/lab2_oop/docs/data/small_2lab.txt b/stepinim/lab2_oop/docs/data/small_2lab.txt new file mode 100644 index 0000000..9fec091 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/small_2lab.txt @@ -0,0 +1,10 @@ +########## +#S # +# ###### # +# # # # +# # ## # # +# # ## # # +# # # # +# ###### # +# E# +########## diff --git a/stepinim/lab2_oop/docs/otchet_2lab.md b/stepinim/lab2_oop/docs/otchet_2lab.md new file mode 100644 index 0000000..3d7029b --- /dev/null +++ b/stepinim/lab2_oop/docs/otchet_2lab.md @@ -0,0 +1,122 @@ +ОПИСАНИЕ ЗАДАЧИ И ВЫБРАННЫХ ПАТТЕРНОВ + +Цель работы — разработать систему поиска пути в лабиринте с использованием +оопп и паттернов проектирования. + +В работе были использованы следующие паттерны: + +Strategy — для реализации алгоритмов поиска пути (BFS, DFS, A*, Dijkstra). +Позволяет менять алгоритм без изменения кода основного класса MazeSolver. + +Builder — для создания лабиринта из текстового файла. +Отделяет логику загрузки данных от основной системы. +'''mermaid + +classDiagram +class Cell { ++x ++y ++is_wall ++is_start ++is_exit ++weight ++isPassable() +} + +class Maze { ++width ++height ++start ++exit ++getCell() ++getNeighbors() ++getWeightedNeighbors() +} + +class MazeBuilder { ++buildFromFile() +} + +class TextFileMazeBuilder +MazeBuilder <|-- TextFileMazeBuilder + +class PathFindingStrategy { ++findPath() +} + +class BFSStrategy +class DFSStrategy +class AStarStrategy +class DijkstraStrategy + +PathFindingStrategy <|-- BFSStrategy +PathFindingStrategy <|-- DFSStrategy +PathFindingStrategy <|-- AStarStrategy +PathFindingStrategy <|-- DijkstraStrategy + +class MazeSolver { ++setStrategy() ++solve() +} + +MazeSolver --> PathFindingStrategy +Maze --> Cell +''' +ЛИСТИНГИ КЛЮЧЕВЫХ КЛАССОВ + +В проекте реализованы основные классы: +Cell — хранение информации о клетке лабиринта +Maze — структура лабиринта и работа с соседями +MazeSolver — запуск поиска пути +PathFindingStrategy — интерфейс алгоритмов +BFSStrategy, DFSStrategy, AStarStrategy, DijkstraStrategy — реализации алгоритмов +TextFileMazeBuilder — загрузка лабиринта из файла +SearchStats — хранение статистики + +РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ + +Алгоритмы тестировались на разных лабиринтах: small, medium, large, empty, no_exit. + +Сравнивались: + +время выполнения +количество посещённых клеток +длина найденного пути + +Результаты в общем виде: + +BFS — гарантирует кратчайший путь, но посещает много клеток +DFS — быстрый, но не гарантирует оптимальный путь +A* — самый быстрый в большинстве случаев за счёт эвристики +Dijkstra — стабильный, но медленнее A* на больших лабиринтах + +АНАЛИЗ ЭФФЕКТИВНОСТИ И ПАТТЕРНОВ + +Результаты показали, что A* является наиболее эффективным алгоритмом на больших данных, +так как использует эвристику и уменьшает количество проверяемых клеток. + +BFS всегда находит оптимальный путь, но работает медленнее из-за полного обхода пространства. + +DFS быстрее по времени, но не гарантирует лучший результат. + +Dijkstra корректно работает с весами, но в данной задаче часто уступает A*. + +Паттерн Strategy позволил легко переключать алгоритмы без изменения основной логики программы. +Паттерн Builder упростил создание лабиринтов и отделил загрузку данных от логики поиска. + +ВЫВОДЫ + +В ходе работы была создана гибкая система поиска пути в лабиринте с использованием ООП +и паттернов проектирования. Благодаря Strategy алгоритмы стали независимыми и легко +заменяемыми. Благодаря Builder упростилась работа с созданием и загрузкой лабиринтов. +В целом, архитектура получилась расширяемой: можно легко добавить новый алгоритм или тип +лабиринта без переписывания существующего кода. +Таким образом, наиболее сбалансированным алгоритмом для поиска пути в лабиринте является A*, +так как он обеспечивает: + +высокую скорость работы, +оптимальность результата, +минимальное количество исследуемых состояний. + +Алгоритмы BFS и Dijkstra гарантируют оптимальность, но проигрывают по производительности, +а DFS является самым быстрым, но не гарантирует качество решения. \ No newline at end of file diff --git a/stepinim/lab2_oop/poisk.py b/stepinim/lab2_oop/poisk.py new file mode 100644 index 0000000..d284267 --- /dev/null +++ b/stepinim/lab2_oop/poisk.py @@ -0,0 +1,571 @@ +import time +from collections import deque +import heapq +import csv +import os +import random +import matplotlib.pyplot as plt + + +# ============================================================ +# ЭТАП 1. МОДЕЛЬ ЛАБИРИНТА +# ============================================================ + +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 + self.weight = 1 # Вес клетки (нужен для Дейкстры) + + # Можно ли пройти через клетку + def isPassable(self): + return not self.is_wall + + def __repr__(self): + return f"Cell({self.x},{self.y})" + + # Хеш по координатам — чтобы класть клетки в set и dict + def __hash__(self): + return hash((self.x, self.y)) + + # Сравнение двух клеток (нужно для set и dict) + def __eq__(self, other): + return isinstance(other, Cell) and self.x == other.x and self.y == other.y + + +class Maze: + def __init__(self, width, height): + self.width = width + self.height = height + self.cells = [] # Двумерный список: cells[y][x] + self.start = None + self.exit = None + + # Получить клетку по координатам, если она в границах лабиринта + def getCell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + # Получить всех соседей клетки (вверх, вниз, влево, вправо), кроме стен + def getNeighbors(self, cell): + neighbors = [] + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: # Четыре направления + nx = cell.x + dx + ny = cell.y + dy + neighbor = self.getCell(nx, ny) + if neighbor and neighbor.isPassable(): + neighbors.append(neighbor) + return neighbors + + # То же самое, но возвращает пары (сосед, вес) — для Дейкстры + def getWeightedNeighbors(self, cell): + return [(n, n.weight) for n in self.getNeighbors(cell)] + + +# ============================================================ +# ЭТАП 2. ЗАГРУЗКА ЛАБИРИНТА ИЗ ФАЙЛА +# ============================================================ + +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] + + height = len(lines) + width = max(len(line) for line in lines) # Берём самую длинную строку + maze = Maze(width, height) + + # Разбираем каждый символ в клетку + for y, line in enumerate(lines): + row = [] + for x, char in enumerate(line): + if char == '#': + cell = Cell(x, y, is_wall=True) # Стена + elif char == 'S': + cell = Cell(x, y, is_start=True) + maze.start = cell # Запомнили старт + elif char == 'E': + cell = Cell(x, y, is_exit=True) + maze.exit = cell # Запомнили выход + else: + cell = Cell(x, y) # Пустая клетка + row.append(cell) + + # Если строка короче ширины — добиваем стенами + while len(row) < width: + row.append(Cell(len(row), y, is_wall=True)) + maze.cells.append(row) + + # Проверяем, что старт и выход есть + if maze.start is None or maze.exit is None: + raise ValueError("В лабиринте нет S или E") + return maze + + +# ============================================================ +# ВОССТАНОВЛЕНИЕ ПУТИ ПО СЛОВАРЮ РОДИТЕЛЕЙ +# ============================================================ + +def reconstruct_path(parents, end_cell): + path = [] + current = end_cell + # Идём от выхода к старту по цепочке parents + while current is not None: + path.append(current) + current = parents[current] + path.reverse() # Разворачиваем — получаем путь от старта к выходу + return path + + +# ============================================================ +# ЭТАП 3. АЛГОРИТМЫ ПОИСКА ПУТИ +# ============================================================ + +class PathFindingStrategy: + @property + def name(self): + return "Unknown" + + def findPath(self, maze, start, exit): + raise NotImplementedError + + +# ============================================================ +# BFS — обход в ширину (очередь) +# ============================================================ +class BFSStrategy(PathFindingStrategy): + @property + def name(self): + return "BFS" + + def findPath(self, maze, start, exit): + queue = deque([start]) # Очередь: кто первый зашёл — первый вышел + visited = {start} + parents = {start: None} # Откуда пришли в клетку + visited_count = 1 + + while queue: + current = queue.popleft() # Берём из начала очереди + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor in maze.getNeighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parents[neighbor] = current + visited_count += 1 + queue.append(neighbor) # Кладём в конец очереди + return [], visited_count + + +# ============================================================ +# DFS — обход в глубину (стек) +# ============================================================ +class DFSStrategy(PathFindingStrategy): + @property + def name(self): + return "DFS" + + def findPath(self, maze, start, exit): + stack = [start] # Стек: кто последний зашёл — первый вышел + visited = {start} + parents = {start: None} + visited_count = 1 + + while stack: + current = stack.pop() # Берём с вершины стека + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor in maze.getNeighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parents[neighbor] = current + visited_count += 1 + stack.append(neighbor) # Кладём на вершину стека + return [], visited_count + + +# ============================================================ +# A* — поиск с подсказкой (эвристикой) +# ============================================================ +class AStarStrategy(PathFindingStrategy): + @property + def name(self): + return "A*" + + # Подсказка: примерное расстояние до выхода (по прямой) + def heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def findPath(self, maze, start, exit): + counter = 0 # Чтобы различать клетки с одинаковым приоритетом + open_set = [] # Куча: всегда берём самую перспективную клетку + heapq.heappush(open_set, (0, counter, start)) + parents = {start: None} + g_score = {start: 0} # Пройденное расстояние от старта + visited = set() + visited_count = 0 + + while open_set: + _, _, current = heapq.heappop(open_set) # Достаём клетку с лучшей оценкой + if current in visited: + continue + visited.add(current) + visited_count += 1 + + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor in maze.getNeighbors(current): + tentative_g = g_score[current] + 1 # Расстояние до соседа через текущую + if neighbor not in g_score or tentative_g < g_score[neighbor]: + g_score[neighbor] = tentative_g + parents[neighbor] = current + # Оценка клетки = пройденный путь + подсказка до выхода + f_score = tentative_g + self.heuristic(neighbor, exit) + counter += 1 + heapq.heappush(open_set, (f_score, counter, neighbor)) + return [], visited_count + + +# ============================================================ +# ДЕЙКСТРА — поиск с учётом весов клеток +# ============================================================ +class DijkstraStrategy(PathFindingStrategy): + @property + def name(self): + return "Dijkstra" + + def findPath(self, maze, start, exit): + counter = 0 + queue = [] # Куча: всегда берём клетку с кратчайшим путём от старта + heapq.heappush(queue, (0, counter, start)) + distances = {start: 0} # Кратчайшее известное расстояние до каждой клетки + parents = {start: None} + visited = set() + visited_count = 0 + + while queue: + dist, _, current = heapq.heappop(queue) # Достаём ближайшую клетку + if current in visited: + continue + visited.add(current) + visited_count += 1 + + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + # Здесь используем вес клеток, а не просто +1 + for neighbor, weight in maze.getWeightedNeighbors(current): + new_dist = dist + weight + if neighbor not in distances or new_dist < distances[neighbor]: + distances[neighbor] = new_dist + parents[neighbor] = current + counter += 1 + heapq.heappush(queue, (new_dist, counter, neighbor)) + return [], visited_count + + +# ============================================================ +# ЭТАП 4. РЕШАТЕЛЬ И СТАТИСТИКА +# ============================================================ + +class SearchStats: + def __init__(self, strategy_name, time_ms, visited_cells, path_length, path_found): + self.strategy_name = strategy_name + self.time_ms = time_ms # Время в миллисекундах + self.visited_cells = visited_cells # Сколько клеток посетили + self.path_length = path_length # Длина найденного пути + self.path_found = path_found # Нашли путь или нет + + +class MazeSolver: + def __init__(self, maze, strategy=None): + self.maze = maze + self.strategy = strategy + + # Сменить алгоритм поиска + def setStrategy(self, strategy): + self.strategy = strategy + + def solve(self): + if self.strategy is None: + raise ValueError("Стратегия не выбрана") + + # Засекаем время и запускаем алгоритм + start_time = time.perf_counter() + path, visited = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit) + end_time = time.perf_counter() + elapsed_ms = (end_time - start_time) * 1000 + + return SearchStats( + self.strategy.name, + elapsed_ms, + visited, + len(path), + len(path) > 0 + ), path + + +# ============================================================ +# ВЫВОД ЛАБИРИНТА В КОНСОЛЬ +# ============================================================ + +def render(maze, path=None): + path_set = set(path) if path else set() # Для быстрой проверки "клетка на пути?" + + for y in range(maze.height): + line = "" + for x in range(maze.width): + cell = maze.getCell(x, y) + if cell == maze.start: + line += "S" + elif cell == maze.exit: + line += "E" + elif cell in path_set: + line += "." # Точка — клетка пути + elif cell.is_wall: + line += "#" + else: + line += " " + print(line) + print() + + +# ============================================================ +# ПУТИ ДЛЯ СОХРАНЕНИЯ ФАЙЛОВ +# ============================================================ + +OUTPUT_DIR = os.path.join("docs", "data") +PREFIX = "_2lab" +os.makedirs(OUTPUT_DIR, exist_ok=True) # Создаём папку, если её нет + + +def get_path(filename): + name, ext = os.path.splitext(filename) + return os.path.join(OUTPUT_DIR, f"{name}{PREFIX}{ext}") + + +# ============================================================ +# СОЗДАНИЕ ЛАБИРИНТА ИЗ СПИСКА СТРОК +# ============================================================ + +def create_test_maze(filename, lines): + with open(filename, 'w', encoding='utf-8') as f: + for line in lines: + f.write(line + '\n') + return filename + + +# ============================================================ +# ГЕНЕРАЦИЯ ЛАБИРИНТОВ +# ============================================================ + +# Случайный лабиринт с гарантированным путём +def generate_maze(width, height, wall_density=0.3): + grid = [[' ' for _ in range(width)] for _ in range(height)] + + # Ставим стены по краям + for x in range(width): + grid[0][x] = '#' + grid[height - 1][x] = '#' + for y in range(height): + grid[y][0] = '#' + grid[y][width - 1] = '#' + + # Прокладываем гарантированную дорожку от (1,1) до (width-2, height-2) + x, y = 1, 1 + path_cells = {(x, y)} + while x < width - 2 or y < height - 2: + if x < width - 2 and random.random() > 0.3: + x += 1 + elif y < height - 2: + y += 1 + else: + x += 1 + path_cells.add((x, y)) + + # Случайно расставляем стены, но не на дорожке + for yy in range(1, height - 1): + for xx in range(1, width - 1): + if (xx, yy) not in path_cells: + if random.random() < wall_density: + grid[yy][xx] = '#' + + # Ставим старт и выход по углам + grid[1][1] = 'S' + grid[height - 2][width - 2] = 'E' + return [''.join(row) for row in grid] + + +# Пустой лабиринт без стен +def generate_empty_maze(size): + lines = [" " * size for _ in range(size)] + lines[0] = "S" + " " * (size - 1) + lines[size - 1] = " " * (size - 1) + "E" + return lines + + +# Лабиринт, где выход замурован со всех сторон +def generate_no_exit_maze(size): + lines = generate_maze(size, size, wall_density=0.2) + for y, line in enumerate(lines): + if 'E' in line: + x = line.index('E') + # Окружаем выход стенами + for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + ny, nx = y + dy, x + dx + if 0 <= ny < size and 0 <= nx < size: + if lines[ny][nx] == ' ': + lines[ny] = lines[ny][:nx] + '#' + lines[ny][nx + 1:] + return lines + + +# ============================================================ +# ЗАПУСК ЭКСПЕРИМЕНТОВ +# ============================================================ + +def run_experiments(): + # Набор лабиринтов для тестов + mazes = { + "small": [ + "##########", + "#S #", + "# ###### #", + "# # # #", + "# # ## # #", + "# # ## # #", + "# # # #", + "# ###### #", + "# E#", + "##########" + ], + "medium": generate_maze(50, 50, 0.35), + "large": generate_maze(100, 100, 0.4), + "empty": generate_empty_maze(20), + "no_exit": generate_no_exit_maze(15) + } + + # Список алгоритмов + strategies = [ + BFSStrategy(), + DFSStrategy(), + AStarStrategy(), + DijkstraStrategy() + ] + + results = [] + + print("=" * 60) + print("ЭКСПЕРИМЕНТЫ") + print("=" * 60) + + for maze_name, lines in mazes.items(): + filename = get_path(f"{maze_name}.txt") + create_test_maze(filename, lines) + maze = TextFileMazeBuilder().buildFromFile(filename) + + print(f"\nЛабиринт: {maze_name}") + print("-" * 60) + + for strategy in strategies: + times = [] + visited_values = [] + final_path_len = 0 + + # Запускаем 5 раз и считаем среднее время + for _ in range(5): + solver = MazeSolver(maze) + solver.setStrategy(strategy) + stats, path = solver.solve() + times.append(stats.time_ms) + visited_values.append(stats.visited_cells) + final_path_len = stats.path_length + + avg_time = sum(times) / len(times) + avg_visited = sum(visited_values) / len(visited_values) + + results.append({ + "maze": maze_name, + "strategy": strategy.name, + "time_ms": round(avg_time, 4), + "visited": int(avg_visited), + "path_length": final_path_len + }) + + status = "найден" if final_path_len > 0 else "не найден" + print(f"{strategy.name:<10} | {avg_time:>8.4f} мс | {int(avg_visited):>5} клеток | путь {status}") + + # Сохраняем всё в CSV + csv_path = get_path("results.csv") + with open(csv_path, "w", newline="", encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=["maze", "strategy", "time_ms", "visited", "path_length"]) + writer.writeheader() + writer.writerows(results) + + print(f"\nCSV сохранён: {csv_path}") + return results + + +# ============================================================ +# ПОСТРОЕНИЕ ГРАФИКА +# ============================================================ + +def build_charts(results): + mazes = list(dict.fromkeys(r["maze"] for r in results)) # Список лабиринтов без повторов + strategies = list(dict.fromkeys(r["strategy"] for r in results)) # Список стратегий без повторов + + fig, ax = plt.subplots(figsize=(12, 6)) + x = range(len(mazes)) + width = 0.2 # Ширина одного столбика + + # Цвета для каждого алгоритма + colors = {'BFS': '#3498db', 'DFS': '#e74c3c', 'A*': '#2ecc71', 'Dijkstra': '#f39c12'} + + for i, strategy in enumerate(strategies): + # Берём время этой стратегии для всех лабиринтов + times = [r["time_ms"] for r in results if r["strategy"] == strategy] + # Рисуем столбики рядом друг с другом + ax.bar([j + i * width for j in x], times, width, label=strategy, color=colors.get(strategy, 'gray')) + + ax.set_xlabel("Лабиринт") + ax.set_ylabel("Время (мс)") + ax.set_title("Сравнение алгоритмов") + ax.set_xticks([j + width * 1.5 for j in x]) # Подписи по центру группы + ax.set_xticklabels(mazes) + ax.legend() + ax.grid(axis='y', alpha=0.3) + + plt.tight_layout() + chart_path = get_path("chart_time.png") + plt.savefig(chart_path, dpi=150, bbox_inches='tight') + print(f"График сохранён: {chart_path}") + plt.show() + + +# ============================================================ +# ГЛАВНАЯ ФУНКЦИЯ +# ============================================================ + +def main(): + results = run_experiments() + build_charts(results) + + +if __name__ == "__main__": + main() \ No newline at end of file