Merge pull request '[12] test пуш' (#339) from shapovalovka/2026-rff_mp:ShapovalovKA into develop

Reviewed-on: #339
This commit is contained in:
git_admin 2026-05-30 11:15:16 +00:00
commit be1af7bf6c
32 changed files with 1918 additions and 0 deletions

0
ShapovalovKA/425.md Normal file
View File

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,48 @@
import csv
import matplotlib.pyplot as plt
array_arr = []
array_list = []
array_hash = []
array_bin = []
with open('results.csv', 'r', encoding='utf-8-sig') as file:
reader = csv.reader(file, delimiter=';')
next(reader) # пропускаем заголовок
values = []
for row in reader:
values.append(float(row[3]))
array_arr = values[0:4]
array_list = values[4:8]
array_hash = values[8:12]
array_bin = values[12:16]
print(f"array_arr : {array_arr}")
print(f"array_list: {array_list}")
print(f"array_hash: {array_hash}")
print(f"array_bin : {array_bin}")
l = [1, 2, 3, 4]
#визуализация без дерева
plt.plot(l, array_arr, label = 'Array', c='black')
plt.plot(l, array_list, label = 'Linked list', c='blue')
plt.plot(l, array_hash, label = 'Hash table', c='orange')
plt.ylabel('array') #название по y
plt.xlabel('l') #название по x
plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol = 3)
plt.savefig('t1p1.png', dpi=300, bbox_inches='tight') #сохранение в файле
plt.show()
#визуализация с деревом
plt.plot(l, array_arr, label = 'Array', c='black')
plt.plot(l, array_list, label = 'Linked list', c='blue')
plt.plot(l, array_hash, label = 'Hash table', c='orange')
plt.plot(l, array_bin, label = 'Binary tree', c='red')
plt.ylabel('array') #название по y
plt.xlabel('l') #название по x
plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol = 4)
plt.savefig('t1p2.png', dpi=300, bbox_inches='tight') #сохранение в файле
plt.show()

View File

@ -0,0 +1,9 @@
Структура;Режим;Операция;Время (сек)
Array;случайный;вставка (в начало);0.06431880006566644
Array;отсортированный;вставка (в начало);0.06380272014066576
Array;любой;поиск 110 записей;0.07721293987706304
Array;любой;удаление 50 записей (среднее);0.0018548803813755513
Linked list;случайный;вставка (в начало);0.01246960014104843
Linked list;отсортированный;вставка (в начало);0.007890580128878355
Linked list;любой;поиск 110 записей;0.23582311999052763
Linked list;любой;удаление 50 записей (среднее);0.0023578427862375973
1 Структура Режим Операция Время (сек)
2 Array случайный вставка (в начало) 0.06431880006566644
3 Array отсортированный вставка (в начало) 0.06380272014066576
4 Array любой поиск 110 записей 0.07721293987706304
5 Array любой удаление 50 записей (среднее) 0.0018548803813755513
6 Linked list случайный вставка (в начало) 0.01246960014104843
7 Linked list отсортированный вставка (в начало) 0.007890580128878355
8 Linked list любой поиск 110 записей 0.23582311999052763
9 Linked list любой удаление 50 записей (среднее) 0.0023578427862375973

View File

