Merge pull request '[2] lab2' (#264) from konnovaea/2026-rff_mp:lab2 into develop

Reviewed-on: #264
This commit is contained in:
VladimirGub 2026-05-30 11:24:46 +00:00
commit 8e0b2b71a2
31 changed files with 2020 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,109 @@
Структура, Режим, Операция, Замер, Время (сек)
LinkedList,случайный,вставка,1,0.011328999999932421
LinkedList,случайный,вставка,2,0.0023913999993965263
LinkedList,случайный,вставка,3,0.0017174000004160916
LinkedList,случайный,вставка,4,0.0017204000005222042
LinkedList,случайный,вставка,5,0.0016142999993462581
LinkedList,случайный,вставка,среднее,0.0037544999999227003
LinkedList,случайный,поиск,1,7.3999999585794285e-06
LinkedList,случайный,поиск,2,1.1699999959091656e-05
LinkedList,случайный,поиск,3,8.099999831756577e-06
LinkedList,случайный,поиск,4,5.899999450775795e-06
LinkedList,случайный,поиск,5,1.500000053056283e-05
LinkedList,случайный,поиск,среднее,9.619999946153258e-06
LinkedList,случайный,удаление,1,5.199999577598646e-06
LinkedList,случайный,удаление,2,3.4000004234258085e-06
LinkedList,случайный,удаление,3,3.9000005926936865e-06
LinkedList,случайный,удаление,4,4.399999852466863e-06
LinkedList,случайный,удаление,5,2.2599999283556826e-05
LinkedList,случайный,удаление,среднее,7.899999945948367e-06
HashTable,случайный,вставка,1,0.013529500000004191
HashTable,случайный,вставка,2,0.017691199999717355
HashTable,случайный,вставка,3,0.016795400000773952
HashTable,случайный,вставка,4,0.015214900000501075
HashTable,случайный,вставка,5,0.012209399999846937
HashTable,случайный,вставка,среднее,0.015088080000168702
HashTable,случайный,поиск,1,0.00028960000054212287
HashTable,случайный,поиск,2,0.0001171000003523659
HashTable,случайный,поиск,3,0.00013169999965612078
HashTable,случайный,поиск,4,0.00011999999969702912
HashTable,случайный,поиск,5,0.00016460000006190967
HashTable,случайный,поиск,среднее,0.00016460000006190967
HashTable,случайный,удаление,1,0.0001094999997803825
HashTable,случайный,удаление,2,0.00011030000041500898
HashTable,случайный,удаление,3,6.83999996908824e-05
HashTable,случайный,удаление,4,6.479999956354732e-05
HashTable,случайный,удаление,5,0.0001382000000376138
HashTable,случайный,удаление,среднее,9.8239999897487e-05
BST,случайный,вставка,1,0.02586410000003525
BST,случайный,вставка,2,0.023826999999982945
BST,случайный,вставка,3,0.028718300000036834
BST,случайный,вставка,4,0.02642329999980575
BST,случайный,вставка,5,0.026569300000119256
BST,случайный,вставка,среднее,0.026280399999996006
BST,случайный,поиск,1,0.00024870000015653204
BST,случайный,поиск,2,0.00022480000006908085
BST,случайный,поиск,3,0.00033259999963775044
BST,случайный,поиск,4,0.00025629999981902074
BST,случайный,поиск,5,0.00023359999977401458
BST,случайный,поиск,среднее,0.00025919999989127974
BST,случайный,удаление,1,0.00018809999983204762
BST,случайный,удаление,2,0.00015689999963797163
BST,случайный,удаление,3,0.00014709999959450215
BST,случайный,удаление,4,0.0001754000004439149
BST,случайный,удаление,5,0.00018170000021200394
BST,случайный,удаление,среднее,0.00016983999994408806
LinkedList,отсортированный,вставка,1,0.0013518000005205977
LinkedList,отсортированный,вставка,2,0.0014992999995229184
LinkedList,отсортированный,вставка,3,0.0033320000002277084
LinkedList,отсортированный,вставка,4,0.001253299999916635
LinkedList,отсортированный,вставка,5,0.0013355999999475898
LinkedList,отсортированный,вставка,среднее,0.0017544000000270898
LinkedList,отсортированный,поиск,1,6.299999768089037e-06
LinkedList,отсортированный,поиск,2,5.800000508315861e-06
LinkedList,отсортированный,поиск,3,5.699999746866524e-06
LinkedList,отсортированный,поиск,4,5.500000042957254e-06
LinkedList,отсортированный,поиск,5,1.9600000086938962e-05
LinkedList,отсортированный,поиск,среднее,8.580000030633528e-06
LinkedList,отсортированный,удаление,1,2.8000004022032954e-06
LinkedList,отсортированный,удаление,2,4.300000000512227e-06
LinkedList,отсортированный,удаление,3,2.6999996407539584e-06
LinkedList,отсортированный,удаление,4,2.499999936844688e-06
LinkedList,отсортированный,удаление,5,2.4000000848900527e-06
LinkedList,отсортированный,удаление,среднее,2.9400000130408445e-06
HashTable,отсортированный,вставка,1,0.013422199999695295
HashTable,отсортированный,вставка,2,0.011119499999949767
HashTable,отсортированный,вставка,3,0.01018590000057884
HashTable,отсортированный,вставка,4,0.011275699999714561
HashTable,отсортированный,вставка,5,0.010843500000191852
HashTable,отсортированный,вставка,среднее,0.011369360000026063
HashTable,отсортированный,поиск,1,0.0001083999995898921
HashTable,отсортированный,поиск,2,0.00013240000043879263
HashTable,отсортированный,поиск,3,0.0002434999996694387
HashTable,отсортированный,поиск,4,0.0001129000002038083
HashTable,отсортированный,поиск,5,0.0001036000003296067
HashTable,отсортированный,поиск,среднее,0.0001401600000463077
HashTable,отсортированный,удаление,1,5.670000064128544e-05
HashTable,отсортированный,удаление,2,7.49000000723754e-05
HashTable,отсортированный,удаление,3,5.3699999625678174e-05
HashTable,отсортированный,удаление,4,5.450000026030466e-05
HashTable,отсортированный,удаление,5,5.409999994299142e-05
HashTable,отсортированный,удаление,среднее,5.878000010852702e-05
BST,отсортированный,вставка,1,5.166896599999745
BST,отсортированный,вставка,2,5.045173700000305
BST,отсортированный,вставка,3,4.877277200000208
BST,отсортированный,вставка,4,4.796063099999628
BST,отсортированный,вставка,5,4.7685291000007055
BST,отсортированный,вставка,среднее,4.930787940000118
BST,отсортированный,поиск,1,0.05183889999989333
BST,отсортированный,поиск,2,0.04380440000022645
BST,отсортированный,поиск,3,0.044272600000113016
BST,отсортированный,поиск,4,0.04941080000025977
BST,отсортированный,поиск,5,0.04630559999986872
BST,отсортированный,поиск,среднее,0.04712646000007226
BST,отсортированный,удаление,1,0.023101800000404182
BST,отсортированный,удаление,2,0.026490100000046368
BST,отсортированный,удаление,3,0.02241980000053445
BST,отсортированный,удаление,4,0.020923000000038883
BST,отсортированный,удаление,5,0.022132500000225264
BST,отсортированный,удаление,среднее,0.02301344000024983
1 Структура Режим Операция Замер Время (сек)
2 LinkedList случайный вставка 1 0.011328999999932421
3 LinkedList случайный вставка 2 0.0023913999993965263
4 LinkedList случайный вставка 3 0.0017174000004160916
5 LinkedList случайный вставка 4 0.0017204000005222042
6 LinkedList случайный вставка 5 0.0016142999993462581
7 LinkedList случайный вставка среднее 0.0037544999999227003
8 LinkedList случайный поиск 1 7.3999999585794285e-06
9 LinkedList случайный поиск 2 1.1699999959091656e-05
10 LinkedList случайный поиск 3 8.099999831756577e-06
11 LinkedList случайный поиск 4 5.899999450775795e-06
12 LinkedList случайный поиск 5 1.500000053056283e-05
13 LinkedList случайный поиск среднее 9.619999946153258e-06
14 LinkedList случайный удаление 1 5.199999577598646e-06
15 LinkedList случайный удаление 2 3.4000004234258085e-06
16 LinkedList случайный удаление 3 3.9000005926936865e-06
17 LinkedList случайный удаление 4 4.399999852466863e-06
18 LinkedList случайный удаление 5 2.2599999283556826e-05
19 LinkedList случайный удаление среднее 7.899999945948367e-06
20 HashTable случайный вставка 1 0.013529500000004191
21 HashTable случайный вставка 2 0.017691199999717355
22 HashTable случайный вставка 3 0.016795400000773952
23 HashTable случайный вставка 4 0.015214900000501075
24 HashTable случайный вставка 5 0.012209399999846937
25 HashTable случайный вставка среднее 0.015088080000168702
26 HashTable случайный поиск 1 0.00028960000054212287
27 HashTable случайный поиск 2 0.0001171000003523659
28 HashTable случайный поиск 3 0.00013169999965612078
29 HashTable случайный поиск 4 0.00011999999969702912
30 HashTable случайный поиск 5 0.00016460000006190967
31 HashTable случайный поиск среднее 0.00016460000006190967
32 HashTable случайный удаление 1 0.0001094999997803825
33 HashTable случайный удаление 2 0.00011030000041500898
34 HashTable случайный удаление 3 6.83999996908824e-05
35 HashTable случайный удаление 4 6.479999956354732e-05
36 HashTable случайный удаление 5 0.0001382000000376138
37 HashTable случайный удаление среднее 9.8239999897487e-05
38 BST случайный вставка 1 0.02586410000003525
39 BST случайный вставка 2 0.023826999999982945
40 BST случайный вставка 3 0.028718300000036834
41 BST случайный вставка 4 0.02642329999980575
42 BST случайный вставка 5 0.026569300000119256
43 BST случайный вставка среднее 0.026280399999996006
44 BST случайный поиск 1 0.00024870000015653204
45 BST случайный поиск 2 0.00022480000006908085
46 BST случайный поиск 3 0.00033259999963775044
47 BST случайный поиск 4 0.00025629999981902074
48 BST случайный поиск 5 0.00023359999977401458
49 BST случайный поиск среднее 0.00025919999989127974
50 BST случайный удаление 1 0.00018809999983204762
51 BST случайный удаление 2 0.00015689999963797163
52 BST случайный удаление 3 0.00014709999959450215
53 BST случайный удаление 4 0.0001754000004439149
54 BST случайный удаление 5 0.00018170000021200394
55 BST случайный удаление среднее 0.00016983999994408806
56 LinkedList отсортированный вставка 1 0.0013518000005205977
57 LinkedList отсортированный вставка 2 0.0014992999995229184
58 LinkedList отсортированный вставка 3 0.0033320000002277084
59 LinkedList отсортированный вставка 4 0.001253299999916635
60 LinkedList отсортированный вставка 5 0.0013355999999475898
61 LinkedList отсортированный вставка среднее 0.0017544000000270898
62 LinkedList отсортированный поиск 1 6.299999768089037e-06
63 LinkedList отсортированный поиск 2 5.800000508315861e-06
64 LinkedList отсортированный поиск 3 5.699999746866524e-06
65 LinkedList отсортированный поиск 4 5.500000042957254e-06
66 LinkedList отсортированный поиск 5 1.9600000086938962e-05
67 LinkedList отсортированный поиск среднее 8.580000030633528e-06
68 LinkedList отсортированный удаление 1 2.8000004022032954e-06
69 LinkedList отсортированный удаление 2 4.300000000512227e-06
70 LinkedList отсортированный удаление 3 2.6999996407539584e-06
71 LinkedList отсортированный удаление 4 2.499999936844688e-06
72 LinkedList отсортированный удаление 5 2.4000000848900527e-06
73 LinkedList отсортированный удаление среднее 2.9400000130408445e-06
74 HashTable отсортированный вставка 1 0.013422199999695295
75 HashTable отсортированный вставка 2 0.011119499999949767
76 HashTable отсортированный вставка 3 0.01018590000057884
77 HashTable отсортированный вставка 4 0.011275699999714561
78 HashTable отсортированный вставка 5 0.010843500000191852
79 HashTable отсортированный вставка среднее 0.011369360000026063
80 HashTable отсортированный поиск 1 0.0001083999995898921
81 HashTable отсортированный поиск 2 0.00013240000043879263
82 HashTable отсортированный поиск 3 0.0002434999996694387
83 HashTable отсортированный поиск 4 0.0001129000002038083
84 HashTable отсортированный поиск 5 0.0001036000003296067
85 HashTable отсортированный поиск среднее 0.0001401600000463077
86 HashTable отсортированный удаление 1 5.670000064128544e-05
87 HashTable отсортированный удаление 2 7.49000000723754e-05
88 HashTable отсортированный удаление 3 5.3699999625678174e-05
89 HashTable отсортированный удаление 4 5.450000026030466e-05
90 HashTable отсортированный удаление 5 5.409999994299142e-05
91 HashTable отсортированный удаление среднее 5.878000010852702e-05
92 BST отсортированный вставка 1 5.166896599999745
93 BST отсортированный вставка 2 5.045173700000305
94 BST отсортированный вставка 3 4.877277200000208
95 BST отсортированный вставка 4 4.796063099999628
96 BST отсортированный вставка 5 4.7685291000007055
97 BST отсортированный вставка среднее 4.930787940000118
98 BST отсортированный поиск 1 0.05183889999989333
99 BST отсортированный поиск 2 0.04380440000022645
100 BST отсортированный поиск 3 0.044272600000113016
101 BST отсортированный поиск 4 0.04941080000025977
102 BST отсортированный поиск 5 0.04630559999986872
103 BST отсортированный поиск среднее 0.04712646000007226
104 BST отсортированный удаление 1 0.023101800000404182
105 BST отсортированный удаление 2 0.026490100000046368
106 BST отсортированный удаление 3 0.02241980000053445
107 BST отсортированный удаление 4 0.020923000000038883
108 BST отсортированный удаление 5 0.022132500000225264
109 BST отсортированный удаление среднее 0.02301344000024983

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@ -0,0 +1,259 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d7f65344",
"metadata": {},
"source": [
"# Отчёт \n",
"## Телефонный справочник: реализация и сравнение структур данных\n",
"\n",
"**Студент:** Коннова Е.А.\n",
"**Группа:** 429\n",
"**Дата:** 12.05.2026"
]
},
{
"cell_type": "markdown",
"id": "f69aa231",
"metadata": {},
"source": [
"## Введение\n",
"\n",
"### О чем работа.\n",
"В данной работе рассматриваются три базовые структуры данных:\n",
"- Связный список (LinkedList)\n",
"- Хеш-таблица (HashTable)\n",
"- Двоичное дерево поиска (BST)\n",
"\n",
"Они применяются для хранения записей телефонного справочника.\n",
"\n",
"### Цель всей работы\n",
"Реализовать три структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций (вставка, поиск, удаление).\n",
"\n",
"### Задачи по достижению цели\n",
"1. Реализовать связный список с операциями insert, find, delete, list_all\n",
"2. Реализовать хеш-таблицу на основе связных списков\n",
"3. Реализовать двоичное дерево поиска\n",
"4. Сгенерировать тестовые данные (10000 записей)\n",
"5. Провести замеры времени для каждой структуры (5 повторений)\n",
"6. Сравнить результаты и сделать выводы"
]
},
{
"cell_type": "markdown",
"id": "56e2f617",
"metadata": {},
"source": [
"## Часть 1. Общая информация о структурах данных\n",
"\n",
"### 1.1 Для неспециалистов\n",
"\n",
"**Что такое структура данных?**\n",
"Это способ организации и хранения данных в компьютере.\n",
"\n",
"**Три структуры из работы:**\n",
"\n",
"| Структура | Как работает | Пример из жизни |\n",
"|-----------|--------------|-----------------|\n",
"| Связный список | Цепочка элементов, где каждый знает следующий | Верёвка с узелками |\n",
"| Хеш-таблица | Массив корзин, элемент попадает в корзину по номеру | Картотека с ящиками |\n",
"| Двоичное дерево | Иерархическая структура: левые меньше, правые больше | Телефонный справочник |\n",
"\n",
"### 1.2 Обзор технологий\n",
"\n",
"**Связный список**\n",
"- Узел: `{'name': str, 'phone': str, 'next': None}`\n",
"- Вставка: O(1) в начало, O(n) в конец\n",
"- Поиск: O(n) - линейный обход\n",
"- Удаление: O(n) - сначала найти\n",
"\n",
"**Хеш-таблица**\n",
"- Корзины: список из None или голов списков\n",
"- Хеш-функция: `hash = (hash * 31 + ord(ch)) % size`\n",
"- Вставка/поиск/удаление: O(1) в среднем\n",
"\n",
"**Двоичное дерево поиска (BST)**\n",
"- Узел: `{'name': str, 'phone': str, 'left': None, 'right': None}`\n",
"- Вставка/поиск/удаление: O(log n) в среднем, O(n) в худшем\n",
"\n",
"### 1.3 Обоснование выбора подхода\n",
"\n",
"**Почему именно эти структуры?**\n",
"1. Они фундаментальны и изучаются в курсе\n",
"2. Показывают разные компромиссы (скорость vs порядок)\n",
"3. Позволяют наглядно сравнить производительность\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "d9327709",
"metadata": {},
"source": [
"## Часть 2. Техническая реализация\n",
"\n",
"### 2.1 Постановка задачи\n",
"\n",
"Реализовать телефонный справочник с операциями:\n",
"- `insert(name, phone)` — добавить или обновить запись\n",
"- `find(name)` — вернуть телефон или None\n",
"- `delete(name)` — удалить запись\n",
"- `list_all()` — вернуть все записи, отсортированные по имени\n",
"\n",
"### 2.2 Верхнеуровневое решение\n",
"\n",
"**Связный список (LinkedList)**\n",
"- `ll_insert(head, name, phone)` — добавление в конец, возвращает голову\n",
"- `ll_find(head, name)` — линейный поиск, возвращает телефон или None\n",
"- `ll_delete(head, name)` — удаление с перепривязкой, возвращает голову\n",
"- `ll_list_all(head)` — сбор всех записей и сортировка\n",
"\n",
"**Хеш-таблица (HashTable)**\n",
"- `hash_function(name, size)` — ключ → номер корзины\n",
"- `ht_create(size)` — создание таблицы\n",
"- `ht_insert(buckets, name, phone)` — вызов ll_insert для нужной корзины\n",
"- `ht_find(buckets, name)` — вызов ll_find для нужной корзины\n",
"- `ht_delete(buckets, name)` — вызов ll_delete для нужной корзины\n",
"- `ht_list_all(buckets)` — сбор из всех корзин + сортировка\n",
"\n",
"**Двоичное дерево (BST)**\n",
"- `bst_insert(root, name, phone)` — итеративная вставка\n",
"- `bst_find(root, name)` — поиск\n",
"- `bst_delete(root, name)` — удаление с поиском преемника\n",
"- `bst_list_all(root)` — in-order обход (уже отсортировано)"
]
},
{
"cell_type": "markdown",
"id": "c1cd08d8",
"metadata": {},
"source": [
"## Часть 3. Эксперименты и результаты\n",
"\n",
"### 3.1 Инструменты и методика\n",
"\n",
"**Параметры эксперимента:**\n",
"- Количество записей: 10 000\n",
"- Количество повторений: 5\n",
"- Поиск: 100 существующих + 10 несуществующих\n",
"- Удаление: 50 случайных записей\n",
"- Режимы: случайный порядок, отсортированный порядок\n",
"\n",
"### 3.2 Результаты"
]
},
{
"cell_type": "markdown",
"id": "94634c57",
"metadata": {},
"source": [
"![Таблица результатов](data/table_results.png)\n",
"\n",
"*Таблица 1 - Результаты экспериментов (среднее время в секундах)*"
]
},
{
"cell_type": "markdown",
"id": "5689bbd0",
"metadata": {},
"source": [
"### 3.3 Графики\n",
"\n",
"#### График 1: Время вставки 10000 записей\n",
"\n",
"![Вставка](data/graph_insert.png)\n",
"\n",
"#### График 2: Время поиска 110 записей\n",
"\n",
"![Поиск](data/graph_search.png)\n",
"\n",
"#### График 3: Время удаления 50 записей\n",
"\n",
"![Удаление](data/graph_delete.png)"
]
},
{
"cell_type": "markdown",
"id": "5561d9dd",
"metadata": {},
"source": [
"### 3.4 Сравнение и анализ\n",
"\n",
"**Как порядок входных данных влияет на BST?**\n",
"\n",
"| Режим | Вставка | Поиск | Удаление |\n",
"|-------|---------|-------|----------|\n",
"| Случайный | 0.026 сек | 0.00026 сек | 0.00017 сек |\n",
"| Отсортированный | 4.931 сек | 0.047 сек | 0.023 сек |\n",
"\n",
"Вывод: На случайных данных BST работает быстро (O(log n)). На отсортированных данных BST вырождается в связный список (O(n)). Работает медленее в 190 раз.\n",
"\n",
"**Техническая ошибка:** Из-за ограничения глубины рекурсии в Python (1000 вызовов) рекурсивная реализация BST не смогла бы обработать 10000 записей. Поэтому все операции BST были реализованы итеративно.\n",
"\n",
"**Почему хеш-таблица не чувствительна к порядку?**\n",
"\n",
"Хеш-функция распределяет записи по корзинам независимо от порядка вставки. Распределение по корзинам равномерное.\n",
"\n",
"**Почему связный список всегда медленен при поиске?**\n",
"\n",
"Связный список не имеет индексов. Поэтому нужно перебирать элементы последовательно. Сложность поиска - O(n).\n",
"\n",
"**Как удаление работает в каждой структуре?**\n",
"\n",
"| Структура | Сложность |\n",
"|-----------|-----------|\n",
"| LinkedList | O(n) |\n",
"| HashTable | O(1) |\n",
"| BST | O(log n) / O(n) |\n",
"\n",
"В связных списках сначала нужно найти, потом перепривязать. В хеш-таблице сразу находишь корзину по хешу. В двоичном дереве нужно найти узел и перестроить поддеревья."
]
},
{
"cell_type": "markdown",
"id": "a57d1502",
"metadata": {},
"source": [
"## Заключение\n",
"\n",
"### Выводы из каждой части\n",
"\n",
"**Из части 1:**\n",
"- Каждая структура имеет свои теоретические характеристики\n",
"- Связный список - прост, но медленен\n",
"- Хеш-таблица - быстра, но не сохраняет порядок\n",
"- BST - быстр и сохраняет порядок, но требует балансировки\n",
"\n",
"**Из части 2:**\n",
"- Все три структуры успешно реализованы\n",
"- Хеш-таблица использует связный список для корзин\n",
"- BST написан итеративно для избежания RecursionError\n",
"\n",
"**Из части 3:**\n",
"- Эксперименты подтвердили теоретические оценки\n",
"- BST на отсортированных данных деградирует\n",
"- Хеш-таблица стабильна независимо от порядка\n",
"\n",
"### Итоговая рекомендация\n",
"\n",
"| Сценарий | Рекомендация | Причина |\n",
"|----------|--------------|---------|\n",
"| Частый поиск по ключу | Хеш-таблица | O(1) |\n",
"| Частые вставки/удаления | Хеш-таблица | Стабильная скорость |\n",
"| Нужен отсортированный вывод | Сбалансированное дерево | In-order обход |\n",
"| Данные поступают отсортированно | Хеш-таблица | BST деградирует |\n",
"| Мало данных (<100) | Любая | Разница незаметна |\n",
"\n",
"**Для телефонного справочника лучший выбор - хеш-таблица.**"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,282 @@
import random
import time
import csv
import os
from lab1.phonebook import *
def generate_test_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:07d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def get_random_names(records, n=100):
return[name for name, _ in random.sample(records, min(n, len(records)))]
def run_linked_experiments(records, mode_name):
print(f"\n связный список ({mode_name}):")
print("вставка 10000 записей:")
insert_times = []
for run in range(5):
start = time.perf_counter()
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
end = time.perf_counter()
insert_times.append(end - start)
print(f"Вставка {run+1}/5: {insert_times[-1]:.6f} сек")
avg_insert = sum(insert_times) / 5
print(f"среднее: {avg_insert:.6f} сек")
print("поиск 110 записей:")
exist_names = get_random_names(records, 100)
non_exist_names = [f"None_{i}" for i in range(10)]
find_times = []
for run in range(5):
start = time.perf_counter()
for name in exist_names:
ll_find(head, name)
for name in non_exist_names:
ll_find(head, name)
end = time.perf_counter()
find_times.append(end - start)
print(f"поиск {run+1}/5: {find_times[-1]:.6f} сек")
avg_find = sum(find_times) / 5
print(f"среднее: {avg_find:.6f} сек")
print("удаление 50 случайных записей:")
to_delete = get_random_names(records,50)
delete_times = []
for run in range(5):
current_head = head
start = time.perf_counter()
for name in to_delete:
current_head = ll_delete(current_head, name)
end = time.perf_counter()
delete_times.append(end - start)
print(f"удаление {run+1}/5: {delete_times[-1]:.6f} сек")
avg_delete = sum(delete_times) / 5
print(f"среднее: {avg_delete:.6f} сек")
return{
'structure': 'LinkedList',
'mode': mode_name,
'insert_avg': avg_insert,
'insert_all': insert_times,
'find_avg': avg_find,
'find_all': find_times,
'delete_avg': avg_delete,
'delete_all': delete_times
}
def run_hash_experiments(records, mode_name):
print(f"\n хеш-таблица({mode_name})")
print("вставка 10000 записей:")
insert_times = []
for run in range(5):
start = time.perf_counter()
buckets = ht_create(1000)
for name, phone in records:
buckets = ht_insert(buckets, name, phone)
end = time.perf_counter()
insert_times.append(end - start)
print(f"Вставка {run+1}/5: {insert_times[-1]:.6f} сек")
avg_insert = sum(insert_times) / 5
print(f"среднее: {avg_insert:.6f} сек")
print("поиск 110 записей:")
exist_names = get_random_names(records, 100)
non_exist_names = [f"None_{i}" for i in range(10)]
find_times = []
for run in range(5):
start = time.perf_counter()
for name in exist_names:
ht_find(buckets, name)
for name in non_exist_names:
ht_find(buckets, name)
end = time.perf_counter()
find_times.append(end - start)
print(f"поиск {run+1}/5: {find_times[-1]:.6f} сек")
avg_find = sum(find_times) / 5
print(f"среднее: {avg_find:.6f} сек")
print("удаление 50 случайных записей:")
to_delete = get_random_names(records,50)
delete_times = []
for run in range(5):
current_buckets = buckets.copy()
start = time.perf_counter()
for name in to_delete:
current_buckets = ht_delete(current_buckets, name)
end = time.perf_counter()
delete_times.append(end - start)
print(f"удаление {run+1}/5: {delete_times[-1]:.6f} сек")
avg_delete = sum(delete_times) / 5
print(f"среднее: {avg_delete:.6f} сек")
return{
'structure': 'HashTable',
'mode': mode_name,
'insert_avg': avg_insert,
'insert_all': insert_times,
'find_avg': avg_find,
'find_all': find_times,
'delete_avg': avg_delete,
'delete_all': delete_times
}
def run_bst_experiments(records, mode_name):
print(f"\n двоичное дерево({mode_name})")
print("вставка 10000 записей:")
insert_times = []
for run in range(5):
start = time.perf_counter()
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
end = time.perf_counter()
insert_times.append(end - start)
print(f"Вставка {run+1}/5: {insert_times[-1]:.6f} сек")
avg_insert = sum(insert_times) / 5
print(f"среднее: {avg_insert:.6f} сек")
print("поиск 110 записей:")
exist_names = get_random_names(records, 100)
non_exist_names = [f"None_{i}" for i in range(10)]
find_times = []
for run in range(5):
start = time.perf_counter()
for name in exist_names:
bst_find(root, name)
for name in non_exist_names:
bst_find(root, name)
end = time.perf_counter()
find_times.append(end - start)
print(f"поиск {run+1}/5: {find_times[-1]:.6f} сек")
avg_find = sum(find_times) / 5
print(f"среднее: {avg_find:.6f} сек")
print("удаление 50 случайных записей:")
to_delete = get_random_names(records,50)
delete_times = []
for run in range(5):
current_root = root
start = time.perf_counter()
for name in to_delete:
current_root = bst_delete(current_root, name)
end = time.perf_counter()
delete_times.append(end - start)
print(f"удаление {run+1}/5: {delete_times[-1]:.6f} сек")
avg_delete = sum(delete_times) / 5
print(f"среднее: {avg_delete:.6f} сек")
return{
'structure': 'BST',
'mode': mode_name,
'insert_avg': avg_insert,
'insert_all': insert_times,
'find_avg': avg_find,
'find_all': find_times,
'delete_avg': avg_delete,
'delete_all': delete_times
}
def save_results_to_csv(all_results):
os.makedirs("docs/data", exist_ok=True)
with open("docs/data/results.csv", "w", encoding="utf-8") as f:
f.write("Структура, Режим, Операция, Замер, Время (сек)\n")
for res in all_results:
struct = res['structure']
mode = res['mode']
for i, t in enumerate(res['insert_all']):
f.write(f"{struct},{mode},вставка,{i+1},{t}\n")
f.write(f"{struct},{mode},вставка,среднее,{res['insert_avg']}\n")
for i, t in enumerate(res['find_all']):
f.write(f"{struct},{mode},поиск,{i+1},{t}\n")
f.write(f"{struct},{mode},поиск,среднее,{res['find_avg']}\n")
for i, t in enumerate(res['delete_all']):
f.write(f"{struct},{mode},удаление,{i+1},{t}\n")
f.write(f"{struct},{mode},удаление,среднее,{res['delete_avg']}\n")
def main():
print("эксперименты по замеру производительности")
records_shuffled, records_sorted = generate_test_data(10000)
all_results = []
print("режим: случайный порядок")
all_results.append(run_linked_experiments(records_shuffled, "случайный"))
all_results.append(run_hash_experiments(records_shuffled, "случайный"))
all_results.append(run_bst_experiments(records_shuffled, "случайный"))
print("режим: отсортированный порядок")
all_results.append(run_linked_experiments(records_sorted, "отсортированный"))
all_results.append(run_hash_experiments(records_sorted, "отсортированный"))
all_results.append(run_bst_experiments(records_sorted, "отсортированный"))
save_results_to_csv(all_results)
if __name__== "__main__":
main()

View File

@ -0,0 +1,123 @@
import matplotlib.pyplot as plt
import numpy as np
import os
os.makedirs('docs/data', exist_ok=True)
structures = ['LinkedList', 'HashTable', 'BST']
random_insert = [0.0037545, 0.015088, 0.026280]
sorted_insert = [0.0017544, 0.011369, 4.930788]
random_search = [0.00000962, 0.0001646, 0.0002592]
sorted_search = [0.00000858, 0.00014016, 0.047126]
random_delete = [0.0000079, 0.00009824, 0.00016984]
sorted_delete = [0.00000294, 0.00005878, 0.023013]
x = np.arange(len(structures))
width = 0.35
#график вставка
fig, ax = plt.subplots(figsize=(12, 7))
bars1 = ax.bar(x - width/2, random_insert, width, label='Случайный порядок', color='#3498db')
bars2 = ax.bar(x + width/2, sorted_insert, width, label='Отсортированный порядок', color='#e74c3c')
for bar in bars1:
height = bar.get_height()
ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
for bar in bars2:
height = bar.get_height()
if height < 1:
ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
else:
ax.annotate(f'{height:.1f} сек', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 5), textcoords="offset points", ha='center', va='bottom', fontsize=10, fontweight='bold')
ax.set_ylabel('Время (сек)', fontsize=12)
ax.set_title('Время вставки 10000 записей', fontsize=14, fontweight='bold')
ax.set_xticks(x)
ax.set_xticklabels(structures, fontsize=11)
ax.legend(fontsize=11)
ax.set_yscale('log')
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('docs/data/graph_insert.png', dpi=150, bbox_inches='tight')
plt.close()
# график поиск
fig, ax = plt.subplots(figsize=(12, 7))
bars1 = ax.bar(x - width/2, random_search, width, label='Случайный порядок', color='#3498db')
bars2 = ax.bar(x + width/2, sorted_search, width, label='Отсортированный порядок', color='#e74c3c')
for bar in bars1:
height = bar.get_height()
ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
for bar in bars2:
height = bar.get_height()
if height < 0.01:
ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
else:
ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
ax.set_ylabel('Время (сек)', fontsize=12)
ax.set_title('Время поиска 110 записей', fontsize=14, fontweight='bold')
ax.set_xticks(x)
ax.set_xticklabels(structures, fontsize=11)
ax.legend(fontsize=11)
ax.set_yscale('log')
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('docs/data/graph_search.png', dpi=150, bbox_inches='tight')
plt.close()
# график удаление
fig, ax = plt.subplots(figsize=(12, 7))
bars1 = ax.bar(x - width/2, random_delete, width, label='Случайный порядок', color='#3498db')
bars2 = ax.bar(x + width/2, sorted_delete, width, label='Отсортированный порядок', color='#e74c3c')
for bar in bars1:
height = bar.get_height()
ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
for bar in bars2:
height = bar.get_height()
if height < 0.01:
ax.annotate(f'{height:.6f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
else:
ax.annotate(f'{height:.4f}', xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=9)
ax.set_ylabel('Время (сек)', fontsize=12)
ax.set_title('Время удаления 50 записей', fontsize=14, fontweight='bold')
ax.set_xticks(x)
ax.set_xticklabels(structures, fontsize=11)
ax.legend(fontsize=11)
ax.set_yscale('log')
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('docs/data/graph_delete.png', dpi=150, bbox_inches='tight')
plt.close()

View File

@ -0,0 +1,34 @@
import matplotlib.pyplot as plt
import os
os.makedirs('docs/data', exist_ok=True)
data = [
['LinkedList', 'случайный', 0.0037545, 0.00000962, 0.0000079],
['HashTable', 'случайный', 0.015088, 0.0001646, 0.00009824],
['BST', 'случайный', 0.026280, 0.0002592, 0.00016984],
['LinkedList', 'отсортированный', 0.0017544, 0.00000858, 0.00000294],
['HashTable', 'отсортированный', 0.011369, 0.00014016, 0.00005878],
['BST', 'отсортированный', 4.930788, 0.047126, 0.023013],
]
fig, ax = plt.subplots(figsize=(12, 5))
ax.axis('tight')
ax.axis('off')
columns = ['Структура', 'Режим', 'Вставка (10000)', 'Поиск (110)', 'Удаление (50)']
table = ax.table(cellText=data, colLabels=columns, loc='center', cellLoc='center')
table.auto_set_font_size(False)
table.set_fontsize(10)
table.scale(1.2, 1.5)
for i, row in enumerate(data):
if row[0] == 'BST' and row[2] > 1:
table[(i+1, 2)].set_facecolor('#ffcccc')
table[(i+1, 2)].set_text_props(weight='bold')
plt.title('Результаты экспериментов (среднее время в секундах)', fontsize=14, fontweight='bold', pad=20)
plt.savefig('docs/data/table_results.png', dpi=200, bbox_inches='tight', facecolor='white')
plt.close()

195
konnovaea/lab1/phonebook.py Normal file
View File

@ -0,0 +1,195 @@
def ll_insert(head, name, phone):
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
current = head
while current['next'] is not None:
current = current['next']
current['next'] = new_node
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
total = 0
for ch in name:
total = (total*31 + ord(ch)) % table_size
return total
def ht_create(size=1000):
return [None]*size
def ht_insert(buckets, name, phone):
idx = hash_function(name, len(buckets))
buckets[idx] = ll_insert(buckets[idx], name, phone)
return buckets
def ht_find(buckets, name):
idx = hash_function(name, len(buckets))
return ll_find(buckets[idx], name)
def ht_delete(buckets, name):
idx = hash_function(name, len(buckets))
buckets[idx] = ll_delete(buckets[idx], name)
return buckets
def ht_list_all(buckets):
records = []
for bucket in buckets:
current = bucket
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def bst_insert(root, name, phone):
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = new_node
break
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = new_node
break
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def _bst_find_min(node):
current = node
while current['left'] is not None:
current = current['left']
return current
def bst_delete(root, name):
if root is None:
return None
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None and current['right'] is None:
if parent is None:
return None
if parent['left'] == current:
parent['left'] = None
else:
parent['right'] = None
return root
if current['left'] is None:
child = current['right']
elif current['right'] is None:
child = current['left']
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
return root
def bst_list_all(root):
records = []
def inorder(node):
if node is None:
return
inorder(node['left'])
records.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return records

20
konnovaea/lab2/dead.txt Normal file
View File

@ -0,0 +1,20 @@
####################
#S #
# #
# #
# #
# ######### #
# # #
# # #
# # #
# # #
# # #
# # #
# # #
# # #
# # #
# #
# #
# #
# E#
####################

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,13 @@
Лабиринт,Стратегия,Время(мс),Посещено клеток,Длина пути
Простой,BFS,0.02,11.0,6.0
Простой,DFS,0.012,9.0,8.0
Простой,A*,0.02,9.0,6.0
С тупиками,BFS,0.492,306.0,35.0
С тупиками,DFS,0.234,198.0,81.0
С тупиками,A*,0.456,225.0,35.0
Пустой,BFS,3.486,2304.0,95.0
Пустой,DFS,10.452,2304.0,1129.0
Пустой,A*,5.743,2304.0,95.0
Без выхода,BFS,0.01,1.0,нет пути
Без выхода,DFS,0.003,1.0,нет пути
Без выхода,A*,0.004,1.0,нет пути
1 Лабиринт Стратегия Время(мс) Посещено клеток Длина пути
2 Простой BFS 0.02 11.0 6.0
3 Простой DFS 0.012 9.0 8.0
4 Простой A* 0.02 9.0 6.0
5 С тупиками BFS 0.492 306.0 35.0
6 С тупиками DFS 0.234 198.0 81.0
7 С тупиками A* 0.456 225.0 35.0
8 Пустой BFS 3.486 2304.0 95.0
9 Пустой DFS 10.452 2304.0 1129.0
10 Пустой A* 5.743 2304.0 95.0
11 Без выхода BFS 0.01 1.0 нет пути
12 Без выхода DFS 0.003 1.0 нет пути
13 Без выхода A* 0.004 1.0 нет пути

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,237 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "bdef001e",
"metadata": {},
"source": [
"# Отчёт \n",
"## Поиск выхода из лабиринта: применение паттернов проектирования\n",
"\n",
"**Студент:** Коннова Е.А.\n",
"**Группа:** 429\n",
"**Дата:** 22.05.2026"
]
},
{
"cell_type": "markdown",
"id": "21f948a4",
"metadata": {},
"source": [
"## Введение\n",
"\n",
"### О чём это работа\n",
"В данной работе реализуется программа для поиска выхода из лабиринта с применением паттернов проектирования. Поддерживаются три алгоритма поиска пути: BFS, DFS и A*.\n",
"\n",
"### Цель работы\n",
"Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. Применить минимум 3 паттерна проектирования.\n",
"\n",
"### Задачи\n",
"1. Реализовать модель лабиринта (классы Cell, Maze)\n",
"2. Реализовать загрузку лабиринта из файла (паттерн Builder)\n",
"3. Реализовать алгоритмы поиска пути (паттерн Strategy): BFS, DFS, A*\n",
"4. Реализовать класс-оркестратор MazeSolver со сбором статистики\n",
"5. Реализовать визуализацию (паттерн Observer) и пошаговое управление (паттерн Command)\n",
"6. Провести эксперименты на лабиринтах разной сложности\n",
"7. Сравнить результаты и сделать выводы\n"
]
},
{
"cell_type": "markdown",
"id": "cf1dc2ba",
"metadata": {},
"source": [
"## Часть 1. Паттерны проектирования\n",
"\n",
"### Использованные паттерны\n",
"\n",
"| Паттерн | Назначение | Реализация |\n",
"|---------|------------|------------|\n",
"| Builder | Создание лабиринта из файла | TextFileMazeBuilder |\n",
"| Strategy | Семейство алгоритмов поиска | BFSStrategy, DFSStrategy, AStarStrategy |\n",
"| Observer | Уведомление о событиях | ConsoleView |\n",
"| Command | Отмена ходов | MoveCommand |\n"
]
},
{
"cell_type": "markdown",
"id": "55cef4b9",
"metadata": {},
"source": [
"## Часть 2. Реализация\n",
"\n",
"### 2.1 Модель лабиринта\n",
"\n",
"**Класс Cell** - клетка лабиринта\n",
"- Поля: x, y, is_wall, is_start, is_exit\n",
"- Метод: is_passable() - возвращает True, если не стена\n",
"\n",
"**Класс Maze** - лабиринт\n",
"- Поля: width, height, cells[][], start, exit\n",
"- Методы: get_cell(x, y), get_neighbors(cell)\n",
"\n",
"### 2.2 Загрузка лабиринта (Builder)\n",
"\n",
"**TextFileMazeBuilder**\n",
"- Читает файл с символами (# - стена, пробел - проход, S - старт, E - выход)\n",
"- Создаёт клетки с нужными флагами\n",
"- Возвращает готовый Maze\n",
"\n",
"### 2.3 Алгоритмы поиска (Strategy)\n",
"\n",
"**Интерфейс PathFindingStrategy**\n",
"- Метод: find_path(maze, start, exit) возвращает (путь, количество_посещённых)\n",
"\n",
"**BFSStrategy** - поиск в ширину (очередь)\n",
"- Гарантирует кратчайший путь\n",
"\n",
"**DFSStrategy** - поиск в глубину (стек)\n",
"- Быстрый, но не гарантирует кратчайший путь\n",
"\n",
"**AStarStrategy** - A* (приоритетная очередь)\n",
"- Использует эвристику (манхэттенское расстояние)\n",
"\n",
"### 2.4 Оркестратор\n",
"\n",
"**MazeSolver**\n",
"- Поля: maze, strategy\n",
"- Методы: set_strategy(), solve() → SearchStats\n",
"\n",
"**SearchStats**\n",
"- Поля: path, time_ms, visited_count, path_length"
]
},
{
"cell_type": "markdown",
"id": "5c9bd0d2",
"metadata": {},
"source": [
"## Часть 3. Эксперименты\n",
"\n",
"### 3.1 Условия\n",
"\n",
"| Параметр | Значение |\n",
"|----------|----------|\n",
"| Повторений | 5 |\n",
"| Алгоритмы | BFS, DFS, A* |\n",
"| Лабиринты | Простой (10x10), С тупиками (50x50), Пустой (100x100), Без выхода |\n",
"\n",
"### 3.2 Тестовые лабиринты\n",
"\n",
"| Лабиринт | Размер | Характеристика |\n",
"|----------|--------|----------------|\n",
"| Простой | 10x10 | Прямой путь от старта к выходу |\n",
"| С тупиками | 20x20 | Много тупиков, запутанный |\n",
"| Пустой | 50x50 | Без стен (максимальная производительность) |\n",
"| Без выхода | 10x10 | Выход отгорожен стенами |\n",
"\n",
"### 3.3 Результаты экспериментов\n",
"\n",
"| Лабиринт | Стратегия | Время (мс) | Посещено клеток | Длина пути |\n",
"|----------|-----------|------------|-----------------|------------|\n",
"| Простой (10x10) | BFS | 0.020 | 11 | 6 |\n",
"| Простой (10x10) | DFS | 0.012 | 9 | 8 |\n",
"| Простой (10x10) | A* | 0.020 | 9 | 6 |\n",
"| С тупиками (20x20) | BFS | 0.492 | 306 | 35 |\n",
"| С тупиками (20x20) | DFS | 0.234 | 198 | 81 |\n",
"| С тупиками (20x20) | A* | 0.456 | 225 | 35 |\n",
"| Пустой (50x50) | BFS | 3.486 | 2304 | 95 |\n",
"| Пустой (50x50) | DFS | 10.452 | 2304 | 1129 |\n",
"| Пустой (50x50) | A* | 5.743 | 2304 | 95 |\n",
"| Без выхода | BFS | 0.010 | 1 | нет пути |\n",
"| Без выхода | DFS | 0.003 | 1 | нет пути |\n",
"| Без выхода | A* | 0.004 | 1 | нет пути |\n",
"\n",
"### 3.4 Графики\n",
"\n",
"#### Простой лабиринт (10x10)\n",
"\n",
"![Время](data/simple_time_graph.png)\n",
"![Посещено](data/simple_visited_graph.png)\n",
"![Длина пути](data/simple_path_graph.png)\n",
"\n",
"#### Лабиринт с тупиками (20x20)\n",
"\n",
"![Время](data/dead_time_graph.png)\n",
"![Посещено](data/dead_visited_graph.png)\n",
"![Длина пути](data/dead_path_graph.png)\n",
"\n",
"#### Пустой лабиринт (50x50)\n",
"\n",
"![Время](data/empty_time_graph.png)\n",
"![Посещено](data/empty_visited_graph.png)\n",
"![Длина пути](data/empty_path_graph.png)\n",
"\n",
"#### Лабиринт без выхода\n",
"\n",
"![Время](data/noexit_time_graph.png)\n",
"![Посещено](data/noexit_visited_graph.png)\n",
"\n",
"### 3.5 Общая таблица результатов\n",
"\n",
"![Таблица](data/maze_table_results.png)\n",
"\n",
"### 3.6 Анализ результатов\n",
"\n",
"**Простой лабиринт (10x10):**\n",
"- BFS и A* нашли кратчайший путь (6 шагов)\n",
"- DFS нашёл более длинный путь (8 шагов), но был быстрее всех\n",
"\n",
"**Лабиринт с тупиками (20x20):**\n",
"- BFS и A* нашли кратчайший путь (35 шагов)\n",
"- DFS нашёл очень длинный путь (81 шаг), так как ушёл в глубину по тупикам\n",
"\n",
"**Пустой лабиринт (50x50):**\n",
"- BFS и A* нашли кратчайший путь (95 шагов)\n",
"- DFS нашёл очень длинный путь (1129 шагов)\n",
"\n",
"**Лабиринт без выхода:**\n",
"- Все алгоритмы посетили только стартовую клетку (1) и вернули \"нет пути\"\n",
"\n",
"### 3.7 Сравнение алгоритмов\n",
"\n",
"| Алгоритм | Кратчайший путь | Скорость | Память | Когда использовать |\n",
"|----------|-----------------|----------|--------|-------------------|\n",
"| BFS | Да | Средняя | Много | Нужен гарантированно кратчайший путь |\n",
"| DFS | Нет | Быстрая | Мало | Важна скорость, не важна длина пути |\n",
"| A* | Да | Быстрая | Средне | Большие лабиринты, есть эвристика |\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "e687a8ee",
"metadata": {},
"source": [
"## Заключение\n",
"\n",
"### Выводы\n",
"\n",
"1. **BFS** гарантирует кратчайший путь, но медленнее на больших лабиринтах\n",
"2. **DFS** самый быстрый, но путь может быть очень длинным\n",
"3. **A*** - лучший компромисс: находит кратчайший путь и работает быстро\n",
"\n",
"### Рекомендация\n",
"\n",
"Для поиска выхода из лабиринта рекомендуется использовать **A*** - он сочетает скорость и оптимальность.\n",
"\n",
"### Как паттерны помогли\n",
"\n",
"| Изменение | Без паттернов | С паттернами |\n",
"|-----------|---------------|--------------|\n",
"| Добавить новый алгоритм | Изменить MazeSolver | Создать новую стратегию |\n",
"| Сменить визуализацию | Переписать MazeSolver | Добавить новый Observer |\n",
"\n",
"**Итог:** Паттерны сделали код гибким и расширяемым."
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

50
konnovaea/lab2/empty.txt Normal file
View File

@ -0,0 +1,50 @@
##################################################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##################################################

View File

@ -0,0 +1,162 @@
import matplotlib.pyplot as plt
import os
os.makedirs('lab2/docs/data', exist_ok=True)
algorithms = ['BFS', 'DFS', 'A*']
simple_time = [0.020, 0.012, 0.020]
simple_visited = [11, 9, 9]
simple_path = [6, 8, 6]
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, simple_time, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Время (мс)')
ax.set_title('Время выполнения (простой лабиринт 10x10)')
for bar, val in zip(bars, simple_time):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.001, f'{val:.3f}', ha='center', va='bottom')
plt.savefig('docs/data/simple_time_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, simple_visited, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Количество клеток')
ax.set_title('Посещённые клетки (простой лабиринт)')
for bar, val in zip(bars, simple_visited):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3, str(val), ha='center', va='bottom')
plt.savefig('docs/data/simple_visited_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, simple_path, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Длина пути (шагов)')
ax.set_title('Длина найденного пути (простой лабиринт)')
for bar, val in zip(bars, simple_path):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3, str(val), ha='center', va='bottom')
plt.savefig('docs/data/simple_path_graph.png', dpi=150, bbox_inches='tight')
plt.close()
dead_time = [0.492, 0.234, 0.456]
dead_visited = [306, 198, 225]
dead_path = [35, 81, 35]
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, dead_time, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Время (мс)')
ax.set_title('Время выполнения (лабиринт с тупиками)')
for bar, val in zip(bars, dead_time):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, f'{val:.3f}', ha='center', va='bottom')
plt.savefig('docs/data/dead_time_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, dead_visited, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Количество клеток')
ax.set_title('Посещённые клетки (лабиринт с тупиками)')
for bar, val in zip(bars, dead_visited):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 10, str(val), ha='center', va='bottom')
plt.savefig('docs/data/dead_visited_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, dead_path, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Длина пути (шагов)')
ax.set_title('Длина найденного пути (лабиринт с тупиками)')
for bar, val in zip(bars, dead_path):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 3, str(val), ha='center', va='bottom')
plt.savefig('docs/data/dead_path_graph.png', dpi=150, bbox_inches='tight')
plt.close()
empty_time = [3.486, 10.452, 5.743]
empty_visited = [2304, 2304, 2304]
empty_path = [95, 1129, 95]
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, empty_time, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Время (мс)')
ax.set_title('Время выполнения (пустой лабиринт 50x50)')
for bar, val in zip(bars, empty_time):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.3, f'{val:.3f}', ha='center', va='bottom')
plt.savefig('docs/data/empty_time_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, empty_visited, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Количество клеток')
ax.set_title('Посещённые клетки (пустой лабиринт)')
for bar, val in zip(bars, empty_visited):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50, str(val), ha='center', va='bottom')
plt.savefig('docs/data/empty_visited_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, empty_path, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Длина пути (шагов)')
ax.set_title('Длина найденного пути (пустой лабиринт)')
for bar, val in zip(bars, empty_path):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50, str(val), ha='center', va='bottom')
plt.savefig('docs/data/empty_path_graph.png', dpi=150, bbox_inches='tight')
plt.close()
noexit_time = [0.010, 0.003, 0.004]
noexit_visited = [1, 1, 1]
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, noexit_time, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Время (мс)')
ax.set_title('Время выполнения (лабиринт без выхода)')
for bar, val in zip(bars, noexit_time):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.0005, f'{val:.3f}', ha='center', va='bottom')
plt.savefig('docs/data/noexit_time_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(algorithms, noexit_visited, color=['#3498db', '#e74c3c', '#2ecc71'])
ax.set_ylabel('Количество клеток')
ax.set_title('Посещённые клетки (лабиринт без выхода)')
for bar, val in zip(bars, noexit_visited):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05, str(val), ha='center', va='bottom')
plt.savefig('docs/data/noexit_visited_graph.png', dpi=150, bbox_inches='tight')
plt.close()
fig, ax = plt.subplots(figsize=(14, 8))
ax.axis('off')
table_data = [
['Лабиринт', 'Стратегия', 'Время (мс)', 'Посещено', 'Длина пути'],
['Простой (10x10)', 'BFS', '0.020', '11', '6'],
['Простой (10x10)', 'DFS', '0.012', '9', '8'],
['Простой (10x10)', 'A*', '0.020', '9', '6'],
['С тупиками (20x20)', 'BFS', '0.492', '306', '35'],
['С тупиками (20x20)', 'DFS', '0.234', '198', '81'],
['С тупиками (20x20)', 'A*', '0.456', '225', '35'],
['Пустой (50x50)', 'BFS', '3.486', '2304', '95'],
['Пустой (50x50)', 'DFS', '10.452', '2304', '1129'],
['Пустой (50x50)', 'A*', '5.743', '2304', '95'],
['Без выхода', 'BFS', '0.010', '1', 'нет пути'],
['Без выхода', 'DFS', '0.003', '1', 'нет пути'],
['Без выхода', 'A*', '0.004', '1', 'нет пути'],
]
table = ax.table(cellText=table_data, loc='center', cellLoc='center', colWidths=[0.2, 0.13, 0.13, 0.13, 0.13])
table.auto_set_font_size(False)
table.set_fontsize(10)
table.scale(1, 1.8)
for i in range(5):
table[(0, i)].set_facecolor('#4472C4')
table[(0, i)].set_text_props(weight='bold', color='white')
for i in range(1, len(table_data)):
if i % 2 == 1:
for j in range(5):
table[(i, j)].set_facecolor('#E8F0FE')
plt.title('Результаты экспериментов по поиску пути в лабиринте', fontsize=14, fontweight='bold', pad=20)
plt.savefig('docs/data/maze_table_results.png', dpi=200, bbox_inches='tight', facecolor='white')
plt.close()

