Compare commits

...

23 Commits

Author SHA1 Message Date
oSTEVEo
3d9390a894 Рефакторинг всего и готовый отчёт 2026-05-25 03:00:00 +03:00
oSTEVEo
9849075a38 Реализован класс-оркестратор для тестирования 2026-05-24 19:12:04 +03:00
oSTEVEo
983ef65bef Закончен этап 5. Паттерн Command будет реализован позже 2026-05-24 00:45:43 +03:00
oSTEVEo
95805467ab Реализован паттерн Observer 2026-05-24 00:33:24 +03:00
oSTEVEo
74928a997a Реализован Этап 5 2026-05-23 12:02:30 +03:00
oSTEVEo
107d5cbd61 Реализован этап 3, добавлен A*, некоторые исправления 2026-05-20 21:25:57 +03:00
oSTEVEo
d5f28df86a Рефакторинг: Вынес restorePath() в отдельный общий файл 2026-05-17 00:48:23 +03:00
oSTEVEo
1a562a7594 Реализован DFS 2026-05-17 00:45:29 +03:00
oSTEVEo
89c11085ba Реализован BFS, созданы шаблоны для всех алгоритмов 2026-05-16 19:20:59 +03:00
oSTEVEo
f3978396eb Фикс width <=> height в maze.py 2026-05-16 19:19:51 +03:00
oSTEVEo
c7c181f30b Реализован этап 2 2026-04-28 01:11:11 +03:00
oSTEVEo
b002e85958 Реализован этап 1 2026-04-24 06:14:07 +03:00
oSTEVEo
94a9cba182 Пара исправлений 2026-04-05 10:06:28 +03:00
oSTEVEo
cb02fb20fe Переделан и реализован подсчёт времени для каждой СД 2026-04-03 01:31:26 +03:00
oSTEVEo
94bc3b8524 Pylance fixes 2026-04-03 01:30:35 +03:00
oSTEVEo
7026ad395d Начат отчёт 2026-03-30 17:47:24 +03:00
oSTEVEo
dd4eca8407 Полностью реализован BinaryTree 2026-03-23 00:07:17 +03:00
oSTEVEo
b383ed1cdc Реализован bst_insert 2026-03-22 00:39:01 +03:00
oSTEVEo
9bdefcc922 Реализован HashTable 2026-03-21 23:41:46 +03:00
oSTEVEo
0b092e8d26 Обновлён .gitignore 2026-03-21 22:30:43 +03:00
oSTEVEo
b984ec3569 Перемещены файлы и добавлена реализация LinkedList 2026-03-21 22:30:40 +03:00
oSTEVEo
9c1cd94e87 Добавлен .gitignore для vscode 2026-03-21 22:30:36 +03:00
oSTEVEo
8124c755f8 Добавлены шаблоны для реализации структур 2026-03-21 22:30:30 +03:00
38 changed files with 2222 additions and 0 deletions

2
MusinAA/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode/
*/tests/

148
MusinAA/docs/Report 1.ipynb Normal file
View File