@ -0,0 +1,212 @@
import random
import time
import csv
# ---------- Реализация связного списка ----------
def ll_insert_begin(head, name, phone):
# Вставка узла в начало списка. Возвращает новую голову.
new_node = {'name': name, 'phone': phone, 'next': head}
return new_node
def ll_find(head, name):
# Поиск телефона по имени. Возвращает phone или None.
current = head
while current:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
# Удаление узла по имени. Возвращает новую голову.
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next']:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
# Собирает все записи в список и сортирует по имени.
records = []
current = head
while current:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
# ---------- Измерения для массива ----------
def array_insert_measure(records, sorted_flag=False):
# Вставка записей в начало массива. Возвращает время.
arr = []
start = time.perf_counter()
if sorted_flag:
# records уже отсортированы
for item in records:
arr.insert(0, item)
else:
for item in records:
arr.insert(0, item)
end = time.perf_counter()
return end - start
def array_find_measure(records, test_names):
# Поиск в массиве: линейный перебор.
start = time.perf_counter()
for name in test_names:
for rec in records:
if rec[0] == name:
break
end = time.perf_counter()
return end - start
def array_delete_measure(records, delete_names):
# Удаление из массива через создание нового списка (как в оригинале).
times = []
for name in delete_names:
start = time.perf_counter()
records = [rec for rec in records if rec[0] != name]
end = time.perf_counter()
times.append(end - start)
return sum(times) / len(times) if times else 0
# ---------- Измерения для связного списка ----------
def linked_insert_measure(records, sorted_flag=False):
# Вставка записей в начало связного списка. Возвращает время.
head = None
start = time.perf_counter()
# Если sorted_flag == True, records уже отсортированы, но для связного списка
# вставка в начало всегда O(1), порядок не влияет на время.
for name, phone in records:
head = ll_insert_begin(head, name, phone)
end = time.perf_counter()
return end - start
def linked_find_measure(head, test_names):
# Поиск в связном списке.
start = time.perf_counter()
for name in test_names:
ll_find(head, name)
end = time.perf_counter()
return end - start
def linked_delete_measure(head, delete_names):
# Удаление из связного списка.
times = []
for name in delete_names:
start = time.perf_counter()
head = ll_delete(head, name)
end = time.perf_counter()
times.append(end - start)
return sum(times) / len(times) if times else 0
# ---------- Основная функция эксперимента ----------
def main():
N = 10000
# Генерация тестовых данных
records = []
for i in range(N):
name = f"User_{i:05d}"
phone = f"8{random.randint(9000000000, 9999999999)}"
records.append((name, phone))
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
# Имена для поиска (100 существующих + 10 несуществующих)
existing_names = random.sample([rec[0] for rec in records], 100)
non_existing = [f"None_{i}" for i in range(10)]
test_names = existing_names + non_existing
# Имена для удаления (50 случайных)
delete_names = random.sample([rec[0] for rec in records], 50)
# Результаты будем собирать в список списков
results = [["Структура", "Режим", "Операция", "Время (сек)"]]
# ----- Массив -----
# Вставка (случайный порядок)
arr_time_shuffled = 0.0
arr_time_sorted = 0.0
for _ in range(5):
arr_time_shuffled += array_insert_measure(records_shuffled, sorted_flag=False)
arr_time_sorted += array_insert_measure(records_sorted, sorted_flag=True)
results.append(["Array", "случайный", "вставка (в начало)", arr_time_shuffled / 5])
results.append(["Array", "отсортированный", "вставка (в начало)", arr_time_sorted / 5])
# Поиск
find_time = 0.0
for _ in range(5):
find_time += array_find_measure(records, test_names)
results.append(["Array", "любой", "поиск 110 записей", find_time / 5])
# Удаление
del_time = 0.0
for _ in range(5):
del_time += array_delete_measure(records.copy(), delete_names)
results.append(["Array", "любой", "удаление 50 записей (среднее)", del_time / 5])
# ----- Связный список -----
# Вставка
ll_time_shuffled = 0.0
ll_time_sorted = 0.0
for _ in range(5):
ll_time_shuffled += linked_insert_measure(records_shuffled)
ll_time_sorted += linked_insert_measure(records_sorted)
results.append(["Linked list", "случайный", "вставка (в начало)", ll_time_shuffled / 5])
results.append(["Linked list", "отсортированный", "вставка (в начало)", ll_time_sorted / 5])
# Поиск (предварительно строим список)
head = None
for name, phone in records:
head = ll_insert_begin(head, name, phone)
find_time_ll = 0.0
for _ in range(5):
find_time_ll += linked_find_measure(head, test_names)
results.append(["Linked list", "любой", "поиск 110 записей", find_time_ll / 5])
# Удаление (копируем список для каждого замера)
del_time_ll = 0.0
for _ in range(5):
# Строим новую копию списка
h = None
for name, phone in records:
h = ll_insert_begin(h, name, phone)
del_time_ll += linked_delete_measure(h, delete_names)
results.append(["Linked list", "любой", "удаление 50 записей (среднее)", del_time_ll / 5])
# ----- Вывод результатов в единый столбец -----
print("\nРезультаты экспериментов (время в секундах):\n")
# Определяем максимальную ширину первого столбца для красивого выравнивания
col_widths = [max(len(str(row[i])) for row in results) for i in range(4)]
for row in results:
print(f"{row[0]:<{col_widths[0]}} {row[1]:<{col_widths[1]}} "
f"{row[2]:<{col_widths[2]}} {row[3]:<{col_widths[3]}}")
# ----- Запись результатов в CSV-файл -----
with open('results.csv', 'w', newline='', encoding='utf-8-sig') as csvfile:
writer = csv.writer(csvfile, delimiter = ';')
writer.writerows(results)
print("\nРезультаты сохранены в файл 'results.csv'.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,185 @@
import random
import time
import csv
import os
# --------------------- Реализация связного списка (взята из t1_1) ---------------------
def ll_insert_begin(head, name, phone):
# Вставка узла в начало списка. Возвращает новую голову.
new_node = {'name': name, 'phone': phone, 'next': head}
return new_node
def ll_find(head, name):
# Поиск телефона по имени. Возвращает phone или None.
current = head
while current:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
# Удаление узла по имени. Возвращает новую голову.
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next']:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
# Собирает все записи в список и сортирует по имени.
records = []
current = head
while current:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
# --------------------- Реализация хеш-таблицы ---------------------
class HashTable:
def __init__(self, size=2000):
self.size = size
self.buckets = [None] * size # каждый bucket — голова связного списка
def _hash(self, name):
# Простая хеш-функция: сумма кодов символов по модулю размера.
return sum(ord(ch) for ch in name) % self.size
def insert(self, name, phone):
index = self._hash(name)
# Вставляем в начало связного списка в данном bucket'е
self.buckets[index] = ll_insert_begin(self.buckets[index], name, phone)
def find(self, name):
index = self._hash(name)
return ll_find(self.buckets[index], name)
def delete(self, name):
index = self._hash(name)
self.buckets[index] = ll_delete(self.buckets[index], name)
def list_all(self):
# Собирает все записи из всех bucket'ов и сортирует по имени.
all_records = []
for head in self.buckets:
current = head
while current:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
# --------------------- Функции измерений ---------------------
def generate_data(N=10000):
records = []
for i in range(N):
name = f"User_{i:05d}"
phone = f"8{random.randint(9000000000, 9999999999)}"
records.append((name, phone))
return records
def measure_insert(records, sort_order='random'):
# Измеряет время вставки в хеш-таблицу.
# sort_order: 'random' или 'sorted' — порядок передаваемых записей.
ht = HashTable(size=2000)
start = time.perf_counter()
for name, phone in records:
ht.insert(name, phone)
end = time.perf_counter()
return end - start
def measure_find(records, test_names):
# Поиск 110 записей в уже заполненной хеш-таблице.
ht = HashTable(size=2000)
for name, phone in records:
ht.insert(name, phone)
start = time.perf_counter()
for name in test_names:
ht.find(name)
end = time.perf_counter()
return end - start
def measure_delete(records, delete_names):
# Удаление 50 записей из хеш-таблицы (среднее время одного удаления).
times = []
for name in delete_names:
ht = HashTable(size=2000)
for n, p in records:
ht.insert(n, p)
start = time.perf_counter()
ht.delete(name)
end = time.perf_counter()
times.append(end - start)
return sum(times) / len(times)
# --------------------- Основная функция ---------------------
def main():
N = 10000
records = generate_data(N)
# Перемешанные и отсортированные копии
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
# Имена для поиска (100 существующих + 10 несуществующих)
existing_names = random.sample([rec[0] for rec in records], 100)
non_existing = [f"None_{i}" for i in range(10)]
test_names = existing_names + non_existing
# Имена для удаления (50 случайных)
delete_names = random.sample([rec[0] for rec in records], 50)
# Замеры (по 5 повторений)
insert_shuffled_avg = 0.0
insert_sorted_avg = 0.0
find_avg = 0.0
delete_avg = 0.0
repeats = 5
for _ in range(repeats):
insert_shuffled_avg += measure_insert(records_shuffled, 'random')
insert_sorted_avg += measure_insert(records_sorted, 'sorted')
find_avg += measure_find(records, test_names)
delete_avg += measure_delete(records, delete_names)
insert_shuffled_avg /= repeats
insert_sorted_avg /= repeats
find_avg /= repeats
delete_avg /= repeats
# Подготовка строк для CSV
new_rows = [
["Hash table", "случайный", "вставка (в начало)", insert_shuffled_avg],
["Hash table", "отсортированный", "вставка (в начало)", insert_sorted_avg],
["Hash table", "любой", "поиск 110 записей", find_avg],
["Hash table", "любой", "удаление 50 записей (среднее)", delete_avg]
]
# Определяем имя CSV-файла (там же, где и t1_1.py)
csv_filename = "results.csv"
file_exists = os.path.isfile(csv_filename)
# Запись в CSV (добавление)
with open(csv_filename, 'a', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f, delimiter=';')
# Если файл только что создан, сначала запишем заголовок
if not file_exists:
writer.writerow(["Структура", "Режим", "Операция", "Время (сек)"])
writer.writerows(new_rows)
print("Результаты для хеш-таблицы добавлены в", csv_filename)
print(f"Среднее время вставки (случ. порядок): {insert_shuffled_avg:.6f} сек")
print(f"Среднее время вставки (отсорт.): {insert_sorted_avg:.6f} сек")
print(f"Среднее время поиска 110 записей: {find_avg:.6f} сек")
print(f"Среднее время удаления 50 записей: {delete_avg:.6f} сек")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,211 @@
import random
import time
import csv
import os
# --------------------- Реализация бинарного дерева поиска (итеративная) ---------------------
def bst_insert(root, name, phone):
#Итеративная вставка. Возвращает корень.
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = new_node
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = new_node
break
else:
current = current['right']
else: # имя уже существует — обновляем телефон
current['phone'] = phone
break
return root
def bst_find(root, name):
#Итеративный поиск. Возвращает phone или None.
current = root
while current:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def bst_find_min(node):
#Возвращает узел с минимальным ключом в поддереве.
while node['left']:
node = node['left']
return node
def bst_delete(root, name):
#Итеративное удаление. Возвращает новый корень.
# Сначала найдём удаляемый узел и его родителя
parent = None
current = root
while current and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None: # узел не найден
return root
# Случай 1: нет левого потомка
if current['left'] is None:
child = current['right']
# Случай 2: нет правого потомка
elif current['right'] is None:
child = current['left']
# Случай 3: два потомка
else:
# Находим минимальный узел в правом поддереве (преемник)
min_parent = current
min_node = current['right']
while min_node['left']:
min_parent = min_node
min_node = min_node['left']
# Копируем данные из min_node в current
current['name'], current['phone'] = min_node['name'], min_node['phone']
# Удаляем min_node (у него нет левого потомка)
if min_parent['left'] == min_node:
min_parent['left'] = min_node['right']
else:
min_parent['right'] = min_node['right']
return root
# Подсоединяем child к parent
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
return root
def bst_list_all(root):
#Итеративный симметричный обход (inorder) без рекурсии, используя стек.
result = []
stack = []
current = root
while stack or current:
while current:
stack.append(current)
current = current['left']
current = stack.pop()
result.append((current['name'], current['phone']))
current = current['right']
return result
# --------------------- Функции измерений ---------------------
def generate_data(N=10000):
records = []
for i in range(N):
name = f"User_{i:05d}"
phone = f"8{random.randint(9000000000, 9999999999)}"
records.append((name, phone))
return records
def measure_insert(records):
root = None
start = time.perf_counter()
for name, phone in records:
root = bst_insert(root, name, phone)
end = time.perf_counter()
return end - start
def measure_find(records, test_names):
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
start = time.perf_counter()
for name in test_names:
bst_find(root, name)
end = time.perf_counter()
return end - start
def measure_delete(records, delete_names):
times = []
for name in delete_names:
root = None
for n, p in records:
root = bst_insert(root, n, p)
start = time.perf_counter()
root = bst_delete(root, name)
end = time.perf_counter()
times.append(end - start)
return sum(times) / len(times)
def main():
N = 10000
records = generate_data(N)
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
existing_names = random.sample([rec[0] for rec in records], 100)
non_existing = [f"None_{i}" for i in range(10)]
test_names = existing_names + non_existing
delete_names = random.sample([rec[0] for rec in records], 50)
insert_shuffled_avg = 0.0
insert_sorted_avg = 0.0
find_avg = 0.0
delete_avg = 0.0
repeats = 5
for _ in range(repeats):
insert_shuffled_avg += measure_insert(records_shuffled)
insert_sorted_avg += measure_insert(records_sorted)
find_avg += measure_find(records, test_names)
delete_avg += measure_delete(records, delete_names)
insert_shuffled_avg /= repeats
insert_sorted_avg /= repeats
find_avg /= repeats
delete_avg /= repeats
new_rows = [
["Binary tree", "случайный", "вставка (корень)", insert_shuffled_avg],
["Binary tree", "отсортированный", "вставка (корень)", insert_sorted_avg],
["Binary tree", "любой", "поиск 110 записей", find_avg],
["Binary tree", "любой", "удаление 50 записей (среднее)", delete_avg]
]
csv_filename = "results.csv"
file_exists = os.path.isfile(csv_filename)
need_header = False
if file_exists:
with open(csv_filename, 'r', encoding='utf-8-sig') as f:
first_line = f.readline()
if not first_line.startswith("Структура"):
need_header = True
else:
need_header = True
with open(csv_filename, 'a', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f, delimiter=';')
if need_header:
writer.writerow(["Структура", "Режим", "Операция", "Время (сек)"])
writer.writerows(new_rows)
print("Результаты для двоичного дерева поиска добавлены в", csv_filename)
print(f"Среднее время вставки (случ. порядок): {insert_shuffled_avg:.6f} сек")
print(f"Среднее время вставки (отсорт.): {insert_sorted_avg:.6f} сек")
print(f"Среднее время поиска 110 записей: {find_avg:.6f} сек")
print(f"Среднее время удаления 50 записей: {delete_avg:.6f} сек")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,2 @@
t1_1.py -> t1_2.py -> t1_3.py
-> res.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,21 @@
maze_type;strategy;avg_time_ms;std_time_ms;avg_visited;avg_path_len;path_found
small_10x10_simple;BFS;0.187180;0.026335;19.000000;19.000000;True
small_10x10_simple;DFS;0.167600;0.006841;19.000000;19.000000;True
small_10x10_simple;A*;0.262300;0.029262;19.000000;19.000000;True
small_10x10_simple;Dijkstra;0.260840;0.008608;19.000000;19.000000;True
medium_50x50_deadends;BFS;3.563500;0.053603;380.000000;99.000000;True
medium_50x50_deadends;DFS;3.618520;0.082922;270.000000;219.000000;True
medium_50x50_deadends;A*;4.865660;0.017732;334.000000;99.000000;True
medium_50x50_deadends;Dijkstra;6.019060;0.037679;380.000000;99.000000;True
large_100x100_complex;BFS;8.644360;0.236037;886.000000;199.000000;True
large_100x100_complex;DFS;13.781640;2.087117;697.000000;511.000000;True
large_100x100_complex;A*;12.167040;0.334660;774.000000;199.000000;True
large_100x100_complex;Dijkstra;14.365940;0.236778;886.000000;199.000000;True
empty_50x50;BFS;24.584480;0.184147;2500.000000;99.000000;True
empty_50x50;DFS;182.315780;4.196306;2451.000000;2451.000000;True
empty_50x50;A*;42.602980;0.184895;2500.000000;99.000000;True
empty_50x50;Dijkstra;43.213780;0.745780;2500.000000;99.000000;True
no_exit_50x50;BFS;25.037680;0.572634;2496.000000;0.000000;False
no_exit_50x50;DFS;191.040920;3.180626;2496.000000;0.000000;False
no_exit_50x50;A*;42.158280;0.396219;2496.000000;0.000000;False
no_exit_50x50;Dijkstra;42.499100;0.482887;2496.000000;0.000000;False
1 maze_type strategy avg_time_ms std_time_ms avg_visited avg_path_len path_found
2 small_10x10_simple BFS 0.187180 0.026335 19.000000 19.000000 True
3 small_10x10_simple DFS 0.167600 0.006841 19.000000 19.000000 True
4 small_10x10_simple A* 0.262300 0.029262 19.000000 19.000000 True
5 small_10x10_simple Dijkstra 0.260840 0.008608 19.000000 19.000000 True
6 medium_50x50_deadends BFS 3.563500 0.053603 380.000000 99.000000 True
7 medium_50x50_deadends DFS 3.618520 0.082922 270.000000 219.000000 True
8 medium_50x50_deadends A* 4.865660 0.017732 334.000000 99.000000 True
9 medium_50x50_deadends Dijkstra 6.019060 0.037679 380.000000 99.000000 True
10 large_100x100_complex BFS 8.644360 0.236037 886.000000 199.000000 True
11 large_100x100_complex DFS 13.781640 2.087117 697.000000 511.000000 True
12 large_100x100_complex A* 12.167040 0.334660 774.000000 199.000000 True
13 large_100x100_complex Dijkstra 14.365940 0.236778 886.000000 199.000000 True
14 empty_50x50 BFS 24.584480 0.184147 2500.000000 99.000000 True
15 empty_50x50 DFS 182.315780 4.196306 2451.000000 2451.000000 True
16 empty_50x50 A* 42.602980 0.184895 2500.000000 99.000000 True
17 empty_50x50 Dijkstra 43.213780 0.745780 2500.000000 99.000000 True
18 no_exit_50x50 BFS 25.037680 0.572634 2496.000000 0.000000 False
19 no_exit_50x50 DFS 191.040920 3.180626 2496.000000 0.000000 False
20 no_exit_50x50 A* 42.158280 0.396219 2496.000000 0.000000 False
21 no_exit_50x50 Dijkstra 42.499100 0.482887 2496.000000 0.000000 False

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -0,0 +1,272 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Настройка русских шрифтов
plt.rcParams['font.family'] = 'DejaVu Sans'
plt.rcParams['axes.unicode_minus'] = False
def load_and_prepare_data(filename='experiment_results.csv'):
"""Загрузка данных из CSV и подготовка."""
df = pd.read_csv(filename, delimiter=';')
# Преобразование типов (если нужно)
numeric_cols = ['avg_time_ms', 'std_time_ms', 'avg_visited', 'avg_path_len']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
return df
def plot_time_comparison(df):
"""График 1: Сравнение времени выполнения по лабиринтам."""
fig, ax = plt.subplots(figsize=(12, 6))
maze_types = df['maze_type'].unique()
strategies = df['strategy'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, strategy in enumerate(strategies):
strategy_data = df[df['strategy'] == strategy]
times = []
errors = []
for maze in maze_types:
row = strategy_data[strategy_data['maze_type'] == maze]
if not row.empty:
times.append(row['avg_time_ms'].values[0])
errors.append(row['std_time_ms'].values[0])
else:
times.append(0)
errors.append(0)
bars = ax.bar(x + i*width, times, width, label=strategy,
color=colors[i], yerr=errors, capsize=3)
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Время выполнения (мс)', fontsize=12)
ax.set_title('Сравнение времени выполнения алгоритмов поиска пути', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('time_comparison.png', dpi=150)
plt.show()
def plot_visited_cells(df):
"""График 2: Количество посещённых клеток."""
fig, ax = plt.subplots(figsize=(12, 6))
maze_types = df['maze_type'].unique()
strategies = df['strategy'].unique()
x = np.arange(len(maze_types))
width = 0.2
for i, strategy in enumerate(strategies):
strategy_data = df[df['strategy'] == strategy]
visited = []
for maze in maze_types:
row = strategy_data[strategy_data['maze_type'] == maze]
if not row.empty:
visited.append(row['avg_visited'].values[0])
else:
visited.append(0)
ax.bar(x + i*width, visited, width, label=strategy)
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Количество посещённых клеток', fontsize=12)
ax.set_title('Сравнение количества посещённых клеток', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('visited_cells.png', dpi=150)
plt.show()
def plot_path_length(df):
"""График 3: Длина найденного пути."""
fig, ax = plt.subplots(figsize=(12, 6))
# Исключаем лабиринты без выхода (где путь = 0)
df_filtered = df[df['avg_path_len'] > 0]
maze_types = df_filtered['maze_type'].unique()
strategies = df_filtered['strategy'].unique()
x = np.arange(len(maze_types))
width = 0.2
for i, strategy in enumerate(strategies):
strategy_data = df_filtered[df_filtered['strategy'] == strategy]
path_lengths = []
for maze in maze_types:
row = strategy_data[strategy_data['maze_type'] == maze]
if not row.empty:
path_lengths.append(row['avg_path_len'].values[0])
else:
path_lengths.append(0)
ax.bar(x + i*width, path_lengths, width, label=strategy)
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Длина пути (количество клеток)', fontsize=12)
ax.set_title('Сравнение длины найденного пути', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('path_length.png', dpi=150)
plt.show()
def plot_time_per_maze(df):
"""График 4: Для каждого лабиринта - сравнение стратегий."""
maze_types = df['maze_type'].unique()
strategies = df['strategy'].unique()
for maze in maze_types:
fig, ax = plt.subplots(figsize=(10, 6))
maze_data = df[df['maze_type'] == maze]
times = maze_data['avg_time_ms'].values
errors = maze_data['std_time_ms'].values
strategy_names = maze_data['strategy'].values
bars = ax.bar(strategy_names, times, yerr=errors, capsize=5,
color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'])
ax.set_xlabel('Алгоритм', fontsize=12)
ax.set_ylabel('Время выполнения (мс)', fontsize=12)
ax.set_title(f'Сравнение алгоритмов на лабиринте: {maze}', fontsize=14)
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for bar, time_val in zip(bars, times):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + max(errors)/2,
f'{time_val:.1f}', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
plt.savefig(f'time_{maze}.png', dpi=150)
plt.show()
def plot_visited_per_maze(df):
"""График 5: Для каждого лабиринта - посещённые клетки."""
maze_types = df['maze_type'].unique()
for maze in maze_types:
fig, ax = plt.subplots(figsize=(10, 6))
maze_data = df[df['maze_type'] == maze]
visited = maze_data['avg_visited'].values
strategy_names = maze_data['strategy'].values
bars = ax.bar(strategy_names, visited,
color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'])
ax.set_xlabel('Алгоритм', fontsize=12)
ax.set_ylabel('Количество посещённых клеток', fontsize=12)
ax.set_title(f'Посещённые клетки на лабиринте: {maze}', fontsize=14)
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for bar, val in zip(bars, visited):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height,
f'{int(val)}', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
plt.savefig(f'visited_{maze}.png', dpi=150)
plt.show()
def plot_efficiency_ratio(df):
"""График 6: Эффективность (время на клетку пути)."""
fig, ax = plt.subplots(figsize=(12, 6))
# Исключаем лабиринты без пути
df_filtered = df[(df['avg_path_len'] > 0) & (df['avg_time_ms'] > 0)].copy()
df_filtered['efficiency'] = df_filtered['avg_time_ms'] / df_filtered['avg_path_len']
maze_types = df_filtered['maze_type'].unique()
strategies = df_filtered['strategy'].unique()
x = np.arange(len(maze_types))
width = 0.2
for i, strategy in enumerate(strategies):
strategy_data = df_filtered[df_filtered['strategy'] == strategy]
efficiency = []
for maze in maze_types:
row = strategy_data[strategy_data['maze_type'] == maze]
if not row.empty:
efficiency.append(row['efficiency'].values[0])
else:
efficiency.append(0)
ax.bar(x + i*width, efficiency, width, label=strategy)
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Время на клетку пути (мс/клетку)', fontsize=12)
ax.set_title('Эффективность алгоритмов (время на единицу длины пути)', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('efficiency_ratio.png', dpi=150)
plt.show()
def main():
"""Основная функция: загрузка данных и построение всех графиков."""
try:
df = load_and_prepare_data('experiment_results.csv')
print("Данные успешно загружены")
print(f"Найдено {len(df)} записей")
print("\nСтруктура данных:")
print(df.head())
print("\nПостроение графиков...")
# Базовые графики
plot_time_comparison(df)
plot_visited_cells(df)
plot_path_length(df)
# Детальные графики по каждому лабиринту
plot_time_per_maze(df)
plot_visited_per_maze(df)
# Аналитические графики
plot_efficiency_ratio(df)
print("\nВсе графики сохранены в текущей директории:")
print(" - time_comparison.png")
print(" - visited_cells.png")
print(" - path_length.png")
print(" - time_{maze}.png (для каждого лабиринта)")
print(" - visited_{maze}.png (для каждого лабиринта)")
print(" - efficiency_ratio.png")
print(" - summary_heatmap.png")
except FileNotFoundError:
print("Ошибка: файл experiment_results.csv не найден")
print("Сначала запустите основной скрипт для генерации результатов")
except Exception as e:
print(f"Ошибка: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,843 @@
"""
Лабораторная работа: Применение паттернов проектирования
Этапы 1-6: Модель лабиринта, Builder, Strategy, MazeSolver, Observer/Command, эксперименты
"""
import time
import csv
import random
from collections import deque
from typing import List, Tuple, Dict, Set, Optional
import heapq
from dataclasses import dataclass
from abc import ABC, abstractmethod
# ============================================================
# Этап 1. Модель лабиринта
# ============================================================
class Cell:
"""Клетка лабиринта."""
def __init__(self, x: int, y: int, is_wall: bool = False, weight: int = 1):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = False
self.is_exit = False
self.weight = weight # для взвешенных лабиринтов
def is_passable(self) -> bool:
return not self.is_wall
def __eq__(self, other):
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return f"Cell({self.x},{self.y})"
class Maze:
"""Лабиринт, содержащий сетку клеток."""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
self.start_cell = None
self.exit_cell = None
def get_cell(self, x: int, y: int) -> Cell:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
raise IndexError("Координаты вне границ лабиринта")
def get_neighbors(self, cell: Cell) -> List[Cell]:
"""Возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо)."""
neighbors = []
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = cell.x + dx, cell.y + dy
if 0 <= nx < self.width and 0 <= ny < self.height:
n = self.cells[ny][nx]
if n.is_passable():
neighbors.append(n)
return neighbors
def set_start(self, x: int, y: int):
cell = self.get_cell(x, y)
cell.is_start = True
self.start_cell = cell
def set_exit(self, x: int, y: int):
cell = self.get_cell(x, y)
cell.is_exit = True
self.exit_cell = cell
def copy(self):
"""Создаёт глубокую копию лабиринта (для взвешенных вариантов)."""
new_maze = Maze(self.width, self.height)
for y in range(self.height):
for x in range(self.width):
orig = self.cells[y][x]
new_maze.cells[y][x] = Cell(x, y, orig.is_wall, orig.weight)
if orig.is_start:
new_maze.set_start(x, y)
if orig.is_exit:
new_maze.set_exit(x, y)
return new_maze
# ============================================================
# Этап 2. Builder для загрузки из текстового файла
# ============================================================
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
"""Строитель лабиринта из текстового файла."""
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f]
if not lines:
raise ValueError("Файл пуст")
height = len(lines)
width = max(len(line) for line in lines)
grid = []
for y, line in enumerate(lines):
row = []
for x in range(width):
ch = line[x] if x < len(line) else ' '
row.append(ch)
grid.append(row)
maze = Maze(width, height)
start_found = exit_found = False
for y in range(height):
for x in range(width):
ch = grid[y][x]
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
if start_found:
raise ValueError("Обнаружено несколько стартовых клеток 'S'")
cell.is_start = True
maze.start_cell = cell
start_found = True
elif ch == 'E':
if exit_found:
raise ValueError("Обнаружено несколько выходных клеток 'E'")
cell.is_exit = True
maze.exit_cell = cell
exit_found = True
elif ch != ' ':
raise ValueError(f"Недопустимый символ '{ch}' в позиции ({x},{y})")
if not start_found:
raise ValueError("Отсутствует стартовая клетка 'S'")
if not exit_found:
raise ValueError("Отсутствует выходная клетка 'E'")
return maze
# ============================================================
# Этап 3. Стратегии поиска пути (возвращают путь и число посещённых)
# ============================================================
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
"""Возвращает (путь, количество посещённых клеток)."""
pass
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину гарантирует кратчайший путь."""
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
if start == exit_cell:
return [start], 1
queue = deque([start])
visited = {start}
parent = {start: None}
visited_count = 1
while queue:
cur = queue.popleft()
if cur == exit_cell:
path = []
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path, visited_count
for nb in maze.get_neighbors(cur):
if nb not in visited:
visited.add(nb)
visited_count += 1
parent[nb] = cur
queue.append(nb)
return [], visited_count
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину быстрый, но не гарантирует кратчайший путь."""
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
if start == exit_cell:
return [start], 1
stack = [(start, [start])]
visited = set()
visited_count = 0
while stack:
cur, path = stack.pop()
if cur in visited:
continue
visited.add(cur)
visited_count += 1
if cur == exit_cell:
return path, visited_count
for nb in maze.get_neighbors(cur):
if nb not in visited:
stack.append((nb, path + [nb]))
return [], visited_count
class AStarStrategy(PathFindingStrategy):
"""А* с эвристикой Манхэттенского расстояния."""
def _heuristic(self, a: Cell, b: Cell) -> float:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
if start == exit_cell:
return [start], 1
open_set = []
counter = 0
heapq.heappush(open_set, (0, counter, start))
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit_cell)}
parent = {start: None}
visited_count = 1
while open_set:
_, _, cur = heapq.heappop(open_set)
if cur == exit_cell:
path = []
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path, visited_count
for nb in maze.get_neighbors(cur):
move_cost = nb.weight
tentative = g_score[cur] + move_cost
if nb not in g_score or tentative < g_score[nb]:
parent[nb] = cur
g_score[nb] = tentative
f_score[nb] = tentative + self._heuristic(nb, exit_cell)
counter += 1
heapq.heappush(open_set, (f_score[nb], counter, nb))
visited_count += 1
return [], visited_count
class DijkstraStrategy(PathFindingStrategy):
"""Алгоритм Дейкстры для взвешенных графов."""
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> Tuple[List[Cell], int]:
if start == exit_cell:
return [start], 1
pq = []
counter = 0
heapq.heappush(pq, (0, counter, start))
dist = {start: 0}
parent = {start: None}
visited_count = 1
while pq:
cur_dist, _, cur = heapq.heappop(pq)
if cur_dist > dist.get(cur, float('inf')):
continue
if cur == exit_cell:
path = []
while cur is not None:
path.append(cur)
cur = parent[cur]
path.reverse()
return path, visited_count
for nb in maze.get_neighbors(cur):
new_dist = cur_dist + nb.weight
if new_dist < dist.get(nb, float('inf')):
dist[nb] = new_dist
parent[nb] = cur
counter += 1
heapq.heappush(pq, (new_dist, counter, nb))
visited_count += 1
return [], visited_count
# ============================================================
# Этап 4. MazeSolver (оркестратор)
# ============================================================
@dataclass
class SearchStats:
path_length: int
visited_cells: int
time_ms: float
class MazeSolver:
"""Оркестратор: управляет лабиринтом и стратегией поиска."""
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> SearchStats:
start_time = time.perf_counter()
path, visited = self.strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
end_time = time.perf_counter()
return SearchStats(len(path), visited, (end_time - start_time) * 1000.0)
# ============================================================
# Этап 5. Observer и Command (визуализация и пошаговое управление)
# ============================================================
class Observer(ABC):
@abstractmethod
def update(self, event: str, data: dict = None):
pass
class ConsoleView(Observer):
"""Отображает лабиринт, позицию игрока и найденный путь."""
def __init__(self):
self.last_maze = None
self.last_player_pos = None
self.last_path = None
def update(self, event: str, data: dict = None):
if event == "maze_loaded":
self.last_maze = data["maze"]
self.render()
elif event == "player_moved":
self.last_maze = data["maze"]
self.last_player_pos = data["player_pos"]
self.render()
elif event == "path_found":
self.last_path = data["path"]
self.render()
elif event == "clear_path":
self.last_path = None
self.render()
def render(self):
if self.last_maze is None:
print("Нет лабиринта для отображения")
return
maze = self.last_maze
player = self.last_player_pos
path_set = set(self.last_path) if self.last_path else set()
for y in range(maze.height):
row = []
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player and cell == player:
row.append('@')
elif cell == maze.start_cell:
row.append('S')
elif cell == maze.exit_cell:
row.append('E')
elif cell in path_set and cell.is_passable():
row.append('*')
elif cell.is_wall:
row.append('#')
else:
row.append(' ')
print(''.join(row))
print()
class Player:
"""Игрок, перемещающийся по лабиринту."""
def __init__(self, start_cell: Cell):
self.position = start_cell
def move_to(self, new_cell: Cell):
self.position = new_cell
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
"""Команда перемещения игрока."""
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.prev_position = player.position
self.new_position = None
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W':
dy = -1
elif self.direction == 'S':
dy = 1
elif self.direction == 'A':
dx = -1
elif self.direction == 'D':
dx = 1
else:
return False
nx = self.player.position.x + dx
ny = self.player.position.y + dy
try:
target = self.maze.get_cell(nx, ny)
if target.is_passable():
self.new_position = target
self.player.move_to(target)
return True
except IndexError:
pass
return False
def undo(self):
if self.prev_position:
self.player.move_to(self.prev_position)
class GameController:
"""Управляет игрой, наблюдателями и командами."""
def __init__(self, maze: Maze):
self.maze = maze
self.player = Player(maze.start_cell)
self.observers = []
self.command_stack = []
def attach(self, observer: Observer):
self.observers.append(observer)
def detach(self, observer: Observer):
self.observers.remove(observer)
def notify(self, event: str, data: dict = None):
for obs in self.observers:
obs.update(event, data or {})
def load_maze(self, maze: Maze):
self.maze = maze
self.player = Player(maze.start_cell)
self.notify("maze_loaded", {"maze": maze})
def find_path(self, strategy: PathFindingStrategy) -> List[Cell]:
solver = MazeSolver(self.maze, strategy)
stats = solver.solve()
print(f"Длина пути: {stats.path_length}, посещено: {stats.visited_cells}, время: {stats.time_ms:.3f} мс")
path, _ = strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
self.notify("path_found", {"path": path})
return path
def clear_path(self):
self.notify("clear_path", {})
def execute_command(self, cmd: Command):
if cmd.execute():
self.command_stack.append(cmd)
self.notify("player_moved", {"maze": self.maze, "player_pos": self.player.position})
def undo(self):
if self.command_stack:
cmd = self.command_stack.pop()
cmd.undo()
self.notify("player_moved", {"maze": self.maze, "player_pos": self.player.position})
# ============================================================
# Этап 6. Генераторы тестовых лабиринтов
# ============================================================
def generate_simple_maze(width: int, height: int) -> Maze:
"""Маленький лабиринт с простым путём."""
maze = Maze(width, height)
for y in range(height):
for x in range(width):
maze.cells[y][x].is_wall = True
x, y = 0, 0
path = [(x, y)]
while x < width - 1 or y < height - 1:
if x < width - 1 and (y == height - 1 or random.random() < 0.5):
x += 1
else:
if y < height - 1:
y += 1
path.append((x, y))
for px, py in path:
maze.cells[py][px].is_wall = False
maze.set_start(0, 0)
maze.set_exit(width - 1, height - 1)
return maze
def generate_with_dead_ends(width: int, height: int) -> Maze:
"""Средний лабиринт с гарантированным путём и множеством тупиков."""
maze = Maze(width, height)
for y in range(height):
for x in range(width):
maze.cells[y][x].is_wall = True
x, y = 0, 0
main_path = []
while x < width - 1 or y < height - 1:
main_path.append((x, y))
if x < width - 1 and (y == height - 1 or random.random() < 0.6):
x += 1
else:
if y < height - 1:
y += 1
else:
x += 1
main_path.append((width - 1, height - 1))
for px, py in main_path:
maze.cells[py][px].is_wall = False
num_dead_ends = int(width * height * 0.08)
for _ in range(num_dead_ends):
base_x, base_y = random.choice(main_path)
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
random.shuffle(directions)
for dx, dy in directions:
nx, ny = base_x + dx, base_y + dy
if 0 <= nx < width and 0 <= ny < height and maze.cells[ny][nx].is_wall:
length = random.randint(2, 4)
for step in range(length):
if 0 <= nx < width and 0 <= ny < height and maze.cells[ny][nx].is_wall:
maze.cells[ny][nx].is_wall = False
nx += dx
ny += dy
else:
break
break
maze.set_start(0, 0)
maze.set_exit(width - 1, height - 1)
return maze
def generate_complex_maze(width: int, height: int) -> Maze:
"""Большой лабиринт с гарантированным путём и высокой запутанностью."""
maze = Maze(width, height)
for y in range(height):
for x in range(width):
maze.cells[y][x].is_wall = True
x, y = 0, 0
main_path = []
while x < width - 1 or y < height - 1:
main_path.append((x, y))
if x < width - 1 and (y == height - 1 or random.random() < 0.7):
x += 1
else:
if y < height - 1:
y += 1
else:
x += 1
main_path.append((width - 1, height - 1))
for px, py in main_path:
maze.cells[py][px].is_wall = False
num_branches = int(width * height * 0.12)
for _ in range(num_branches):
base_x, base_y = random.choice(main_path)
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
random.shuffle(directions)
for dx, dy in directions:
nx, ny = base_x + dx, base_y + dy
if 0 <= nx < width and 0 <= ny < height and maze.cells[ny][nx].is_wall:
length = random.randint(1, 5)
branch = []
for step in range(length):
if 0 <= nx < width and 0 <= ny < height and maze.cells[ny][nx].is_wall:
maze.cells[ny][nx].is_wall = False
branch.append((nx, ny))
nx += dx
ny += dy
else:
break
if random.random() < 0.3 and len(branch) >= 2:
bx, by = branch[-1]
for ddx, ddy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
nnx, nny = bx + ddx, by + ddy
if (0 <= nnx < width and 0 <= nny < height and
maze.cells[nny][nnx].is_wall and random.random() < 0.5):
maze.cells[nny][nnx].is_wall = False
break
maze.set_start(0, 0)
maze.set_exit(width - 1, height - 1)
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
"""Пустой лабиринт без стен."""
maze = Maze(width, height)
for y in range(height):
for x in range(width):
maze.cells[y][x].is_wall = False
maze.set_start(0, 0)
maze.set_exit(width - 1, height - 1)
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
"""Лабиринт без выхода (выход окружён стенами)."""
maze = generate_empty_maze(width, height)
ex, ey = width - 1, height - 1
for dx, dy in [(0, 0), (0, -1), (0, 1), (-1, 0), (1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]:
nx, ny = ex + dx, ey + dy
if 0 <= nx < width and 0 <= ny < height:
if not (nx == 0 and ny == 0):
maze.cells[ny][nx].is_wall = True
maze.cells[ey][ex].is_wall = False
maze.set_exit(ex, ey)
return maze
# ============================================================
# Экспериментальная часть
# ============================================================
def run_experiment(maze: Maze, strategies: List[Tuple[str, PathFindingStrategy]], runs: int = 5) -> List[dict]:
"""Запускает эксперимент на одном лабиринте и возвращает усреднённые результаты."""
results = []
for name, strategy in strategies:
times = []
visited_counts = []
path_lengths = []
for _ in range(runs):
solver = MazeSolver(maze, strategy)
stats = solver.solve()
times.append(stats.time_ms)
visited_counts.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / runs
variance = sum((t - avg_time) ** 2 for t in times) / runs
std_time = variance ** 0.5
results.append({
'maze_type': '',
'strategy': name,
'avg_time_ms': avg_time,
'std_time_ms': std_time,
'avg_visited': sum(visited_counts) / runs,
'avg_path_len': sum(path_lengths) / runs,
'path_found': all(l > 0 for l in path_lengths)
})
return results
def save_results_to_csv(results: List[dict], filename: str):
"""Сохраняет результаты в CSV с разделителем ';' для совместимости с Excel."""
if not results:
return
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=results[0].keys(), delimiter=';')
writer.writeheader()
for row in results:
row_copy = {}
for k, v in row.items():
if isinstance(v, float):
row_copy[k] = f"{v:.6f}".replace(',', '.')
else:
row_copy[k] = v
writer.writerow(row_copy)
# ============================================================
# Взвешенные лабиринты (опциональное задание)
# ============================================================
def assign_weights_random(maze: Maze, weights: List[Tuple[float, int]]) -> Maze:
"""Присваивает веса клеткам согласно вероятностям."""
for y in range(maze.height):
for x in range(maze.width):
if not maze.cells[y][x].is_wall:
r = random.random()
cum = 0
for prob, w in weights:
cum += prob
if r < cum:
maze.cells[y][x].weight = w
break
return maze
def weighted_experiment():
"""Дополнительный эксперимент со взвешенными клетками."""
print("\n=== ВЗВЕШЕННЫЕ ЛАБИРИНТЫ (опциональное задание) ===")
maze = generate_with_dead_ends(30, 30)
assign_weights_random(maze, [(0.8, 1), (0.15, 3), (0.05, 2)])
strategies = [
("A* (манхэттен)", AStarStrategy()),
("Dijkstra", DijkstraStrategy())
]
print("Лабиринт 30x30 со взвешенными клетками (болото 3, песок 2, асфальт 1)")
results = run_experiment(maze, strategies, runs=10)
for r in results:
print(f"{r['strategy']:15} | Время: {r['avg_time_ms']:.2f} мс | "
f"Посещено: {r['avg_visited']:.0f} | Длина пути: {r['avg_path_len']:.0f}")
# Сравнение с BFS
bfs = BFSStrategy()
path_bfs, _ = bfs.find_path(maze, maze.start_cell, maze.exit_cell)
if path_bfs:
cost_bfs = sum(cell.weight for cell in path_bfs)
print(f"BFS нашёл путь длиной {len(path_bfs)} клеток, стоимость = {cost_bfs}")
path_dijkstra, _ = DijkstraStrategy().find_path(maze, maze.start_cell, maze.exit_cell)
if path_dijkstra:
cost_dijkstra = sum(cell.weight for cell in path_dijkstra)
print(f"Dijkstra нашёл путь длиной {len(path_dijkstra)} клеток, стоимость = {cost_dijkstra}")
path_astar, _ = AStarStrategy().find_path(maze, maze.start_cell, maze.exit_cell)
if path_astar:
cost_astar = sum(cell.weight for cell in path_astar)
print(f"A* нашёл путь длиной {len(path_astar)} клеток, стоимость = {cost_astar}")
# ============================================================
# Демонстрация работы Observer и Command (по желанию)
# ============================================================
def demo_observer_command():
"""Демонстрирует паттерны Observer и Command."""
print("\n=== ДЕМОНСТРАЦИЯ OBSERVER И COMMAND ===")
maze = generate_simple_maze(10, 10)
controller = GameController(maze)
view = ConsoleView()
controller.attach(view)
print("Лабиринт загружен:")
controller.load_maze(maze)
print("Поиск пути с помощью BFS:")
controller.find_path(BFSStrategy())
input("Нажмите Enter для пошагового управления...")
controller.clear_path()
print("\nУправление: W/A/S/D - движение, Z - отмена, Q - выход")
while True:
cmd = input("> ").upper().strip()
if cmd == 'Q':
break
elif cmd == 'Z':
controller.undo()
elif cmd in ('W', 'A', 'S', 'D'):
move_cmd = MoveCommand(controller.player, controller.maze, cmd)
controller.execute_command(move_cmd)
else:
print("Неизвестная команда")
# ============================================================
# Основной эксперимент
# ============================================================
def main():
"""Основной эксперимент: сравнение стратегий на различных лабиринтах."""
print("=== ЗАПУСК ЭКСПЕРИМЕНТОВ ===")
strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("A*", AStarStrategy()),
("Dijkstra", DijkstraStrategy())
]
# Генерация тестовых лабиринтов
maze_definitions = {
"small_10x10_simple": generate_simple_maze(10, 10),
"medium_50x50_deadends": generate_with_dead_ends(50, 50),
"large_100x100_complex": generate_complex_maze(100, 100),
"empty_50x50": generate_empty_maze(50, 50),
"no_exit_50x50": generate_no_exit_maze(50, 50)
}
all_results = []
for maze_name, maze in maze_definitions.items():
print(f"\nЗапуск на лабиринте: {maze_name} ({maze.width}x{maze.height})")
results = run_experiment(maze, strategies, runs=5)
for r in results:
r['maze_type'] = maze_name
all_results.append(r)
# Вывод промежуточных результатов
for r in results:
print(f" {r['strategy']:8} | Время: {r['avg_time_ms']:7.2f}±{r['std_time_ms']:.2f} мс | "
f"Посещено: {r['avg_visited']:7.0f} | Длина пути: {r['avg_path_len']:5.0f}")
# Сохранение результатов
save_results_to_csv(all_results, "experiment_results.csv")
print("\nРезультаты сохранены в experiment_results.csv")
# Вывод сводной таблицы
print("\n" + "=" * 100)
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ")
print("=" * 100)
print(f"{'Лабиринт':<25} | {'Стратегия':<10} | {'Время (мс)':<15} | {'Посещено':<10} | {'Длина пути':<10}")
print("-" * 100)
for r in all_results:
print(f"{r['maze_type']:<25} | {r['strategy']:<10} | {r['avg_time_ms']:>8.2f} ± {r['std_time_ms']:<5.2f} | "
f"{r['avg_visited']:>8.0f} | {r['avg_path_len']:>8.0f}")
# ============================================================
# Запуск
# ============================================================
if __name__ == "__main__":
main()
# Раскомментируйте для демонстрации:
# demo_observer_command()
# weighted_experiment()

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -0,0 +1,89 @@
classDiagram
class Клетка {
+int x, y
+bool стена, старт, выход
+int вес
+проходима()
}
class Лабиринт {
+int ширина, высота
+Клетка[][] клетки
+Клетка стартоваяКлетка, выходнаяКлетка
+получитьКлетку(x,y)
+получитьСоседей(клетка)
+установитьСтарт(x,y)
+установитьВыход(x,y)
}
class СтроительЛабиринта {
<<интерфейс>>
+построитьИзФайла(имяФайла)
}
class СтроительИзТекстовогоФайла {
+построитьИзФайла(имяФайла)
}
class СтратегияПоискаПути {
<<интерфейс>>
+найтиПуть(лабиринт, старт, выход)
}
class ПоискВШирину
class ПоискВГлубину
class Астар
class Дейкстра
class РешательЛабиринта {
-Лабиринт лабиринт
-СтратегияПоискаПути стратегия
+установитьСтратегию(стратегия)
+решить() СтатистикаПоиска
}
class СтатистикаПоиска {
+int длинаПути
+int посещеноКлеток
+float времяМс
}
class Наблюдатель {
<<интерфейс>>
+обновить(событие, данные)
}
class КонсольноеПредставление {
+обновить(событие, данные)
-отобразить()
}
class Команда {
<<интерфейс>>
+выполнить()
+отменить()
}
class КомандаПеремещения {
-Игрок игрок
-Лабиринт лабиринт
-String направление
+выполнить()
+отменить()
}
class Игрок {
+Клетка позиция
+переместитьсяВ(клетка)
}
class КонтроллерИгры {
-List наблюдатели
-Stack команды
+подписать(наблюдатель)
+уведомить(событие, данные)
+выполнитьКоманду(команда)
+отменить()
}
СтроительЛабиринта <|-- СтроительИзТекстовогоФайла
СтратегияПоискаПути <|-- ПоискВШирину
СтратегияПоискаПути <|-- ПоискВГлубину
СтратегияПоискаПути <|-- Астар
СтратегияПоискаПути <|-- Дейкстра
РешательЛабиринта --> СтратегияПоискаПути
РешательЛабиринта --> Лабиринт
Лабиринт --> Клетка
КонтроллерИгры --> Игрок
КонтроллерИгры --> Команда
КонтроллерИгры --> Наблюдатель
КомандаПеремещения --> Игрок
КомандаПеремещения --> Лабиринт
КонсольноеПредставление ..|> Наблюдатель

View File

@ -0,0 +1 @@
t2.py -> res2.py

25
ShapovalovKA/себе.txt Normal file
View File

@ -0,0 +1,25 @@
cd 2026-rff_mp
cd shapovalovka
git add .
git commit -m "[] "
git push origin
Сделать запрос на слияние (Pull Request (PR))
git log --oneline - для логов
Логи:
e90dc47 (HEAD -> master) [10] Task 2 is complete
69a8554 [9] Task 2.7 in pogress
a644775 [8] Task 2.6 is complete
000535f [8] Task 2.6 is complete
34904a0 [7] Task 2 is started
1998da8 [6] Task 1 and analisys is complete
a88c7b8 [5] Task 1 is complete
81205c8 [4] t1.2 is complete
8dad5b8 [3] t1.1.4 in progress
9e5cee6 [2] t1.1 in progress
915990a [1] docs and data