[0] initial commit #388

Open
NeklyudovMS wants to merge 1 commits from NeklyudovMS/2026-rff_mp:NeklyudovMS into develop
38 changed files with 2544 additions and 0 deletions

0
NeklyudovMS/428b.md Normal file
View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -0,0 +1,31 @@
Структура,Режим,Вставка,Поиск,Удаление
Связанный список,Cлучайный,0.07007737499999989,0.0006544170000000182,0.0003327920000000262
Хэш-таблица,Cлучайный,0.043278625000000126,0.0005041249999999664,0.00023345800000007522
Бинарное дерево,Cлучайный,0.008526416000000037,9.408300000002257e-05,7.983400000011187e-05
Связанный список,Отсортированный,0.13493312499999988,0.0012999580000001565,0.0006884169999998857
Хэш-таблица,Отсортированный,0.048969957999999814,0.0005084579999998784,0.00024320799999988374
Бинарное дерево,Отсортированный,0.103865667,0.0009378749999999769,0.000703750000000003
Связанный список,Cлучайный,0.06731966599999994,0.000668082999999875,0.0004018329999999626
Хэш-таблица,Cлучайный,0.043154374999999856,0.0004823340000001952,0.00024004199999994036
Бинарное дерево,Cлучайный,0.008746917000000076,9.750000000008363e-05,7.454100000003905e-05
Связанный список,Отсортированный,0.13605504099999988,0.0013472089999999604,0.0007064999999997212
Хэш-таблица,Отсортированный,0.044353584,0.0005022089999999757,0.0002355419999999775
Бинарное дерево,Отсортированный,0.0875441669999999,0.0009309160000001704,0.0005734159999999378
Связанный список,Cлучайный,0.06584004200000004,0.0006574590000001379,0.0003679170000001619
Хэш-таблица,Cлучайный,0.04198924999999987,0.00046887499999970217,0.00022008299999987102
Бинарное дерево,Cлучайный,0.008201666000000163,9.141600000006633e-05,6.500000000020378e-05
Связанный список,Отсортированный,0.131494,0.0013212089999998788,0.0007440840000003668
Хэш-таблица,Отсортированный,0.04231241699999977,0.00045720899999990294,0.0002253330000003828
Бинарное дерево,Отсортированный,0.08590329100000016,0.0008908750000000687,0.0006914590000000054
Связанный список,Cлучайный,0.06636370799999991,0.0006459999999997024,0.000381874999999976
Хэш-таблица,Cлучайный,0.04220029199999997,0.0004550420000000166,0.0002180420000001959
Бинарное дерево,Cлучайный,0.008514166999999961,9.845800000007898e-05,7.695799999973829e-05
Связанный список,Отсортированный,0.13364512500000014,0.0013258340000001922,0.0007597920000002034
Хэш-таблица,Отсортированный,0.04282124999999981,0.00046591600000001065,0.00022437500000016541
Бинарное дерево,Отсортированный,0.0862158329999998,0.0009080829999996709,0.0006712090000000615
Связанный список,Cлучайный,0.06781329099999978,0.0006859999999999644,0.0004325419999999802
Хэш-таблица,Cлучайный,0.04273579199999977,0.0004545839999998691,0.00022783399999992682
Бинарное дерево,Cлучайный,0.008394375000000398,9.254099999989052e-05,7.054100000036811e-05
Связанный список,Отсортированный,0.13225183399999985,0.0013435840000002308,0.0007666250000002428
Хэш-таблица,Отсортированный,0.04290666599999993,0.0005269579999995777,0.0002517500000003281
Бинарное дерево,Отсортированный,0.08622245900000003,0.0009014160000000437,0.0005397080000002497
1 Структура Режим Вставка Поиск Удаление
2 Связанный список Cлучайный 0.07007737499999989 0.0006544170000000182 0.0003327920000000262
3 Хэш-таблица Cлучайный 0.043278625000000126 0.0005041249999999664 0.00023345800000007522
4 Бинарное дерево Cлучайный 0.008526416000000037 9.408300000002257e-05 7.983400000011187e-05
5 Связанный список Отсортированный 0.13493312499999988 0.0012999580000001565 0.0006884169999998857
6 Хэш-таблица Отсортированный 0.048969957999999814 0.0005084579999998784 0.00024320799999988374
7 Бинарное дерево Отсортированный 0.103865667 0.0009378749999999769 0.000703750000000003
8 Связанный список Cлучайный 0.06731966599999994 0.000668082999999875 0.0004018329999999626
9 Хэш-таблица Cлучайный 0.043154374999999856 0.0004823340000001952 0.00024004199999994036
10 Бинарное дерево Cлучайный 0.008746917000000076 9.750000000008363e-05 7.454100000003905e-05
11 Связанный список Отсортированный 0.13605504099999988 0.0013472089999999604 0.0007064999999997212
12 Хэш-таблица Отсортированный 0.044353584 0.0005022089999999757 0.0002355419999999775
13 Бинарное дерево Отсортированный 0.0875441669999999 0.0009309160000001704 0.0005734159999999378
14 Связанный список Cлучайный 0.06584004200000004 0.0006574590000001379 0.0003679170000001619
15 Хэш-таблица Cлучайный 0.04198924999999987 0.00046887499999970217 0.00022008299999987102
16 Бинарное дерево Cлучайный 0.008201666000000163 9.141600000006633e-05 6.500000000020378e-05
17 Связанный список Отсортированный 0.131494 0.0013212089999998788 0.0007440840000003668
18 Хэш-таблица Отсортированный 0.04231241699999977 0.00045720899999990294 0.0002253330000003828
19 Бинарное дерево Отсортированный 0.08590329100000016 0.0008908750000000687 0.0006914590000000054
20 Связанный список Cлучайный 0.06636370799999991 0.0006459999999997024 0.000381874999999976
21 Хэш-таблица Cлучайный 0.04220029199999997 0.0004550420000000166 0.0002180420000001959
22 Бинарное дерево Cлучайный 0.008514166999999961 9.845800000007898e-05 7.695799999973829e-05
23 Связанный список Отсортированный 0.13364512500000014 0.0013258340000001922 0.0007597920000002034
24 Хэш-таблица Отсортированный 0.04282124999999981 0.00046591600000001065 0.00022437500000016541
25 Бинарное дерево Отсортированный 0.0862158329999998 0.0009080829999996709 0.0006712090000000615
26 Связанный список Cлучайный 0.06781329099999978 0.0006859999999999644 0.0004325419999999802
27 Хэш-таблица Cлучайный 0.04273579199999977 0.0004545839999998691 0.00022783399999992682
28 Бинарное дерево Cлучайный 0.008394375000000398 9.254099999989052e-05 7.054100000036811e-05
29 Связанный список Отсортированный 0.13225183399999985 0.0013435840000002308 0.0007666250000002428
30 Хэш-таблица Отсортированный 0.04290666599999993 0.0005269579999995777 0.0002517500000003281
31 Бинарное дерево Отсортированный 0.08622245900000003 0.0009014160000000437 0.0005397080000002497

