Merge pull request '[1,2] 1 и 2 лабы' (#173) from musinaa/2026-rff_mp:task-1 into develop

Reviewed-on: #173
This commit is contained in:
kit8nino 2026-05-30 11:26:01 +00:00
commit e87bdd2808
39 changed files with 2426 additions and 0 deletions

2
MusinAA/.gitignore vendored Normal file
View File

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

349
MusinAA/docs/Report 1.ipynb Normal file

File diff suppressed because one or more lines are too long

903
MusinAA/docs/Report 2.ipynb Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -0,0 +1,31 @@
Структура,Режим,Вставка,Поиск,Удаление
Связанный список,Cлучайный,0.09829891600020346,0.0007068659997457871,0.0005386180000641616
Хэш-таблица,Cлучайный,0.04794800999934523,0.00069890399936412,0.00033887100016727345
Бинарное дерево,Cлучайный,0.014146466999591212,0.00019723300010809908,0.00022258500030147843
Связанный список,Отсортированный,0.16592630900049699,0.0017924130006576888,0.001010537000183831
Хэш-таблица,Отсортированный,0.04675658399992244,0.000497691000418854,0.00027706199944077525
Бинарное дерево,Отсортированный,0.098506346999784,0.001621370999600913,0.0008596789994044229
Связанный список,Cлучайный,0.07528530299987324,0.0006713170005241409,0.0004351130000941339
Хэш-таблица,Cлучайный,0.04169118899972091,0.0004370679998828564,0.0002442360000713961
Бинарное дерево,Cлучайный,0.009762656000020797,0.0001406600003974745,8.869900011632126e-05
Связанный список,Отсортированный,0.15083865700034949,0.001965620000191848,0.0009268670000892598
Хэш-таблица,Отсортированный,0.04658651899990218,0.0004731760000140639,0.00026295399948139675
Бинарное дерево,Отсортированный,0.10888835700006894,0.0032681640004739165,0.0010110960001838976
Связанный список,Cлучайный,0.09252672599996004,0.0014638780003224383,0.0009516599993730779
Хэш-таблица,Cлучайный,0.04701576600018598,0.0004413979995661066,0.00024472499990224605
Бинарное дерево,Cлучайный,0.010519597999518737,0.00015120700027182465,0.00012815900026907912
Связанный список,Отсортированный,0.15883956299967394,0.001480011000239756,0.0007378059999609832
Хэш-таблица,Отсортированный,0.043343710000044666,0.0005192710004848777,0.0002623249993121135
Бинарное дерево,Отсортированный,0.19170180800028902,0.0011184409995621536,0.0008248280000771047
Связанный список,Cлучайный,0.09595573600017815,0.0009538959993733442,0.0004928719999952591
Хэш-таблица,Cлучайный,0.04453241200008051,0.000944256999900972,0.0005029280000599101
Бинарное дерево,Cлучайный,0.011908257000868616,0.0001221530001203064,0.00011502899997140048
Связанный список,Отсортированный,0.16769071699945926,0.0015361639998445753,0.0011414199998398544
Хэш-таблица,Отсортированный,0.05018426599963277,0.0006002179998176871,0.000283696000224154
Бинарное дерево,Отсортированный,0.09999411199987662,0.0010742320000645122,0.0009550129998388002
Связанный список,Cлучайный,0.08812657299949933,0.0006700599997202517,0.0006053869992683758
Хэш-таблица,Cлучайный,0.042967892999513424,0.0005705349994968856,0.0002917279998655431
Бинарное дерево,Cлучайный,0.01326883900037501,0.00013954399946669582,0.00013297800069267396
Связанный список,Отсортированный,0.16893773900028464,0.0017602859998078202,0.0007569420004074345
Хэш-таблица,Отсортированный,0.05997269399995275,0.000543855999239895,0.0002741980006248923
Бинарное дерево,Отсортированный,0.11176624800009449,0.0010512540002309834,0.0007160159993873094
1 Структура Режим Вставка Поиск Удаление
2 Связанный список Cлучайный 0.09829891600020346 0.0007068659997457871 0.0005386180000641616
3 Хэш-таблица Cлучайный 0.04794800999934523 0.00069890399936412 0.00033887100016727345
4 Бинарное дерево Cлучайный 0.014146466999591212 0.00019723300010809908 0.00022258500030147843
5 Связанный список Отсортированный 0.16592630900049699 0.0017924130006576888 0.001010537000183831
6 Хэш-таблица Отсортированный 0.04675658399992244 0.000497691000418854 0.00027706199944077525
7 Бинарное дерево Отсортированный 0.098506346999784 0.001621370999600913 0.0008596789994044229
8 Связанный список Cлучайный 0.07528530299987324 0.0006713170005241409 0.0004351130000941339
9 Хэш-таблица Cлучайный 0.04169118899972091 0.0004370679998828564 0.0002442360000713961
10 Бинарное дерево Cлучайный 0.009762656000020797 0.0001406600003974745 8.869900011632126e-05
11 Связанный список Отсортированный 0.15083865700034949 0.001965620000191848 0.0009268670000892598
12 Хэш-таблица Отсортированный 0.04658651899990218 0.0004731760000140639 0.00026295399948139675
13 Бинарное дерево Отсортированный 0.10888835700006894 0.0032681640004739165 0.0010110960001838976
14 Связанный список Cлучайный 0.09252672599996004 0.0014638780003224383 0.0009516599993730779
15 Хэш-таблица Cлучайный 0.04701576600018598 0.0004413979995661066 0.00024472499990224605
16 Бинарное дерево Cлучайный 0.010519597999518737 0.00015120700027182465 0.00012815900026907912
17 Связанный список Отсортированный 0.15883956299967394 0.001480011000239756 0.0007378059999609832
18 Хэш-таблица Отсортированный 0.043343710000044666 0.0005192710004848777 0.0002623249993121135
19 Бинарное дерево Отсортированный 0.19170180800028902 0.0011184409995621536 0.0008248280000771047
20 Связанный список Cлучайный 0.09595573600017815 0.0009538959993733442 0.0004928719999952591
21 Хэш-таблица Cлучайный 0.04453241200008051 0.000944256999900972 0.0005029280000599101
22 Бинарное дерево Cлучайный 0.011908257000868616 0.0001221530001203064 0.00011502899997140048
23 Связанный список Отсортированный 0.16769071699945926 0.0015361639998445753 0.0011414199998398544
24 Хэш-таблица Отсортированный 0.05018426599963277 0.0006002179998176871 0.000283696000224154
25 Бинарное дерево Отсортированный 0.09999411199987662 0.0010742320000645122 0.0009550129998388002
26 Связанный список Cлучайный 0.08812657299949933 0.0006700599997202517 0.0006053869992683758
27 Хэш-таблица Cлучайный 0.042967892999513424 0.0005705349994968856 0.0002917279998655431
28 Бинарное дерево Cлучайный 0.01326883900037501 0.00013954399946669582 0.00013297800069267396
29 Связанный список Отсортированный 0.16893773900028464 0.0017602859998078202 0.0007569420004074345
30 Хэш-таблица Отсортированный 0.05997269399995275 0.000543855999239895 0.0002741980006248923
31 Бинарное дерево Отсортированный 0.11176624800009449 0.0010512540002309834 0.0007160159993873094

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,63 @@
"""
Связный список (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:
if currentNode['name'] == name:
currentNode['phone'] = phone
return head
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()