@ -0,0 +1,148 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "2acfa743",
"metadata": {},
"source": [
"# 0. Подготовим окружение"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "4689b73e",
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"import os\n",
"sys.path.insert(0, os.path.abspath( '../task1'))\n",
"sys.path.insert(0, os.path.abspath( '../'))"
]
},
{
"cell_type": "markdown",
"id": "37cc11a5",
"metadata": {},
"source": [
"# 1. Генерация тестовых данных\n",
"\n",
"Создадим список records из N=10000 элементов. Каждый элемент — кортеж (name, phone). \n",
"Имена возъмём случайные из небольшого набора (чтобы были повторения и коллизии). \n",
"Для проверки влияния порядка подготовим два варианта: \n",
"\n",
"_records_shuffled_ — случайный порядок. \n",
"_records_sorted_ — отсортированный по имени (по алфавиту)."
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "a3b5c31b",
"metadata": {},
"outputs": [],
"source": [
"from util.randomNames import generate_test_data\n",
"from util.timeTester import test\n",
"\n",
"records_shuffled = generate_test_data(N=10000)\n",
"records_sorted = generate_test_data(N=10000, _sorted=True)"
]
},
{
"cell_type": "markdown",
"id": "c2f4989c",
"metadata": {},
"source": [
"# 2. Проведение замеров"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "df12d41d",
"metadata": {},
"outputs": [],
"source": [
"# Подготовим функции СД, которые будем тестировать\n",
"from structures.LinkedList import *\n",
"from structures.HashTable import *\n",
"from structures.BinaryTree import *\n",
"\n",
"func_list = {\"Связанный список\" : (ll_insert, ll_find, ll_delete),\n",
" \"Хэш-таблица\" : (ht_insert, ht_find, ht_delete),\n",
" \"Бинарное дерево\" : (bst_insert, bst_find, bst_delete)}"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "cc8d0436",
"metadata": {},
"outputs": [],
"source": [
"# Проведём замеры\n",
"report = [[\"Структура\", \"Режим\", \"Вставка\", \"Поиск\", \"Удаление\"]]\n",
"records = {\"Cлучайный\" : records_shuffled, \"Отсортированный\" : records_sorted}\n",
"\n",
"TEST_ITERATIONS_NUM = 5\n",
"\n",
"for _ in range(TEST_ITERATIONS_NUM):\n",
" for mode, data in records.items():\n",
" for struct_name, fns in func_list.items():\n",
" result = test(data, *fns)\n",
" row = [struct_name, mode,\n",
" result[\"insert_time\"],\n",
" result[\"find_time\"],\n",
" result[\"delete_time\"]]\n",
" report.append(row)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "2eedf056",
"metadata": {},
"outputs": [],
"source": [
"# Сохраним данные в csv\n",
"import csv\n",
"with open(\"data/results.csv\", \"w\", newline=\"\") as f:\n",
" writer = csv.writer(f)\n",
" writer.writerows(report)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8523ae48",
"metadata": {},
"outputs": [],
"source": [
"TODO проверить работает ли оно вообще"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

903
MusinAA/docs/Report 2.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,31 @@
Структура,Режим,Вставка,Поиск,Удаление
Связанный список,Cлучайный,4.661078881000321,0.012941928999680385,0.020117076999667916
Хэш-таблица,Cлучайный,0.18879180299973086,0.0014481220005109208,0.0012560499999381136
Бинарное дерево,Cлучайный,0.01616830700004357,0.00016560199946979992,0.00016564300040045055
Связанный список,Отсортированный,5.038275819999399,0.07141196100019442,0.1353478549999636
Хэш-таблица,Отсортированный,0.1631836659998953,0.003129734999674838,0.003202291999514273
Бинарное дерево,Отсортированный,0.16634428900033527,0.0017354540004816954,0.001874036000117485
Связанный список,Cлучайный,4.664316974999565,0.014653709000413073,0.02280332600003021
Хэш-таблица,Cлучайный,0.186777711999639,0.0010283499996148748,0.0009878339997158037
Бинарное дерево,Cлучайный,0.016256722999969497,0.00015175599946815055,0.0001649409996389295
Связанный список,Отсортированный,5.907060995999927,0.08458327099924645,0.12409427100010362
Хэш-таблица,Отсортированный,0.18781050100005814,0.003518858999996155,0.0038270310005827923
Бинарное дерево,Отсортированный,0.17500397699950554,0.0019018089997189236,0.001833940000324219
Связанный список,Cлучайный,5.755159846999959,0.016742109000006167,0.026386416000605095
Хэш-таблица,Cлучайный,0.22951744300007704,0.0009921219998432207,0.0011742059996322496
Бинарное дерево,Cлучайный,0.01580532200023299,0.00017768500038073398,0.00019722199976968113
Связанный список,Отсортированный,6.30150953500015,0.11176149099992472,0.1474957850005012
Хэш-таблица,Отсортированный,0.18557919999966543,0.003155624000100943,0.002713390000280924
Бинарное дерево,Отсортированный,0.19082545899982506,0.001985636999961571,0.002389950000178942
Связанный список,Cлучайный,6.395210606000546,0.014804241999627266,0.02304338900012226
Хэш-таблица,Cлучайный,0.1902105980007036,0.000852088000101503,0.0009626670007492066
Бинарное дерево,Cлучайный,0.01663886400001502,0.00016400000004068715,0.000184548000106588
Связанный список,Отсортированный,4.850914527999521,0.0771323629996914,0.1152741280002374
Хэш-таблица,Отсортированный,0.17607759100064868,0.002924628000073426,0.0033850670006358996
Бинарное дерево,Отсортированный,0.19345043999965128,0.0018585970001367969,0.0019752460002564476
Связанный список,Cлучайный,4.803303787999539,0.015972447000422108,0.0223228390004806
Хэш-таблица,Cлучайный,0.19020581800032232,0.0011616620004133438,0.0009839170006671338
Бинарное дерево,Cлучайный,0.016469425000650517,0.000160212000082538,0.00017693399968266021
Связанный список,Отсортированный,4.741838529000233,0.075463203000254,0.10462550600004761
Хэш-таблица,Отсортированный,0.16722737300005974,0.004002030999799899,0.005207103999964602
Бинарное дерево,Отсортированный,0.16575109800032806,0.0020921670002280734,0.002277146999404067
1 Структура Режим Вставка Поиск Удаление
2 Связанный список Cлучайный 4.661078881000321 0.012941928999680385 0.020117076999667916
3 Хэш-таблица Cлучайный 0.18879180299973086 0.0014481220005109208 0.0012560499999381136
4 Бинарное дерево Cлучайный 0.01616830700004357 0.00016560199946979992 0.00016564300040045055
5 Связанный список Отсортированный 5.038275819999399 0.07141196100019442 0.1353478549999636
6 Хэш-таблица Отсортированный 0.1631836659998953 0.003129734999674838 0.003202291999514273
7 Бинарное дерево Отсортированный 0.16634428900033527 0.0017354540004816954 0.001874036000117485
8 Связанный список Cлучайный 4.664316974999565 0.014653709000413073 0.02280332600003021
9 Хэш-таблица Cлучайный 0.186777711999639 0.0010283499996148748 0.0009878339997158037
10 Бинарное дерево Cлучайный 0.016256722999969497 0.00015175599946815055 0.0001649409996389295
11 Связанный список Отсортированный 5.907060995999927 0.08458327099924645 0.12409427100010362
12 Хэш-таблица Отсортированный 0.18781050100005814 0.003518858999996155 0.0038270310005827923
13 Бинарное дерево Отсортированный 0.17500397699950554 0.0019018089997189236 0.001833940000324219
14 Связанный список Cлучайный 5.755159846999959 0.016742109000006167 0.026386416000605095
15 Хэш-таблица Cлучайный 0.22951744300007704 0.0009921219998432207 0.0011742059996322496
16 Бинарное дерево Cлучайный 0.01580532200023299 0.00017768500038073398 0.00019722199976968113
17 Связанный список Отсортированный 6.30150953500015 0.11176149099992472 0.1474957850005012
18 Хэш-таблица Отсортированный 0.18557919999966543 0.003155624000100943 0.002713390000280924
19 Бинарное дерево Отсортированный 0.19082545899982506 0.001985636999961571 0.002389950000178942
20 Связанный список Cлучайный 6.395210606000546 0.014804241999627266 0.02304338900012226
21 Хэш-таблица Cлучайный 0.1902105980007036 0.000852088000101503 0.0009626670007492066
22 Бинарное дерево Cлучайный 0.01663886400001502 0.00016400000004068715 0.000184548000106588
23 Связанный список Отсортированный 4.850914527999521 0.0771323629996914 0.1152741280002374
24 Хэш-таблица Отсортированный 0.17607759100064868 0.002924628000073426 0.0033850670006358996
25 Бинарное дерево Отсортированный 0.19345043999965128 0.0018585970001367969 0.0019752460002564476
26 Связанный список Cлучайный 4.803303787999539 0.015972447000422108 0.0223228390004806
27 Хэш-таблица Cлучайный 0.19020581800032232 0.0011616620004133438 0.0009839170006671338
28 Бинарное дерево Cлучайный 0.016469425000650517 0.000160212000082538 0.00017693399968266021
29 Связанный список Отсортированный 4.741838529000233 0.075463203000254 0.10462550600004761
30 Хэш-таблица Отсортированный 0.16722737300005974 0.004002030999799899 0.005207103999964602
31 Бинарное дерево Отсортированный 0.16575109800032806 0.0020921670002280734 0.002277146999404067

View File

@ -0,0 +1,16 @@
Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути
BFS,10x10,0.4038135000882903,53,23
BFS,5x5,0.07533170064562,8,7
BFS,100x100,17.14356810080062,2495,1171
BFS,50x50,3.010086300491821,640,427
BFS,25x25,1.0405578999780118,232,173
DFS,10x10,0.07943829987198114,35,31
DFS,5x5,0.018403499416308478,8,7
DFS,100x100,8.430859900545329,3219,1243
DFS,50x50,2.0664067997131497,995,435
DFS,25x25,0.5787261994555593,316,173
AStar,10x10,0.0671462003083434,23,23
AStar,5x5,0.022370600345311686,8,7
AStar,100x100,4.951790099585196,1286,1171
AStar,50x50,2.081632300541969,496,427
AStar,25x25,0.5791453000711044,186,177
1 Алгоритм Лабиринт Время (мс) Посещённые клетки Длинна пути
2 BFS 10x10 0.4038135000882903 53 23
3 BFS 5x5 0.07533170064562 8 7
4 BFS 100x100 17.14356810080062 2495 1171
5 BFS 50x50 3.010086300491821 640 427
6 BFS 25x25 1.0405578999780118 232 173
7 DFS 10x10 0.07943829987198114 35 31
8 DFS 5x5 0.018403499416308478 8 7
9 DFS 100x100 8.430859900545329 3219 1243
10 DFS 50x50 2.0664067997131497 995 435
11 DFS 25x25 0.5787261994555593 316 173
12 AStar 10x10 0.0671462003083434 23 23
13 AStar 5x5 0.022370600345311686 8 7
14 AStar 100x100 4.951790099585196 1286 1171
15 AStar 50x50 2.081632300541969 496 427
16 AStar 25x25 0.5791453000711044 186 177

View File

@ -0,0 +1,7 @@
Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути
BFS,maze_25x25_wo_exit,1.9682294001540868,338,-1
BFS,maze_25x25_empty,4.574537699954817,625,49
DFS,maze_25x25_wo_exit,0.719102000221028,338,-1
DFS,maze_25x25_empty,0.903778699648683,625,337
AStar,maze_25x25_wo_exit,1.0117966015968705,338,-1
AStar,maze_25x25_empty,0.21763520016975235,49,49
1 Алгоритм Лабиринт Время (мс) Посещённые клетки Длинна пути
2 BFS maze_25x25_wo_exit 1.9682294001540868 338 -1
3 BFS maze_25x25_empty 4.574537699954817 625 49
4 DFS maze_25x25_wo_exit 0.719102000221028 338 -1
5 DFS maze_25x25_empty 0.903778699648683 625 337
6 AStar maze_25x25_wo_exit 1.0117966015968705 338 -1
7 AStar maze_25x25_empty 0.21763520016975235 49 49

View File

View File

@ -0,0 +1,88 @@
"""
Двоичное дерево поиска
Узел словарь:
{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.
"""
def bst_insert(root: dict|None, name: str, phone: str) -> dict:
"""Итеративно вставляет, возвращает новый корень (если корень меняется)."""
if root == None:
return {'name': name, 'phone': phone, 'left': None, 'right': None}
# '674' < '722' == True, lol
current = root
while True:
if current['name'] == name:
current['phone'] = phone
return root
elif name < current['name']:
if current['left'] == None:
current['left'] = bst_insert(None, name, phone)
return root
else:
current = current['left']
else:
if current['right'] == None:
current['right'] = bst_insert(None, name, phone)
return root
else:
current = current['right']
# Увы, это самый лаконичный вариант, который я придумал.
def bst_find(root: dict|None, name: str) -> str|None:
"""Поиск в ширину."""
node = find_node_to_delete(root, name)
if node != None:
return node['phone']
def find_node_to_delete(root: dict|None, name: str) -> dict|None:
"""Поиск в ширину."""
while root != None:
if root['name'] == name:
return root
elif name < root['name']:
root = root['left']
else:
root = root['right']
return None
def find_minimal_child(root: dict) -> dict|None:
while root['left']:
root = root['left']
return root
def bst_delete(root: dict, name: str) -> None:
"""Удаляет узел и возвращает новый корень."""
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:
# Случай 1: нет детей или один ребенок
if root['left'] is None:
return root['right']
elif root['right'] is None:
return root['left']
# Случай 2: два ребенка
min_node = find_minimal_child(root['right'])
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: dict) -> list:
"""Центрированный обход.
Рекурсивно собирает записи в отсортированном порядке."""
if root is None:
return []
node_values = {"name": root['name'], "phone": root['phone']}
return bst_list_all(root['left']) + [node_values] + bst_list_all(root['right'])

View File

@ -0,0 +1,59 @@
"""
Хеш-таблица
Хранится как список buckets фиксированной длины,
каждый элемент голова связного списка (или None).
"""
from task1.structures.LinkedList import *
def hash_fun(name: str, size: int) -> int:
"""Принимает имя и возвращает индекс бакета для него."""
if size <= 0:
raise ValueError("size должен быть больше 0")
hashSum = 0
n = size+1
base = 1103 # ord('я')
for letter in name:
hashSum += ord(letter) * pow(base, n)
n -= 1
return int(hashSum) % size
def ht_insert(buckets: list|None, name: str, phone: str, blen:int = 50) -> list:
"""Возвращает новый массив бакетов
Вычисляет индекс, вызывает ll_insert для соответствующего бакета.
Функция не меняет размер массива бакетов автоматически!"""
if buckets == [] or buckets == None:
buckets = [None] * blen
# raise ValueError("Длинна buckets должна быть больше 0")
size = len(buckets)
index = hash_fun(name, size)
buckets[index] = ll_insert(buckets[index], name, phone)
return buckets
def ht_delete(buckets: list, name: str) -> list:
"""Возвращает новый массив бакетов без элемента с именем name"""
if buckets == []:
raise ValueError("Длинна buckets должна быть больше 0")
size = len(buckets)
index = hash_fun(name, size)
buckets[index] = ll_delete(buckets[index], name)
return buckets
def ht_find(buckets: list|None, name: str) -> str|None:
if buckets == [] or buckets == None:
raise ValueError("Длинна buckets должна быть больше 0")
size = len(buckets)
index = hash_fun(name, size)
return ll_find(buckets[index], name)
def ht_list_all(buckets):
"""Собирает все записи из всех бакетов и сортирует"""
allRecords = []
for bucket in buckets:
allRecords.extend(ll_list_all(bucket))
return sorted(allRecords, key=lambda x: x[0])

View File

@ -0,0 +1,60 @@
"""
Связный список (LinkedListPhoneBook)
Узел представляется словарём:
{'name': 'Имя', 'phone': '123', 'next': None}.
"""
def ll_insert(head : dict|None, name: str, phone: str) -> dict:
"""
Проходит до конца (или сразу добавляет в конец) и возвращает новую
голову (если вставка в начало) или изменяет список по ссылке.
Удобнее возвращать новую голову, если вставка может быть в начало.
"""
newNode = {'name': name, 'phone': phone, 'next': None}
if head == None:
return newNode
currentNode = head
while currentNode['next'] != None:
currentNode = currentNode['next']
currentNode['next'] = newNode
return head
def ll_find(head : dict|None, name: str) -> str|None:
"""Ищет узел, возвращает телефон или None."""
currentNode = head
while currentNode != None:
if currentNode['name'] == name:
return currentNode['phone']
currentNode = currentNode['next']
return None
def ll_delete(head : dict|None, name: str) -> dict|None:
"""Удаляет узел, возвращает новую голову."""
if head == None:
return None
if head['name'] == name:
return head['next']
currentNode = head
while currentNode['next'] != None:
if currentNode['next']['name'] == name:
currentNode['next'] = currentNode['next']['next']
return head
currentNode = currentNode['next']
return head
def ll_list_all(head: dict|None) -> list:
"""Cобирает все записи в список и сортирует.
сортировка вынесена отдельно)."""
records = []
currentNode = head
while currentNode != None:
records.append((currentNode['name'], currentNode['phone']))
currentNode = currentNode['next']
records.sort(key=lambda item: item[0])
return records

View File

View File

View File

@ -0,0 +1,55 @@
import random
names_pool = (
"Иван", "Мария", "Петр", "Анна", "Сергей", "Елена", "Алексей", "Ольга",
"Дмитрий", "Татьяна", "Михаил", "Наталья", "Андрей", "Ирина", "Николай",
"Светлана", "Владимир", "Екатерина", "Александр", "Юлия", "Павел", "Ксения",
"Виктор", "Анастасия", "Артем", "Виктория", "Максим", "Полина", "Даниил",
"София", "Евгений", "Алиса", "Станислав", "Дарья", "Георгий", "Вероника",
"Кирилл", "Маргарита", "Тимофей", "Арина", "Руфина", "Илларион", "Стелла",
"Роман", "Валерия", "Игорь", "Алина", "Олег", "Диана", "Юрий", "Милана",
"Василий", "Ева", "Никита", "Алиса", "Константин", "Кира", "Денис", "Ангелина",
"Вячеслав", "Мирослава", "Григорий", "Эмилия", "Леонид", "Василиса", "Руслан",
"Стефания", "Арсений", "Есения", "Антон", "Яна", "Матвей", "Любовь", "Семен",
"Надежда", "Федор", "Софья", "Лев", "Варвара", "Егор", "Амелия", "Борис",
"Агата", "Захар", "Камилла", "Давид", "Олеся", "Ярослав", "Людмила", "Данила",
"Регина", "Марк", "Каролина", "Артур", "Нелли", "Глеб", "Инна", "Платон",
"Нина", "Святослав", "Римма", "Родион", "Лидия", "Эдуард", "Жанна", "Вадим",
"Рената", "Савелий", "Алла", "Назар", "Снежана", "Демид", "Лариса", "Филипп",
"Злата", "Тимур", "Майя", "Клим", "Эльвира", "Дамир", "Таисия", "Илья",
"Роза", "Виталий", "Азалия", "Степан", "Лиана", "Богдан", "Инесса", "Эрик",
"Ариана", "Алан", "Юлиана", "Лука", "Антонина", "Мирон", "Клавдия", "Гордей",
"Руслана", "Макар", "Елизавета", "Северин", "Александра", "Моисей", "Агафья",
"Наум", "Серафима", "Влад", "Фаина", "Кузьма", "Пелагея", "Ермак", "Ульяна",
"Тарас", "Марианна", "Остап", "Бронислава", "Архип", "Владислава", "Фома",
"Станислава", "Еремей", "Зинаида", "Прохор", "Раиса", "Мстислав", "Галина",
"Ростислав", "Валентина", "Серафим", "Евдокия", "Лаврентий", "Кристина",
"Никон", "Анфиса", "Феликс", "Лия", "Иннокентий", "Роксана", "Всеволод",
"Эвелина", "Модест", "Юнона", "Трофим", "Изабелла", "Аполлон", "Глория",
"Касьян", "Аврора", "Любомир", "Адель", "Бронислав", "Доминика", "Афанасий",
"Фрида", "Евстафий", "Ассоль", "Венедикт", "Цветана", "Епифан", "Мелисса",
"Добрыня"
)
_non_existent_names = [
"Ноль", "Целковый", "Полушка", "Четвертушка", "Осьмушка",
"Пудовичок", "Медячок", "Серебрячок", "Золотничок", "Девятичок"
]
assert set(names_pool).isdisjoint(set(_non_existent_names)), \
"В списке несуществующих имён существуют существующие имена сущностей"
names_pool_to_find = random.choices(names_pool, k=100) + _non_existent_names
def generate_phone(phone_len=11) -> str:
# 88005553535
return str(random.randint(10**phone_len, 10**(phone_len+1)-1))
def generate_test_data(N=10000, _sorted=False):
records = []
for i in range(N):
name = random.choice(names_pool)
phone = generate_phone()
records.append((name, phone))
if _sorted:
return sorted(records)
return records

View File

@ -0,0 +1,37 @@
import time
import random
from typing import Callable, Any
from task1.util.randomNames import names_pool_to_find, names_pool
def test(records: list,
insert_func: Callable[[Any, str, str], Any],
find_func: Callable[[Any, str], Any],
delete_func: Callable[[Any, str], Any]) -> dict:
data = None
# Вставка всех записей
start = time.perf_counter()
for item in records:
data = insert_func(data, item[0], item[1])
end = time.perf_counter()
insert_time = end - start
# Поиск 110 случайных записей
start = time.perf_counter()
for name in names_pool_to_find:
find_func(data, name)
end = time.perf_counter()
find_time = end - start
# Удаление 50 случайных записей
start = time.perf_counter()
for name in random.choices(names_pool, k = 50):
data = delete_func(data, name)
end = time.perf_counter()
delete_time = end - start
return {
"insert_time" : insert_time ,
"find_time" : find_time ,
"delete_time": delete_time
}

1
MusinAA/task2/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
maze_generator.py

View File

View File

@ -0,0 +1,94 @@
"""
Реализовать класс ConsoleView, который отображает лабиринт,
текущее положение игрока (если реализован пошаговый режим) и найденный путь.
Метод render(maze, player_position, path) рисует карту в консоли."""
import os
from task2.mazeObjects.cell import Cell
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.path import Path
from task2.observerSubject import MazeEvent, MazeEventType, Observer
SBROS = "\033[0m"
WALL = "#"
EXIT = "E"
START = "S"
PATH_SYMBOL = "+"
SPACE_SYMBOL = " "
# Я убрал аргументы из render(), чтобы не передавать их при каждом запуске.
# И работать внутри класса приятнее, чем тянуть эти аргументы туда-сюда
class ConsoleView(Observer):
maze:Maze|None
path:Path|None
def __init__(self, maze:Maze|None=None, path:Path|None=None):
super().__init__()
self.maze = maze
self.path = path
def _getCellColored(self, cell:Cell) -> str:
if cell.isWall:
# Белый
return self._fmt_str(7, 7, WALL)
elif cell.isExit:
# Кислотно-зелёный
return self._fmt_str(12, 7, EXIT)
elif cell.isStart:
# Кислотно-красный
return self._fmt_str(9, 7, START)
elif self.path and self.path.array:
if cell in self.path.array:
# Градиент
percent = self.path.array.index(cell) / len(self.path.array)
n = self._ANSICalculator(*self._getGradient(percent))
return self._fmt_str(n, 10, PATH_SYMBOL)
return SPACE_SYMBOL
def _fmt_str(self, bg:int, fg:int, symbol:str) -> str:
return f"\033[48;5;{bg}m\033[38;5;{fg}m{symbol}{SBROS}"
def _ANSICalculator(self, r:int, g:int, b:int):
r = max(0, min(5, r))
g = max(0, min(5, g))
b = max(0, min(5, b))
return 16 + 36*r + 6*g + b
def _getGradient(self, percent:float):
r = 5 * (1-percent)
g = 0
b = 5 * percent
return int(round(r)), int(round(g)), int(round(b))
def render(self, player_position=None):
"""
Печатем ячейку.
Цвет зависит от индекса ячейчки в массиве path.
Если в массиве нет - просто белый.
"""
os.system('cls' if os.name == 'nt' else 'clear')
if not self.maze:
print("Лабиринт ещё не загружен")
return None
output = ""
for y in range(self.maze.height):
for x in range(self.maze.width):
cell = self.maze.getCell(x, y)
output += self._getCellColored(cell)
output += "\n"
print(output)
def update(self, event: MazeEvent):
if event.evtype in (MazeEventType.MAZE_LOADED, MazeEventType.PATH_FOUND, MazeEventType.MOVE):
if event.evtype == MazeEventType.PATH_FOUND:
if not event.data: raise ValueError
self.path = event.data
if event.evtype == MazeEventType.MAZE_LOADED:
if not event.data: raise ValueError
self.maze = self.maze
self.render()

View File

@ -0,0 +1,72 @@
from abc import ABC, abstractmethod
from itertools import product
import sys
import os.path as path
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
from task2.observerSubject import MazeEvent, MazeEventType, Subject
class MazeBuilder(ABC):
"""Интерфейс MazeBuilder с методом buildFromFile(filename)"""
@abstractmethod
def buildFromFile(self, filename: str):
"""Создание лабиринта из файла."""
class TextFileMazeBuilder(MazeBuilder):
"""Читает файл, парсит символы,
создаёт объекты Cell,
задаёт координаты и флаги,
после чего возвращает готовый Maze."""
start:dict
end:dict
def _cellStrategy(self, letter: str) -> Cell:
if letter == '#':
return Cell(isWall=True)
elif letter == ' ':
return Cell()
elif letter == 'S':
return Cell(isStart=True)
elif letter == 'E':
return Cell(isExit=True)
else:
sys.stderr.write(f"Неизвестный символ '{letter}' при загрузке из файла\n")
return Cell()
def _updateStartEnd(self, letter: str, x:int, y:int) -> None:
if letter == 'S':
self.start = {'x': x, 'y': y}
elif letter == 'E':
self.end = {'x': x, 'y': y}
def _generate_row_from_txt(self, filename: str) -> list[str]:
with open(filename) as file:
text = file.read()
text = text.strip()
if not text:
raise ValueError(f"Файл \"{filename}\" пуст")
text = text.split('\n')
return text
def buildFromFile(self, filename: str):
rows = self._generate_row_from_txt(filename)
height = len(rows)
width = len(rows[0])
array = [[Cell() for j in range(width)] for i in range(height)]
try:
for x, y in product(range(width), range(height)):
cell = self._cellStrategy(rows[y][x])
self._updateStartEnd(rows[y][x], x, y)
cell.x = x
cell.y = y
array[y][x] = cell
except IndexError:
raise ValueError(f"В файле {filename}: Строка {y+1} имеет длину {len(rows[y])}, ожидалось {width}")
maze_name, _ = path.splitext(path.basename(filename))
return Maze(array, self.start, self.end, name=maze_name)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,25 @@
S
E

View File

@ -0,0 +1,25 @@
S # # #
# ##### # # # ####### #
# # # # # #
#### # ##### # # #######
# # # # # # #
## # # # ##### ### # # #
# # # # # # # # #
### # # # # ### # # # #
# # # # # # # #
# ### # # ### ### # ####
# # # # # # #
### # # # ####### #####
# # # #
# ################# ###
# # # #
# # # ####### ####### ##
# # # # #
### ##### # ### ### ###
# # # # # #
# # # ##### # # #######
# # # # # #
##### # ####### # ### ##
# # # # # # #
#### # # # ### ##### # #
# # # #

View File

View File

@ -0,0 +1,13 @@
class Cell:
"""Хранит координаты (x, y)
флаги isWall, isStart, isExit
метод isPassable() (возвращает True для прохода, если не стена)."""
def __init__(self, x: int = 0, y: int = 0, isWall:bool = False, isStart:bool = False, isExit:bool = False):
self.x = x
self.y = y
self.isWall = isWall
self.isStart = isStart
self.isExit = isExit
def isPassable(self):
return not self.isWall

View File

@ -0,0 +1,41 @@
from task2.mazeObjects.cell import Cell
class Maze:
"""Хранит двумерный массив клеток,
ширину, высоту, ссылки на стартовую и выходную клетку.
Методы:
getCell(x, y), getNeighbors(cell) возвращает список соседних проходимых клеток
(вверх, вниз, влево, вправо, если в пределах границ и не стена)."""
def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict, name:str="") -> None:
self.mazeArray = mazeArray
self.height = len(mazeArray) # X
self.width = len(mazeArray[0]) # Y
self.startCell = self.getCell(start['x'], start['y'])
self.endCell = self.getCell(end['x'], end['y'])
self.name = name
def getCell(self, x: int, y: int):
return self.mazeArray[y][x]
def checkCell(self, x: int, y: int):
if not(0 <= x and x < self.width):
return False
if not(0 <= y and y < self.height):
return False
return self.getCell(x, y).isPassable()
def getNeighbors(self, cell: Cell):
point = (cell.x, cell.y)
offsets = ((0, 1),
(0, -1),
(-1, 0),
(1, 0))
passableCells = []
for ofst in offsets:
x = point[0]+ofst[0]
y = point[1]+ofst[1]
if self.checkCell(x, y):
passableCells.append(self.getCell(x, y))
return passableCells

View File

@ -0,0 +1,6 @@
from task2.mazeObjects.cell import Cell
class Path:
def __init__(self, array:list[Cell]|None, visited_cells:int):
self.array = array
self.visited_cells = visited_cells

View File

@ -0,0 +1,66 @@
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
from task2.observerSubject import MazeEvent, MazeEventType, Subject
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
from task2.strategyObjects.BFS import BFS
import time
class SearchStats:
maze_name:str = "None"
"""Время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути"""
def __init__(self, path: list[Cell]|None, duration:float, visited_cells:int, path_len:int, strategy_name:str):
self.duration = duration
self.visited_cells = visited_cells
self.path_len = path_len
self.path = path
self.strategy_name = strategy_name
def toDict(self,):
return {
"strategy_name" : self.strategy_name,
"maze_name" : self.maze_name,
"duration" : self.duration,
"visited_cells" : self.visited_cells,
"path_len" : self.path_len
}
class MazeSolver(Subject):
"""
MazeSolver содержит поля maze и strategy.
Метод setStrategy(strategy) для динамической смены алгоритма.
Метод solve() вызывает strategy.findPath(...) и возвращает объект SearchStats (время выполнения в миллисекундах,
количество посещённых клеток, длина найденного пути).
Для замера времени используйте time.perf_counter() до и после вызова стратегии.
"""
def __init__(self, strategy:PathFindingStrategy, maze:Maze|None=None):
super().__init__()
self._maze = maze
self.strategy = strategy
def setMaze(self, maze: Maze|None):
self._maze = maze
self.notify(MazeEvent(MazeEventType.MAZE_LOADED, data=maze))
def setStrategy(self, strategy:PathFindingStrategy):
self.strategy = strategy
def getStrategyName(self):
return self.strategy.__class__.__name__
def solve(self):
if not self._maze:
raise ValueError
t_start = time.perf_counter()
path = self.strategy.findPath(self._maze, self._maze.startCell, self._maze.endCell)
duration = (time.perf_counter() - t_start) * 1000
path_len = len(path.array) if path.array else -1
strategy_name = self.getStrategyName()
stats = SearchStats(path.array, duration, path.visited_cells, path_len, strategy_name)
self.notify(MazeEvent(MazeEventType.PATH_FOUND, data=path))
return stats

View File

@ -0,0 +1,43 @@
"""
Создать интерфейс Observer с методом update(event),
где event может быть строкой или объектом с типом события ("path_found", "move", "maze_loaded").
"""
from enum import Enum
from abc import ABC, abstractmethod
class MazeEventType(Enum):
PATH_FOUND = "path_found"
MOVE = "move"
MAZE_LOADED = "maze_loaded"
class MazeEvent:
data=None
def __init__(self, evtype: MazeEventType, data=None):
if not isinstance(evtype, MazeEventType):
raise TypeError(f"evtype must be an EventType, got {type(evtype)}")
self.evtype = evtype
self.data = data
class Observer(ABC):
@abstractmethod
def update(self, event: MazeEvent):
raise NotImplementedError
class Subject(ABC):
"""Издатель: управляет подписчиками и отправляет им уведомления."""
def __init__(self):
self._observers:set[Observer] = set()
def attach(self, obs:Observer):
"Подписать наблюдателя"
self._observers.add(obs)
def detach(self, obs:Observer):
"Отписать наблюдателя"
self._observers.discard(obs)
def notify(self, event:MazeEvent):
for obs in self._observers:
obs.update(event)

View File

@ -0,0 +1,46 @@
import heapq
from itertools import count
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
from task2.strategyObjects.util import restorePath
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
from task2.mazeObjects.path import Path
class AStar(PathFindingStrategy):
"""Алгоритм с эвристикой (etc. манхэттенское расстояние) компромисс между скоростью и оптимальностью."""
def heuristic(self, first: Cell, second: Cell) -> int:
return abs(first.x - second.x) + abs(first.y - second.y)
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
tie_breaker = count()
start_heuristic = self.heuristic(start, exit)
heap: list[tuple[int, int, int, Cell]] = [
(start_heuristic, start_heuristic, next(tie_breaker), start)
]
g_score: dict[Cell, int] = {start: 0}
parents: dict[Cell, Cell | None] = {start: None}
visited: set[Cell] = set()
while heap:
_, _, _, current = heapq.heappop(heap)
if current in visited:
continue
visited.add(current)
if current.isExit:
return Path(restorePath(parents, exit), len(visited))
for neighbor in maze.getNeighbors(current):
tentative_score = g_score[current]
if tentative_score < g_score.get(neighbor, 10**12):
g_score[neighbor] = tentative_score
parents[neighbor] = current
heuristic = self.heuristic(neighbor, exit)
priority = tentative_score + heuristic
heapq.heappush(
heap,
(priority, heuristic, next(tie_breaker), neighbor),
)
return Path(None, len(visited))

View File

@ -0,0 +1,43 @@
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
from task2.strategyObjects.util import restorePath
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
from task2.mazeObjects.path import Path
import queue
class BFS(PathFindingStrategy):
"""Поиск в ширину гарантирует кратчайший путь по количеству шагов.
Возвращает None, если пути нет"""
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
visited = dict()
parents = dict()
q = queue.Queue()
q.put(start)
visited[start] = 0
parents[start] = None
found_exit = False
while not q.empty():
current = q.get()
# Условие нахождение выхода
if current.isExit:
found_exit = True
break
# Перебор соседей
for hood in maze.getNeighbors(current):
if hood in visited:
continue
visited[hood] = visited[current] + 1
parents[hood] = current
q.put(hood)
if not found_exit:
path_list = None
else:
path_list = restorePath(parents, exit)
return Path(path_list, len(visited))

View File

@ -0,0 +1,41 @@
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
from task2.strategyObjects.util import restorePath
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
from task2.mazeObjects.path import Path
class DFS(PathFindingStrategy):
"""Поиск в глубину быстрый, но не обязательно кратчайший.
Возвращает None, если пути нет"""
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
visited = dict()
parents = dict()
stack = []
stack.append(start)
visited[start] = 0
parents[start] = None
found_exit = False
while stack:
current = stack.pop()
# Условие нахождение выхода
if current.isExit:
found_exit = True
break
# Перебор соседей
for hood in maze.getNeighbors(current):
if hood in visited:
continue
visited[hood] = visited[current] + 1
parents[hood] = current
stack.append(hood)
if not found_exit:
path_list = None
else:
path_list = restorePath(parents, exit)
return Path(path_list, len(visited))

View File

@ -0,0 +1,14 @@
from abc import ABC, abstractmethod
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
from task2.mazeObjects.path import Path
class PathFindingStrategy(ABC):
"""Интерфейс PathFindingStrategy с методом findPath(maze, start, exit),
возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет."""
@abstractmethod
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
"""Возвращает список клеток пути от старта до выхода включительно. Пути нет - пустой список."""
raise NotImplementedError

View File

@ -0,0 +1,12 @@
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
def restorePath(parents: dict, exit: Cell) -> list[Cell]|None:
path = []
current = exit
while current:
path.append(current)
if current not in parents:
return None
current = parents[current]
return path[::-1]

84
MusinAA/task2/tester.py Normal file
View File

@ -0,0 +1,84 @@
from task2.mazeBuilder import MazeBuilder
from task2.mazeObjects.maze import Maze
from task2.mazeSolver import MazeSolver, SearchStats
from task2.strategyObjects.BFS import BFS
from task2.strategyObjects.DFS import DFS
from task2.strategyObjects.AStar import AStar
import csv
import os
TEST_ITERATIONS = 10
class Tester():
"""Для каждого лабиринта и каждой стратегии запустить solve() 510 раз,
усреднить время, количество посещённых клеток, длину пути.
Записать результаты в CSV:
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути."""
result:list[SearchStats]
def __init__(self, builder:MazeBuilder, writefile:str):
self._builder = builder
self.writefile = "../" + writefile
def setTestingDirectory(self, directory:str):
if directory[-1] != "/":
directory += "/"
self._directory = "../" + directory
def _getMazes(self) -> list[Maze]:
arr = []
files = os.listdir(self._directory)
only_txt_files = [f for f in files if os.path.isfile(os.path.join(self._directory, f)) and os.path.splitext(f)[1] == ".txt"]
for f in only_txt_files:
arr.append(self._builder.buildFromFile(os.path.join(self._directory, f)))
return arr
def _solveAvg(self, solver: MazeSolver):
avgtime = 0
for i in range(TEST_ITERATIONS):
result = solver.solve()
# Всё кроме времени будет одинаковым
avgtime += result.duration/TEST_ITERATIONS
result.duration = avgtime
return result
def saveCSV(self):
rows = []
for r in self.result:
r = r.toDict()
row = (r["strategy_name"],
r["maze_name"],
r["duration"],
r["visited_cells"],
r["path_len"]
)
rows.append(row)
with open(self.writefile, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Алгоритм", "Лабиринт", "Время (мс)", "Посещённые клетки", "Длинна пути"])
writer.writerows(rows)
def test(self):
self.result = []
arr = self._getMazes()
for algoritm in (BFS, DFS, AStar):
solver = MazeSolver(algoritm()) # это прикол
for maze in arr:
solver.setMaze(maze)
self.result.append(self._solveAvg(solver))
self.result[-1].maze_name = maze.name
return self.result
if __name__ == "__main__":
exit()
from task2.mazeBuilder import TextFileMazeBuilder
builder = TextFileMazeBuilder()
tester = Tester(builder, "docs/data/task2/results.csv")
tester.setTestingDirectory("task2/mazeExamples")
tester.test()
tester.saveCSV()