diff --git a/ShapovalovKA/docs/1st_task_analysis.docx b/ShapovalovKA/docs/1st_task_analysis.docx new file mode 100644 index 0000000..9e5aa5f Binary files /dev/null and b/ShapovalovKA/docs/1st_task_analysis.docx differ diff --git a/ShapovalovKA/docs/2nd_task_analysis.docx b/ShapovalovKA/docs/2nd_task_analysis.docx new file mode 100644 index 0000000..00041b6 Binary files /dev/null and b/ShapovalovKA/docs/2nd_task_analysis.docx differ diff --git a/ShapovalovKA/docs/data/1Task/res.py b/ShapovalovKA/docs/data/1Task/res.py new file mode 100644 index 0000000..397f942 --- /dev/null +++ b/ShapovalovKA/docs/data/1Task/res.py @@ -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() \ No newline at end of file diff --git a/ShapovalovKA/docs/data/1Task/results.csv b/ShapovalovKA/docs/data/1Task/results.csv new file mode 100644 index 0000000..451aa83 --- /dev/null +++ b/ShapovalovKA/docs/data/1Task/results.csv @@ -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 diff --git a/ShapovalovKA/docs/data/1Task/t1_1.py b/ShapovalovKA/docs/data/1Task/t1_1.py new file mode 100644 index 0000000..a144c3b --- /dev/null +++ b/ShapovalovKA/docs/data/1Task/t1_1.py @@ -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() \ No newline at end of file diff --git a/ShapovalovKA/docs/data/1Task/t1_2.py b/ShapovalovKA/docs/data/1Task/t1_2.py new file mode 100644 index 0000000..6f76e05 --- /dev/null +++ b/ShapovalovKA/docs/data/1Task/t1_2.py @@ -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() \ No newline at end of file diff --git a/ShapovalovKA/docs/data/1Task/t1_3.py b/ShapovalovKA/docs/data/1Task/t1_3.py new file mode 100644 index 0000000..64ffe5a --- /dev/null +++ b/ShapovalovKA/docs/data/1Task/t1_3.py @@ -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() \ No newline at end of file diff --git a/ShapovalovKA/docs/data/1Task/t1p1.png b/ShapovalovKA/docs/data/1Task/t1p1.png new file mode 100644 index 0000000..3ca326b Binary files /dev/null and b/ShapovalovKA/docs/data/1Task/t1p1.png differ diff --git a/ShapovalovKA/docs/data/1Task/t1p2.png b/ShapovalovKA/docs/data/1Task/t1p2.png new file mode 100644 index 0000000..9e059e1 Binary files /dev/null and b/ShapovalovKA/docs/data/1Task/t1p2.png differ diff --git a/ShapovalovKA/docs/data/1Task/Порядок использования.txt b/ShapovalovKA/docs/data/1Task/Порядок использования.txt new file mode 100644 index 0000000..1b5d785 --- /dev/null +++ b/ShapovalovKA/docs/data/1Task/Порядок использования.txt @@ -0,0 +1,2 @@ +t1_1.py -> t1_2.py -> t1_3.py +-> res.py \ No newline at end of file diff --git a/ShapovalovKA/docs/data/2Task/efficiency_ratio.png b/ShapovalovKA/docs/data/2Task/efficiency_ratio.png new file mode 100644 index 0000000..c631121 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/efficiency_ratio.png differ diff --git a/ShapovalovKA/docs/data/2Task/experiment_results.csv b/ShapovalovKA/docs/data/2Task/experiment_results.csv new file mode 100644 index 0000000..adc7d99 --- /dev/null +++ b/ShapovalovKA/docs/data/2Task/experiment_results.csv @@ -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 diff --git a/ShapovalovKA/docs/data/2Task/mermaid_diagramm_task_2.png b/ShapovalovKA/docs/data/2Task/mermaid_diagramm_task_2.png new file mode 100644 index 0000000..546d12f Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/mermaid_diagramm_task_2.png differ diff --git a/ShapovalovKA/docs/data/2Task/path_length.png b/ShapovalovKA/docs/data/2Task/path_length.png new file mode 100644 index 0000000..0146348 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/path_length.png differ diff --git a/ShapovalovKA/docs/data/2Task/res2.py b/ShapovalovKA/docs/data/2Task/res2.py new file mode 100644 index 0000000..1c4d91e --- /dev/null +++ b/ShapovalovKA/docs/data/2Task/res2.py @@ -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() \ No newline at end of file diff --git a/ShapovalovKA/docs/data/2Task/t2.py b/ShapovalovKA/docs/data/2Task/t2.py new file mode 100644 index 0000000..8696f84 --- /dev/null +++ b/ShapovalovKA/docs/data/2Task/t2.py @@ -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() \ No newline at end of file diff --git a/ShapovalovKA/docs/data/2Task/time_comparison.png b/ShapovalovKA/docs/data/2Task/time_comparison.png new file mode 100644 index 0000000..71c84f7 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/time_comparison.png differ diff --git a/ShapovalovKA/docs/data/2Task/time_empty_50x50.png b/ShapovalovKA/docs/data/2Task/time_empty_50x50.png new file mode 100644 index 0000000..af25179 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/time_empty_50x50.png differ diff --git a/ShapovalovKA/docs/data/2Task/time_large_100x100_complex.png b/ShapovalovKA/docs/data/2Task/time_large_100x100_complex.png new file mode 100644 index 0000000..d1b75cb Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/time_large_100x100_complex.png differ diff --git a/ShapovalovKA/docs/data/2Task/time_medium_50x50_deadends.png b/ShapovalovKA/docs/data/2Task/time_medium_50x50_deadends.png new file mode 100644 index 0000000..3065ff4 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/time_medium_50x50_deadends.png differ diff --git a/ShapovalovKA/docs/data/2Task/time_no_exit_50x50.png b/ShapovalovKA/docs/data/2Task/time_no_exit_50x50.png new file mode 100644 index 0000000..a641970 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/time_no_exit_50x50.png differ diff --git a/ShapovalovKA/docs/data/2Task/time_small_10x10_simple.png b/ShapovalovKA/docs/data/2Task/time_small_10x10_simple.png new file mode 100644 index 0000000..35d2431 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/time_small_10x10_simple.png differ diff --git a/ShapovalovKA/docs/data/2Task/visited_cells.png b/ShapovalovKA/docs/data/2Task/visited_cells.png new file mode 100644 index 0000000..2712a96 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/visited_cells.png differ diff --git a/ShapovalovKA/docs/data/2Task/visited_empty_50x50.png b/ShapovalovKA/docs/data/2Task/visited_empty_50x50.png new file mode 100644 index 0000000..47f30db Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/visited_empty_50x50.png differ diff --git a/ShapovalovKA/docs/data/2Task/visited_large_100x100_complex.png b/ShapovalovKA/docs/data/2Task/visited_large_100x100_complex.png new file mode 100644 index 0000000..28b7013 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/visited_large_100x100_complex.png differ diff --git a/ShapovalovKA/docs/data/2Task/visited_medium_50x50_deadends.png b/ShapovalovKA/docs/data/2Task/visited_medium_50x50_deadends.png new file mode 100644 index 0000000..52dede5 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/visited_medium_50x50_deadends.png differ diff --git a/ShapovalovKA/docs/data/2Task/visited_no_exit_50x50.png b/ShapovalovKA/docs/data/2Task/visited_no_exit_50x50.png new file mode 100644 index 0000000..5e6fa24 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/visited_no_exit_50x50.png differ diff --git a/ShapovalovKA/docs/data/2Task/visited_small_10x10_simple.png b/ShapovalovKA/docs/data/2Task/visited_small_10x10_simple.png new file mode 100644 index 0000000..aa358f1 Binary files /dev/null and b/ShapovalovKA/docs/data/2Task/visited_small_10x10_simple.png differ diff --git a/ShapovalovKA/docs/data/2Task/Код диаграммы.txt b/ShapovalovKA/docs/data/2Task/Код диаграммы.txt new file mode 100644 index 0000000..33bb9d7 --- /dev/null +++ b/ShapovalovKA/docs/data/2Task/Код диаграммы.txt @@ -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 команды + +подписать(наблюдатель) + +уведомить(событие, данные) + +выполнитьКоманду(команда) + +отменить() + } + + СтроительЛабиринта <|-- СтроительИзТекстовогоФайла + СтратегияПоискаПути <|-- ПоискВШирину + СтратегияПоискаПути <|-- ПоискВГлубину + СтратегияПоискаПути <|-- Астар + СтратегияПоискаПути <|-- Дейкстра + РешательЛабиринта --> СтратегияПоискаПути + РешательЛабиринта --> Лабиринт + Лабиринт --> Клетка + КонтроллерИгры --> Игрок + КонтроллерИгры --> Команда + КонтроллерИгры --> Наблюдатель + КомандаПеремещения --> Игрок + КомандаПеремещения --> Лабиринт + КонсольноеПредставление ..|> Наблюдатель \ No newline at end of file diff --git a/ShapovalovKA/docs/data/2Task/Порядок использования 2.txt b/ShapovalovKA/docs/data/2Task/Порядок использования 2.txt new file mode 100644 index 0000000..53e453c --- /dev/null +++ b/ShapovalovKA/docs/data/2Task/Порядок использования 2.txt @@ -0,0 +1 @@ +t2.py -> res2.py \ No newline at end of file diff --git a/ShapovalovKA/себе.txt b/ShapovalovKA/себе.txt new file mode 100644 index 0000000..0d7c075 --- /dev/null +++ b/ShapovalovKA/себе.txt @@ -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 \ No newline at end of file