View File

@ -0,0 +1,16 @@
Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути
BFS,10x10,0.3340541999999891,53,23
BFS,50x50,3.734349999999953,640,427
BFS,5x5,0.07042079999999284,8,7
BFS,25x25,1.392629199999984,232,173
BFS,100x100,12.758183300000004,2495,1171
DFS,10x10,0.07979999999996323,35,31
DFS,50x50,2.704262400000035,995,435
DFS,5x5,0.019816699999952725,8,7
DFS,25x25,0.8116916999999724,316,173
DFS,100x100,8.950549999999957,3219,1243
AStar,10x10,0.07624989999999165,23,23
AStar,50x50,1.6550292000000022,496,427
AStar,5x5,0.023187600000018627,8,7
AStar,25x25,0.6458209000000048,186,177
AStar,100x100,4.468516600000005,1286,1171
1 Алгоритм Лабиринт Время (мс) Посещённые клетки Длинна пути
2 BFS 10x10 0.3340541999999891 53 23
3 BFS 50x50 3.734349999999953 640 427
4 BFS 5x5 0.07042079999999284 8 7
5 BFS 25x25 1.392629199999984 232 173
6 BFS 100x100 12.758183300000004 2495 1171
7 DFS 10x10 0.07979999999996323 35 31
8 DFS 50x50 2.704262400000035 995 435
9 DFS 5x5 0.019816699999952725 8 7
10 DFS 25x25 0.8116916999999724 316 173
11 DFS 100x100 8.950549999999957 3219 1243
12 AStar 10x10 0.07624989999999165 23 23
13 AStar 50x50 1.6550292000000022 496 427
14 AStar 5x5 0.023187600000018627 8 7
15 AStar 25x25 0.6458209000000048 186 177
16 AStar 100x100 4.468516600000005 1286 1171