View File

@ -0,0 +1,154 @@
import time
import csv
import os
from lab2.maze_solver import TextFileMazeBuilder, BFSStrategy, DFSStrategy, AStarStrategy, MazeSolver
def save_maze_to_file(maze, filename):
with open(filename, 'w') as f:
for row in maze:
f.write(''.join(row) + '\n')
def run_test(maze_file, strategy_class):
builder = TextFileMazeBuilder()
maze = builder.build_from_file(maze_file)
solver = MazeSolver(maze, strategy_class)
times = []
visited = []
path_len = []
for i in range(5):
stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_count)
path_len.append(stats.path_length)
return {
'time': sum(times) / 5,
'visited': sum(visited) / 5,
'path': sum(path_len) / 5,
'path_found': max(path_len) > 0
}
def main():
print("Эксперименты по поиску пути в лабиринте")
results = []
print("\n1. Простой лабиринт (10x10)")
simple = [
"#######",
"#S #",
"# ### #",
"# E #",
"#######"
]
with open('simple.txt', 'w') as f:
for line in simple:
f.write(line + '\n')
for name, strategy in [('BFS', BFSStrategy()), ('DFS', DFSStrategy()), ('A*', AStarStrategy())]:
res = run_test('simple.txt', strategy)
print(f"{name}: время={res['time']:.3f}мс, посещено={res['visited']:.0f}, путь={res['path']:.0f}")
results.append(['Простой', name, round(res['time'], 3), round(res['visited'], 0), round(res['path'], 0)])
print("\n2. Лабиринт с тупиками (20x20)")
dead = []
for y in range(20):
row = []
for x in range(20):
if x == 0 or y == 0 or x == 19 or y == 19:
row.append('#')
elif (x == 5 and y > 5 and y < 15) or (y == 5 and x > 5 and x < 15):
row.append('#')
else:
row.append(' ')
dead.append(row)
dead[1][1] = 'S'
dead[18][18] = 'E'
with open('dead.txt', 'w') as f:
for row in dead:
f.write(''.join(row) + '\n')
for name, strategy in [('BFS', BFSStrategy()), ('DFS', DFSStrategy()), ('A*', AStarStrategy())]:
res = run_test('dead.txt', strategy)
print(f"{name}: время={res['time']:.3f}мс, посещено={res['visited']:.0f}, путь={res['path']:.0f}")
results.append(['С тупиками', name, round(res['time'], 3), round(res['visited'], 0), round(res['path'], 0)])
print("\n3. Пустой лабиринт (50x50)")
empty = []
for y in range(50):
row = []
for x in range(50):
if x == 0 or y == 0 or x == 49 or y == 49:
row.append('#')
else:
row.append(' ')
empty.append(row)
empty[1][1] = 'S'
empty[48][48] = 'E'
with open('empty.txt', 'w') as f:
for row in empty:
f.write(''.join(row) + '\n')
for name, strategy in [('BFS', BFSStrategy()), ('DFS', DFSStrategy()), ('A*', AStarStrategy())]:
res = run_test('empty.txt', strategy)
print(f"{name}: время={res['time']:.3f}мс, посещено={res['visited']:.0f}, путь={res['path']:.0f}")
results.append(['Пустой', name, round(res['time'], 3), round(res['visited'], 0), round(res['path'], 0)])
print("\n4. Лабиринт без выхода (10x10)")
noexit = []
for y in range(10):
row = []
for x in range(10):
if x == 0 or y == 0 or x == 9 or y == 9:
row.append('#')
else:
row.append('#')
noexit.append(row)
noexit[1][1] = 'S'
noexit[8][8] = 'E'
with open('noexit.txt', 'w') as f:
for row in noexit:
f.write(''.join(row) + '\n')
for name, strategy in [('BFS', BFSStrategy()), ('DFS', DFSStrategy()), ('A*', AStarStrategy())]:
try:
res = run_test('noexit.txt', strategy)
if res['path_found']:
print(f"{name}: путь найден! длина={res['path']:.0f}")
results.append(['Без выхода', name, round(res['time'], 3), round(res['visited'], 0), round(res['path'], 0)])
else:
print(f"{name}: путь не найден (корректно)")
results.append(['Без выхода', name, round(res['time'], 3), round(res['visited'], 0), 'нет пути'])
except Exception as e:
print(f"{name}: ошибка - {e}")
results.append(['Без выхода', name, 0, 0, 'ошибка'])
os.makedirs('docs/data', exist_ok=True)
with open('docs/data/maze_experiments.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Лабиринт', 'Стратегия', 'Время(мс)', 'Посещено клеток', 'Длина пути'])
writer.writerows(results)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,367 @@
from abc import ABC, abstractmethod
from collections import deque
import heapq
import time
import os
class Cell:
def __init__(self, x, y):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self):
return not self.is_wall
def __repr__(self):
return f"Cell({self.x},{self.y})"
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.cells = []
self.start = None
self.exit = None
for y in range(height):
row = []
for x in range(width):
row.append(Cell(x, y))
self.cells.append(row)
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
class TextFileMazeBuilder:
def build_from_file(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip() for line in f.readlines()]
height = len(lines)
width = len(lines[0])
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
maze.start = cell
cell.is_start = True
elif ch == 'E':
maze.exit = cell
cell.is_exit = True
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if not start or not exit:
return [], 0
queue = deque([(start, [start])])
visited = {start}
while queue:
current, path = queue.popleft()
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return [], len(visited)
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if not start or not exit:
return [], 0
stack = [(start, [start])]
visited = {start}
while stack:
current, path = stack.pop()
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
stack.append((neighbor, path + [neighbor]))
return [], len(visited)
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze, start, exit):
if not start or not exit:
return [], 0
heap = [(self._heuristic(start, exit), 0, start, [start])]
g_score = {start: 0}
visited = set()
counter = 1
while heap:
_, _, current, path = heapq.heappop(heap)
if current in visited:
continue
visited.add(current)
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
g_score[neighbor] = tentative_g
f = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(heap, (f, counter, neighbor, path + [neighbor]))
counter += 1
return [], len(visited)
class SearchStats:
def __init__(self, path, time_ms, visited_count):
self.path = path
self.time_ms = time_ms
self.visited_count = visited_count
self.path_length = len(path) if path else 0
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event, data=None):
for observer in self.observers:
observer.update(event, data)
def set_strategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
self.notify("search_started")
start_time = time.perf_counter()
path, visited_count = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
self.notify("search_finished", time_ms)
self.notify("path_found", path)
return SearchStats(path, time_ms, visited_count)
class Observer(ABC):
@abstractmethod
def update(self, event, data=None):
pass
class ConsoleView(Observer):
def __init__(self):
self.events = []
def update(self, event, data=None):
self.events.append((event, data))
if event == "maze_loaded":
print("[Событие] Лфбирин загружен")
elif event == "path_found":
print(f"[Событие] Путь найден! Длина: {len(data) if data else 0}")
elif event == "search_started":
print(f"[Событие] Поиск завершён. Время: {data:.3f}мс" if data else "[Событие] Поиск завершён")
elif event == "mpve":
print(f"[Событие] Игрок переместился в {data}")
elif event == "undo":
print("[Событие] Отмена последнего хода")
def render(self,maze, player=None, path=None):
os.system('cls' if os.name == 'nt' else 'clear')
print("Лабиринт")
for y in range(maze.height):
row = ""
for x in range(maze.width):
cell = maze.get_cell(x,y)
if player and cell == player.current_cell:
row += "p " #игрок
elif path and cell in path:
row += "* " #путь
elif cell.is_wall:
row += "# " #стена
elif cell.is_start:
row += "S " #старт
elif cell.is_exit:
row += "E " #выход
else:
row += ". " #прозод
print(row)
print("Управление: W/A/S/D - движение, U - отмена, Q - выход")
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class Player:
def __init__(self, start_cell):
self.current_cell = start_cell
self.start_cell = start_cell
def move_to(self, cell):
self.current_cell = cell
def resent(self):
self.current_cell = self.start_cell
def __repr__(self):
return f"Player at ({self.current_cell.x}, {self.current_cell.y})"
class MoveCommand(Command):
def __init__(self, player, new_cell, view):
self.player = player
self.new_cell = new_cell
self.old_cell = player.current_cell
self.view = view
def execute(self):
self.player.move_to(self.new_cell)
self.view.update("undo", None)
def undo(self):
self.player.move_to(self.old_cell)
self.view.update("undo",None)
class GameController:
def __init__(self, maze, view):
self.maze = maze
self.view = view
self.player = Player(maze.start)
self.command_history = []
def get_cell_in_direction(self, direction):
x, y = self.player.current_cell.x, self.player.current_cell.y
if direction == 'w':
y -= 1
elif direction == 's':
y += 1
elif direction == 'a':
x -= 1
elif direction == 'd':
x += 1
else:
return None
return self.maze.get_cell(x, y)
def try_move(self, direction):
new_cell = self.get_cell_in_direction(direction)
if new_cell and new_cell.is_passable():
command = MoveCommand(self.player, new_cell, self.view)
command.execute()
self.command_history.append(command)
if new_cell.is_exit:
self.view.update("path_found", [])
print("Вы нашли выход.")
return True
else:
print("Невозможно пройти - стена")
return False
def undo(self):
if self.command_history:
command = self.command_history.pop()
command.undo()
else:
print("Нечего отменять")
def visualize_path(self, path):
self.view.render(self.maze, self.player, path)
def run_manual_mode(self):
while True:
self.view.render(self.maze, self.player)
command = input("Введите команду: ").lower().strip()
if command in ['w', 'a', 's', 'd']:
self.try_move(command)
elif command == 'u':
self.undo()
elif command == 'q':
print('Выход из игры')
break
else:
print("Неизвестная команда. Используйте: W/A/S/D - движение, U - отмена, Q - выход")

10
konnovaea/lab2/noexit.txt Normal file
View File

@ -0,0 +1,10 @@
##########
#S########
##########
##########
##########
##########
##########
##########
########E#
##########

View File

@ -0,0 +1,5 @@
#######
#S #
# ### #
# E #
#######