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