View File

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

View File

View File

@ -0,0 +1,90 @@
from typing import Optional
"""
Двоичное дерево поиска
Узел словарь:
{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.
"""
def bst_insert(root: Optional[dict], 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: Optional[dict], name: str) -> Optional[str]:
"""Поиск в ширину."""
node = find_node_to_delete(root, name)
if node != None:
return node['phone']
def find_node_to_delete(root: Optional[dict], name: str) -> Optional[dict]:
"""Поиск в ширину."""
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) -> Optional[dict]:
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,57 @@
"""
Хеш-таблица
...
"""
from typing import Optional
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: Optional[list], 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: Optional[list], name: str) -> Optional[str]:
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,65 @@
from typing import Optional
"""
Связный список (LinkedListPhoneBook)
Узел представляется словарём:
{'name': 'Имя', 'phone': '123', 'next': None}.
"""
def ll_insert(head : Optional[dict], 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 : Optional[dict], name: str) -> Optional[str]:
"""Ищет узел, возвращает телефон или None."""
currentNode = head
while currentNode != None:
if currentNode['name'] == name:
return currentNode['phone']
currentNode = currentNode['next']
return None
def ll_delete(head : Optional[dict], name: str) -> Optional[dict]:
"""Удаляет узел, возвращает новую голову."""
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: Optional[dict]) -> 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
}

View File

View File

@ -0,0 +1,95 @@
"""
Реализовать класс ConsoleView, который отображает лабиринт,
текущее положение игрока (если реализован пошаговый режим) и найденный путь.
Метод render(maze, player_position, path) рисует карту в консоли."""
import os
from typing import Optional
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:Optional[Maze]
path:Optional[Path]
def __init__(self, maze:Optional[Maze]=None, path:Optional[Path]=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,74 @@
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):
self.start = None
self.end = None
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

@ -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']) if start else None
self.endCell = self.getCell(end['x'], end['y']) if end else None
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,7 @@
from typing import Optional
from task2.mazeObjects.cell import Cell
class Path:
def __init__(self, array:Optional[list[Cell]], visited_cells:int):
self.array = array
self.visited_cells = visited_cells

View File

@ -0,0 +1,70 @@
from typing import Optional
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: Optional[list[Cell]], 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:Optional[Maze]=None):
super().__init__()
self._maze = maze
self.strategy = strategy
def setMaze(self, maze: Optional[Maze]):
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
if not self._maze.startCell or not self._maze.endCell:
return SearchStats(None, 0.0, 0, -1, self.getStrategyName())
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,47 @@
import heapq
from itertools import count
from typing import Optional
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, Optional[Cell]] = {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,13 @@
from typing import Optional
from task2.mazeObjects.maze import Maze
from task2.mazeObjects.cell import Cell
def restorePath(parents: dict, exit: Cell) -> Optional[list[Cell]]:
path = []
current = exit
while current:
path.append(current)
if current not in parents:
return None
current = parents[current]
return path[::-1]

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()