forked from UNN/2026-rff_mp
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 82e988c965 | |||
| 58daf860ed | |||
| 566d89fda2 | |||
| c7229154ca | |||
| 6e4ae1835b | |||
|
|
25341dc814 | ||
| fe9ce65eb2 | |||
| 405d1e583b | |||
| 74807f5514 | |||
| 3a251f06c7 | |||
| e4c7e2d97a | |||
|
|
7e84caffc4 | ||
| 84e5d1e763 |
1
MalkinMV/428b.md
Normal file
1
MalkinMV/428b.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
428b
|
||||||
2
MusinAA/.gitignore
vendored
2
MusinAA/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
.vscode/
|
|
||||||
*/tests/
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 71 KiB |
|
|
@ -1,31 +0,0 @@
|
||||||
Структура,Режим,Вставка,Поиск,Удаление
|
|
||||||
Связанный список,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,88 +0,0 @@
|
||||||
"""
|
|
||||||
Двоичное дерево поиска
|
|
||||||
|
|
||||||
Узел — словарь:
|
|
||||||
{'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'])
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
"""
|
|
||||||
Хеш-таблица
|
|
||||||
|
|
||||||
Хранится как список 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])
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
"""
|
|
||||||
Связный список (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
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
183
README.md
183
README.md
|
|
@ -197,3 +197,186 @@ with open("results.csv", "w", newline="") as f:
|
||||||
- Как удаление работает в каждой структуре.
|
- Как удаление работает в каждой структуре.
|
||||||
|
|
||||||
* Вывод должен содержать ответ на вопрос: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни.*
|
* Вывод должен содержать ответ на вопрос: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни.*
|
||||||
|
|
||||||
|
## Задание: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)
|
||||||
|
|
||||||
|
### Цель работы
|
||||||
|
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
|
||||||
|
|
||||||
|
### Общая схема приложения (пример)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
classDiagram
|
||||||
|
class Maze {
|
||||||
|
-Cell[] cells
|
||||||
|
-int width, height
|
||||||
|
-Cell start
|
||||||
|
-Cell exit
|
||||||
|
+getCell(x,y): Cell
|
||||||
|
+getNeighbors(cell): List~Cell~
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cell {
|
||||||
|
-int x, y
|
||||||
|
-bool isWall
|
||||||
|
-bool isStart
|
||||||
|
-bool isExit
|
||||||
|
+isPassable(): bool
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeBuilder {
|
||||||
|
<<interface>>
|
||||||
|
+buildFromFile(filename): Maze
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextFileMazeBuilder {
|
||||||
|
+buildFromFile(filename): Maze
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathFindingStrategy {
|
||||||
|
<<interface>>
|
||||||
|
+findPath(maze, start, exit): List~Cell~
|
||||||
|
}
|
||||||
|
|
||||||
|
class BFSStrategy
|
||||||
|
class DFSStrategy
|
||||||
|
class AStarStrategy
|
||||||
|
class DijkstraStrategy
|
||||||
|
|
||||||
|
class SearchStats {
|
||||||
|
+timeMs: float
|
||||||
|
+visitedCells: int
|
||||||
|
+pathLength: int
|
||||||
|
}
|
||||||
|
|
||||||
|
class MazeSolver {
|
||||||
|
-Maze maze
|
||||||
|
-PathFindingStrategy strategy
|
||||||
|
+setStrategy(strategy)
|
||||||
|
+solve(): SearchStats
|
||||||
|
}
|
||||||
|
|
||||||
|
class Command {
|
||||||
|
<<interface>>
|
||||||
|
+execute()
|
||||||
|
+undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveCommand {
|
||||||
|
-Player player
|
||||||
|
-Direction dir
|
||||||
|
-Cell previousCell
|
||||||
|
+execute()
|
||||||
|
+undo()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
-Cell currentCell
|
||||||
|
+moveTo(cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Observer {
|
||||||
|
<<interface>>
|
||||||
|
+update(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleView {
|
||||||
|
+update(event)
|
||||||
|
+render(maze, player, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
MazeBuilder <|.. TextFileMazeBuilder
|
||||||
|
MazeBuilder --> Maze : creates
|
||||||
|
PathFindingStrategy <|.. BFSStrategy
|
||||||
|
PathFindingStrategy <|.. DFSStrategy
|
||||||
|
PathFindingStrategy <|.. AStarStrategy
|
||||||
|
PathFindingStrategy <|.. DijkstraStrategy
|
||||||
|
MazeSolver --> PathFindingStrategy : uses
|
||||||
|
MazeSolver --> Maze : uses
|
||||||
|
Command <|.. MoveCommand
|
||||||
|
MoveCommand --> Player
|
||||||
|
Player --> Cell
|
||||||
|
Observer <|.. ConsoleView
|
||||||
|
MazeSolver --> Observer : notifies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Выполнение
|
||||||
|
|
||||||
|
#### Этап 1. Модель лабиринта (без паттернов, просто классы)
|
||||||
|
**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта.
|
||||||
|
- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена).
|
||||||
|
- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` – возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена).
|
||||||
|
|
||||||
|
**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем.
|
||||||
|
|
||||||
|
#### Этап 2. Загрузка лабиринта из файла – применение паттерна **Builder**
|
||||||
|
**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` – стена, ` ` (пробел) – проход, `S` – старт, `E` – выход.
|
||||||
|
- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`.
|
||||||
|
- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`.
|
||||||
|
|
||||||
|
Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`.
|
||||||
|
|
||||||
|
#### Этап 3. Стратегии поиска пути – паттерн **Strategy**
|
||||||
|
**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода.
|
||||||
|
- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.
|
||||||
|
- Реализовать минимум 3 стратегии:
|
||||||
|
- **BFS** (поиск в ширину) – гарантирует кратчайший путь по количеству шагов.
|
||||||
|
- **DFS** (поиск в глубину) – быстрый, но не обязательно кратчайший.
|
||||||
|
- **A*** (с эвристикой, например, манхэттенское расстояние) – компромисс между скоростью и оптимальностью.
|
||||||
|
- (Опционально) **Дейкстра** – полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS.
|
||||||
|
|
||||||
|
Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A* – приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток.
|
||||||
|
|
||||||
|
Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс.
|
||||||
|
|
||||||
|
#### Этап 4. Класс-оркестратор – **MazeSolver** (использует Strategy)
|
||||||
|
**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику.
|
||||||
|
- `MazeSolver` содержит поля `maze` и `strategy`.
|
||||||
|
- Метод `setStrategy(strategy)` для динамической смены алгоритма.
|
||||||
|
- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути).
|
||||||
|
- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии.
|
||||||
|
|
||||||
|
#### Этап 5. Визуализация и пошаговое управление – паттерны **Observer** и **Command** (по желанию)
|
||||||
|
**5.1. Наблюдатель (Observer)** – обновление консольного интерфейса.
|
||||||
|
- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`).
|
||||||
|
- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли.
|
||||||
|
- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния.
|
||||||
|
|
||||||
|
**5.2. Команда (Command)** – для пошагового перемещения игрока по найденному пути (или ручного управления).
|
||||||
|
- Создать интерфейс `Command` с методами `execute()` и `undo()`.
|
||||||
|
- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены.
|
||||||
|
- Создать класс `Player`, хранящий текущую клетку.
|
||||||
|
- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн.
|
||||||
|
|
||||||
|
*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command – для демонстрации undo при ручном исследовании лабиринта.*
|
||||||
|
|
||||||
|
#### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных)
|
||||||
|
**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности.
|
||||||
|
1. **Подготовка тестовых лабиринтов:**
|
||||||
|
- Маленький (10×10) с простым путём.
|
||||||
|
- Средний (50×50) с тупиками.
|
||||||
|
- Большой (100×100) с запутанной структурой.
|
||||||
|
- «Пустой» лабиринт (без стен) – для демонстрации максимальной производительности.
|
||||||
|
- «Без выхода» – чтобы проверить обработку отсутствия пути.
|
||||||
|
2. **Замеры:**
|
||||||
|
- Для каждого лабиринта и каждой стратегии запустить `solve()` 5–10 раз, усреднить время, количество посещённых клеток, длину пути.
|
||||||
|
- Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`.
|
||||||
|
3. **Анализ:**
|
||||||
|
- Построить графики для каждого лабиринта.
|
||||||
|
- Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях).
|
||||||
|
|
||||||
|
4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото – вес 3, песок – вес 2, асфальт – вес 1) и сравнить Дейкстру с A* на взвешенном графе.
|
||||||
|
|
||||||
|
#### Этап 7. Отчёт
|
||||||
|
**Структура отчёта:**
|
||||||
|
1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid).
|
||||||
|
2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий.
|
||||||
|
3. Результаты экспериментов (таблицы, графики).
|
||||||
|
4. Анализ эффективности алгоритмов и применимости паттернов.
|
||||||
|
5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них.
|
||||||
|
|
||||||
|
### Советы
|
||||||
|
- Для A* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`.
|
||||||
|
- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь.
|
||||||
|
- Для BFS/DFS используй `deque` (очередь) и `list` (стек).
|
||||||
|
- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки.
|
||||||
|
|
|
||||||
0
kalinovskiymi/428
Normal file
0
kalinovskiymi/428
Normal file
6
osipovamd/428.md
Normal file
6
osipovamd/428.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{\rtf1\ansi\ansicpg1251\cocoartf2869
|
||||||
|
\cocoatextscaling0\cocoaplatform0{\fonttbl}
|
||||||
|
{\colortbl;\red255\green255\blue255;}
|
||||||
|
{\*\expandedcolortbl;;}
|
||||||
|
\paperw11900\paperh16840\margl1440\margr1440\vieww11520\viewh8400\viewkind0
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user