diff --git a/YanyaevAA/docs/Report_1.md b/YanyaevAA/docs/Report_1.md new file mode 100644 index 0000000..ad02b00 --- /dev/null +++ b/YanyaevAA/docs/Report_1.md @@ -0,0 +1,317 @@ +# Структуры данных +Цель работы: Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Вы должны собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике. + +## Подготовка среды +```Python +import time +from pathlib import Path +import random +import csv +import sys +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +sys.setrecursionlimit(12000) #увеличивает глубину рекурсии +``` + +# Базовые операции +```Python +#Связный список +def ll_insert(head, name, phone): + current = head + while current: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + new_node = {'name': name, 'phone': phone, 'next': None} + new_node['next'] = head + return new_node + +def ll_find(head, name): + current = head + while current: + if current['name'] == name: + return current['phone'] + current = current['next'] + return None + +def ll_delete(head, name): + if head['name'] == name: + return head['next'] + current = head + while current['next']: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + break + current = current['next'] + return head + +def ll_list_all(head): + data= [] + current = head + while current: + data.append((current['name'], current['phone'])) + current = current['next'] + return sorted(data) + +#хеш-таблица +def ht_insert(buckets, name, phone): + id=hash(name)%len(buckets) + buckets[id] = ll_insert(buckets[id], name, phone) + +def ht_find(buckets, name): + id= hash(name)%len(buckets) + return ll_find(buckets[id], name) + +def ht_delete(buckets, name): + id= hash(name)%len(buckets) + buckets[id] = ll_delete(buckets[id], name) + +def ht_list_all(buckets): + data = [] + for head in buckets: + current = head + while current: + data.append((current['name'], current['phone'])) + current = current['next'] + return sorted(data) + + + +#Двоичное дерево поиска +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + if name == root['name']: + root['phone'] = phone + elif name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + else: + root['right'] = bst_insert(root['right'], name, phone) + return root + +def bst_find(root, name): + if root is None: + return None + if root['name'] == name: + return root['phone'] + elif name root['name']: + root['right'] = bst_delete(root['right'], name) + else: + if root['left'] is None: + return root['right'] + elif root['right'] is None: + return root['left'] + min=minimum(root['right']) + root['name']=min['name'] + root['phone']=min['phone'] + root['right']=bst_delete(root['right'], min['name']) + return root + +def bst_list_all(root): + result=[] + if root: + result.extend(bst_list_all(root['left'])) + result.append((root['name'], root['phone'])) + result.extend(bst_list_all(root['right'])) + return result +``` + +# Экспериментальная часть + +## Генерация +Создаем список records из N=10000 элементов. Каждый элемент — кортеж (name, phone). +Имена генерируются как f"User_{i:05d}" (равномерное распределение). Для проверки влияния порядка подготовим два варианта одного и того же набора: + +records_shuffled — случайный порядок. + +records_sorted — отсортированный по имени (по алфавиту). +```Python +def generate(n=10000): + records = [(f"User_{i:05d}", f"+7 ({random.randint(100, 999)}) {random.randint(100, 999)}-{random.randint(00, 99):02}-{random.randint(00, 99):02}") for i in range(n)] + records_sorted =records.copy() + records_shuffled=records.copy() + random.shuffle(records_shuffled) + return records_sorted, records_shuffled +``` +## Проведение замеров + +**А. Вставка всех записей** +Создаем пустую структуру. +Засекаем время, выполняем insert для каждой записи из входного списка. +Фиксируем общее время вставки. +```Python +def task_A(structure_name, data): + start =time.perf_counter() + if structure_name=="LinkedList": + head=None + for name, phone in data: + head = ll_insert(head, name, phone) + container=head + elif structure_name=="HashTable": + buckets=[None]*1000 + for name, phone in data: + ht_insert(buckets, name, phone) + container=buckets + elif structure_name=="BinarySearchTree": + root=None + for name, phone in data: + root = bst_insert(root, name, phone) + container=root + end = time.perf_counter() + elapsed = end - start + return elapsed, container +``` + +**Б. Поиск 100 случайных записей** +Берем 100 случайных имён из того же набора (гарантированно существующих) и 10 имён, которых нет ("None_{i}"). +Засекаем время на выполнение всех 110 вызовов find. +```Python +def task_B(structure_name,container, data): + start=time.perf_counter() + if structure_name=="LinkedList": + for name in data: + ll_find(container, name) + elif structure_name=="HashTable": + for name in data: + ht_find(container, name) + elif structure_name=="BinarySearchTree": + for name in data: + bst_find(container, name) + end=time.perf_counter() + elapsed = end - start + return elapsed +``` + +**В. Удаление 50 случайных записей** +Берем 50 случайных имён из набора. +Засекаем время на выполнение delete для каждого. +```Python +def task_C(structure_name,container, data): + start=time.perf_counter() + if structure_name=="LinkedList": + for name in data: + container=ll_delete(container, name) + elif structure_name=="HashTable": + for name in data: + ht_delete(container, name) + elif structure_name=="BinarySearchTree": + for name in data: + container = bst_delete(container, name) + end=time.perf_counter() + elapsed = end - start + return elapsed +``` + +### Реализация замеров +```Python +results=[["Структура", "Режим", "Операция", "Время (сек)"]] +structures_name=["LinkedList", "HashTable", "BinarySearchTree"] +experiment_name=["Вставка", "Поиск", "Удаление"] +mode_of_data=["Случайный", "Отсортированный"] + +records_sorted, records_shuffled = generate() +container_shuffled=[]#хранилище структур со случайными данными +container_sorted=[]#хранилище структур с отсортированными данными +names=[record[0] for record in records_shuffled] +#Данные для задания Б +random_names=random.sample(names, 100) +missing_names=[f"None_{i}" for i in range(10)] +names_for_test=random_names+missing_names +#Данные для задания В +names_to_delete=random.sample(names,50) + +for i in range(3): + container_shuffled.append(task_A(structures_name[i], records_shuffled)[1]) + container_sorted.append(task_A(structures_name[i], records_sorted)[1]) + for j in range(5): + # Реализация задания А + result_shuffled = task_A(structures_name[i], records_shuffled)[0] + results.append([structures_name[i], mode_of_data[0], experiment_name[0], result_shuffled]) + + result_sorted= task_A(structures_name[i], records_sorted)[0] + results.append([structures_name[i], mode_of_data[1], experiment_name[0], result_sorted]) + print(f"{structures_name[i]}: Время вставки всех записей {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}") + # Реализация задания Б + result_shuffled = task_B(structures_name[i], container_shuffled[i], names_for_test) + results.append([structures_name[i], mode_of_data[0], experiment_name[1], result_shuffled]) + + result_sorted = task_B(structures_name[i], container_sorted[i], names_for_test) + results.append([structures_name[i], mode_of_data[1], experiment_name[1], result_sorted]) + print(f"{structures_name[i]}: Время нахождения 110 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted} ") + #Реализация задания В + shuffled = container_shuffled[i] + sorted = container_sorted[i] + result_shuffled = task_C(structures_name[i], shuffled, names_to_delete) + results.append([structures_name[i], mode_of_data[0], experiment_name[2], result_shuffled]) + + result_sorted = task_C(structures_name[i], sorted, names_to_delete) + results.append([structures_name[i], mode_of_data[1], experiment_name[2], result_sorted]) + print(f"{structures_name[i]}: Время удаления 50 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}") +``` + +## Сохранение результатов +```Python +current_dir=Path.cwd() +target=current_dir.parent/"docs"/"data" +csv_file=target /"results.csv" +with open(csv_file, "w", newline="",encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerows(results) +``` + +# Анализ результатов + +## Построение графиков +```Python +df = pd.read_csv(csv_file) +df_avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index() +fig, axes = plt.subplots(1, 3, figsize=(18, 6)) +for i, experiment in enumerate(experiment_name): + data_experiment = df_avg[df_avg["Операция"] == experiment] + sns.barplot(ax=axes[i],data=data_experiment, x="Структура",y="Время (сек)",hue="Режим") + axes[i].set_title(experiment) + axes[i].set_ylabel("Среднее время (сек)") + axes[i].set_yscale("log") +plt.tight_layout() +png_file= target/"graphics.png" +plt.savefig(png_file, dpi=300, bbox_inches='tight') +plt.show() +``` +![](data/graphics.png) + +### Как порядок входных данных влияет на скорость вставки в BST +Если подать на вход отсортированные данные, дерево превращается в связный список: каждый новый узел становится правым потомком предыдущего. И сложность меняется с логарифмической O(log n) на линейную O(n). Вставка для неотсортированных данных заняла 0.016531 с, а для отсортированных: 7.112118 с, разница в 430 раз. Получается, что BST сильно зависит от входных данных. +### Почему хеш-таблица почти не чувствительна к порядку +Хеш-таблица имеет низкую чувствительность к порядку входных данных, поскольку хеш-функция вычисляет индекс в массиве на основе значения ключа, обеспечивая равномерное распределение элементов по бакетам независимо от их исходной последовательности. По графикам видно, что разница между случайными и отсортированными данными минимальна. И для всех операций сложность составляет O(1). +### Почему связный список всегда медленен при поиске +Связный список всегда медленен при поиске, потому что у него отсутствует прямой доступ к элементам, и нужно перебирать все элементы по порядку. И из-за этого связный список имееет сложность O(n). +### Как удаление работает в каждой структуре +- **Связный список:** Сначала программа ищет нужный элемент, перебирая их по порядку от головы, что занимает время O(n). Как только элемент найден, то у предыдущего обновляется ссылка на элемент, который шел после удаляемого, что занимает время O(1). По графикам видно, что время удаления близко ко времени поиска. Время удаления для отсортированных данных: 0.017500 с, а для случайных: 0.018947 с. +- **Хеш-таблица:** Программа определяет нужный бакет и удаляет элемент из короткого связного списка внутри этого бакета за O(1). Время удаления для отсортированных данных: 0.000036 с, а для случайных: 0.000043 с. +- **Двоичное дерево поиска:** Нет потомков: Узел просто стирается. Один потомок: Потомок занимает место удаленного родителя. Два потомка: На место удаленного узла ставится самый минимальный элемент из его правого поддерева. Для случайных данных занимает O(log n), а для отсортированных данных занимает O(n). Время удаления для отсортированных данных: 0.039463 с, а для случайных: 0.000153 с. + +# Вывод +На основе полученных результатов можно сделать вывод: +- **Связный список:** всегда имеет линейную сложность O(n), что делает его неподходящим для задач частых вставок, частого поиска и получения данных в порядке. Но подходит только в узких случаях: максимально быстрая вставка и удаление элементов в начало или конец структуры(очереди, стеки). +- **Хеш-таблица:** является лучшим выбором для максимально задач частого поиска, добавления и удаления элементов, которые имеют сложность O(1), при этом порядок входных данных не имеет значение. Она идеально подходит для словарей и кэшей. +- **Двоичное дерево поиска:** Необходимо использовать в тех случаях, когда необходимо получать данные в отсортированном состоянии и выполнять поиск в заданном диапазоне значений. При случайных входных данных имеет хорошую сложность O(log n), но при получении отсортированных входных данных сложность возрастает до линейной O(n). + +Таким образом, для реальных задач наиболее подходят хеш-таблицы или сбалансированные деревья, если требуется получить данные в отсортированном виде. \ No newline at end of file diff --git a/YanyaevAA/docs/data/graphics.png b/YanyaevAA/docs/data/graphics.png new file mode 100644 index 0000000..8e54d63 Binary files /dev/null and b/YanyaevAA/docs/data/graphics.png differ diff --git a/YanyaevAA/docs/data/results.csv b/YanyaevAA/docs/data/results.csv new file mode 100644 index 0000000..e7252a2 --- /dev/null +++ b/YanyaevAA/docs/data/results.csv @@ -0,0 +1,91 @@ +Структура,Режим,Операция,Время (сек) +LinkedList,Случайный,Вставка,1.3509334000045783 +LinkedList,Отсортированный,Вставка,1.3042261000009603 +LinkedList,Случайный,Поиск,0.01588919999630889 +LinkedList,Отсортированный,Поиск,0.014776199997868389 +LinkedList,Случайный,Удаление,0.012387100003252272 +LinkedList,Отсортированный,Удаление,0.008979600002930965 +LinkedList,Случайный,Вставка,1.3995262999960687 +LinkedList,Отсортированный,Вставка,1.3076703999977326 +LinkedList,Случайный,Поиск,0.01563009999517817 +LinkedList,Отсортированный,Поиск,0.014876699999149423 +LinkedList,Случайный,Удаление,0.020549799999571405 +LinkedList,Отсортированный,Удаление,0.019360199999937322 +LinkedList,Случайный,Вставка,1.3874801999991178 +LinkedList,Отсортированный,Вставка,1.2993992000047 +LinkedList,Случайный,Поиск,0.015836999999010004 +LinkedList,Отсортированный,Поиск,0.014835000001767185 +LinkedList,Случайный,Удаление,0.020929600003000814 +LinkedList,Отсортированный,Удаление,0.02016870000079507 +LinkedList,Случайный,Вставка,1.3857238999989931 +LinkedList,Отсортированный,Вставка,1.3020963999952073 +LinkedList,Случайный,Поиск,0.015273999997589272 +LinkedList,Отсортированный,Поиск,0.014580000002752058 +LinkedList,Случайный,Удаление,0.0203378000005614 +LinkedList,Отсортированный,Удаление,0.019558400003006682 +LinkedList,Случайный,Вставка,1.4175892999992357 +LinkedList,Отсортированный,Вставка,1.3036662000013166 +LinkedList,Случайный,Поиск,0.015531899996858556 +LinkedList,Отсортированный,Поиск,0.014790299996093381 +LinkedList,Случайный,Удаление,0.0205294999977923 +LinkedList,Отсортированный,Удаление,0.019432499997492414 +HashTable,Случайный,Вставка,0.0048284000004059635 +HashTable,Отсортированный,Вставка,0.00405250000039814 +HashTable,Случайный,Поиск,9.529999806545675e-05 +HashTable,Отсортированный,Поиск,6.0999998822808266e-05 +HashTable,Случайный,Удаление,4.990000161342323e-05 +HashTable,Отсортированный,Удаление,3.060000017285347e-05 +HashTable,Случайный,Вставка,0.0040650000009918585 +HashTable,Отсортированный,Вставка,0.0039127000054577366 +HashTable,Случайный,Поиск,5.650000093737617e-05 +HashTable,Отсортированный,Поиск,4.53000029665418e-05 +HashTable,Случайный,Удаление,5.3499999921768904e-05 +HashTable,Отсортированный,Удаление,4.27999984822236e-05 +HashTable,Случайный,Вставка,0.004214900000079069 +HashTable,Отсортированный,Вставка,0.03241159999743104 +HashTable,Случайный,Поиск,5.999999848427251e-05 +HashTable,Отсортированный,Поиск,5.619999865302816e-05 +HashTable,Случайный,Удаление,4.2100000428035855e-05 +HashTable,Отсортированный,Удаление,3.979999746661633e-05 +HashTable,Случайный,Вставка,0.004221499999403022 +HashTable,Отсортированный,Вставка,0.004123199993046001 +HashTable,Случайный,Поиск,4.7599998652003706e-05 +HashTable,Отсортированный,Поиск,4.7299996367655694e-05 +HashTable,Случайный,Удаление,3.6600002204068005e-05 +HashTable,Отсортированный,Удаление,3.4900003811344504e-05 +HashTable,Случайный,Вставка,0.004094500000064727 +HashTable,Отсортированный,Вставка,0.0039883999997982755 +HashTable,Случайный,Поиск,4.220000118948519e-05 +HashTable,Отсортированный,Поиск,4.189999890513718e-05 +HashTable,Случайный,Удаление,3.440000000409782e-05 +HashTable,Отсортированный,Удаление,3.2000003557186574e-05 +BinarySearchTree,Случайный,Вставка,0.01629050000337884 +BinarySearchTree,Отсортированный,Вставка,7.1500338000041666 +BinarySearchTree,Случайный,Поиск,0.00027830000180983916 +BinarySearchTree,Отсортированный,Поиск,0.05988200000138022 +BinarySearchTree,Случайный,Удаление,0.0001686000032350421 +BinarySearchTree,Отсортированный,Удаление,0.03961960000015097 +BinarySearchTree,Случайный,Вставка,0.016419899999164045 +BinarySearchTree,Отсортированный,Вставка,7.092110900004627 +BinarySearchTree,Случайный,Поиск,0.0002615000048535876 +BinarySearchTree,Отсортированный,Поиск,0.060809999995399266 +BinarySearchTree,Случайный,Удаление,0.00014789999841013923 +BinarySearchTree,Отсортированный,Удаление,0.039564300001075026 +BinarySearchTree,Случайный,Вставка,0.016564800003834534 +BinarySearchTree,Отсортированный,Вставка,7.115889100001368 +BinarySearchTree,Случайный,Поиск,0.000284100002318155 +BinarySearchTree,Отсортированный,Поиск,0.06236229999922216 +BinarySearchTree,Случайный,Удаление,0.00015389999316539615 +BinarySearchTree,Отсортированный,Удаление,0.03888590000133263 +BinarySearchTree,Случайный,Вставка,0.01672099999996135 +BinarySearchTree,Отсортированный,Вставка,7.124367500000517 +BinarySearchTree,Случайный,Поиск,0.00027630000113276765 +BinarySearchTree,Отсортированный,Поиск,0.06082099999912316 +BinarySearchTree,Случайный,Удаление,0.00014789999841013923 +BinarySearchTree,Отсортированный,Удаление,0.03982890000042971 +BinarySearchTree,Случайный,Вставка,0.016656699997838587 +BinarySearchTree,Отсортированный,Вставка,7.078189200001361 +BinarySearchTree,Случайный,Поиск,0.0002753000007942319 +BinarySearchTree,Отсортированный,Поиск,0.05944880000606645 +BinarySearchTree,Случайный,Удаление,0.00014619999274145812 +BinarySearchTree,Отсортированный,Удаление,0.039416899999196175 diff --git a/YanyaevAA/task1/[1].py b/YanyaevAA/task1/[1].py new file mode 100644 index 0000000..5b67113 --- /dev/null +++ b/YanyaevAA/task1/[1].py @@ -0,0 +1,256 @@ +import time +from pathlib import Path +import random +import csv +import sys +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +sys.setrecursionlimit(12000) +#Связный список +def ll_insert(head, name, phone): + current = head + while current: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + new_node = {'name': name, 'phone': phone, 'next': None} + new_node['next'] = head + return new_node + +def ll_find(head, name): + current = head + while current: + if current['name'] == name: + return current['phone'] + current = current['next'] + return None + +def ll_delete(head, name): + if head['name'] == name: + return head['next'] + current = head + while current['next']: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + break + current = current['next'] + return head + +def ll_list_all(head): + data= [] + current = head + while current: + data.append((current['name'], current['phone'])) + current = current['next'] + return sorted(data) + +#хэш-таблица +def ht_insert(buckets, name, phone): + id=hash(name)%len(buckets) + buckets[id] = ll_insert(buckets[id], name, phone) + +def ht_find(buckets, name): + id= hash(name)%len(buckets) + return ll_find(buckets[id], name) + +def ht_delete(buckets, name): + id= hash(name)%len(buckets) + buckets[id] = ll_delete(buckets[id], name) + +def ht_list_all(buckets): + data = [] + for head in buckets: + current = head + while current: + data.append((current['name'], current['phone'])) + current = current['next'] + return sorted(data) + + + +#Двоичное дерево поиска +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + if name == root['name']: + root['phone'] = phone + elif name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + else: + root['right'] = bst_insert(root['right'], name, phone) + return root + +def bst_find(root, name): + if root is None: + return None + if root['name'] == name: + return root['phone'] + elif name root['name']: + root['right'] = bst_delete(root['right'], name) + else: + if root['left'] is None: + return root['right'] + elif root['right'] is None: + return root['left'] + min=minimum(root['right']) + root['name']=min['name'] + root['phone']=min['phone'] + root['right']=bst_delete(root['right'], min['name']) + return root + +def bst_list_all(root): + result=[] + if root: + result.extend(bst_list_all(root['left'])) + result.append((root['name'], root['phone'])) + result.extend(bst_list_all(root['right'])) + return result + + + +#1. Генерация тестовых данных +def generate(n=10000): + records = [(f"User_{i:05d}", f"+7 ({random.randint(100, 999)}) {random.randint(100, 999)}-{random.randint(00, 99):02}-{random.randint(00, 99):02}") for i in range(n)] + records_sorted =records.copy() + records_shuffled=records.copy() + random.shuffle(records_shuffled) + return records_sorted, records_shuffled + +#3.Проведение замеров +#А. Вставка всех записей +def task_A(structure_name, data): + start =time.perf_counter() + if structure_name=="LinkedList": + head=None + for name, phone in data: + head = ll_insert(head, name, phone) + container=head + elif structure_name=="HashTable": + buckets=[None]*1000 + for name, phone in data: + ht_insert(buckets, name, phone) + container=buckets + elif structure_name=="BinarySearchTree": + root=None + for name, phone in data: + root = bst_insert(root, name, phone) + container=root + end = time.perf_counter() + elapsed = end - start + return elapsed, container + +#Б. Поиск 100 случайных записей +def task_B(structure_name,container, data): + start=time.perf_counter() + if structure_name=="LinkedList": + for name in data: + ll_find(container, name) + elif structure_name=="HashTable": + for name in data: + ht_find(container, name) + elif structure_name=="BinarySearchTree": + for name in data: + bst_find(container, name) + end=time.perf_counter() + elapsed = end - start + return elapsed + +#В. Удаление 50 случайных чисел +def task_C(structure_name,container, data): + start=time.perf_counter() + if structure_name=="LinkedList": + for name in data: + container=ll_delete(container, name) + elif structure_name=="HashTable": + for name in data: + ht_delete(container, name) + elif structure_name=="BinarySearchTree": + for name in data: + container = bst_delete(container, name) + end=time.perf_counter() + elapsed = end - start + return elapsed +results=[["Структура", "Режим", "Операция", "Время (сек)"]] +structures_name=["LinkedList", "HashTable", "BinarySearchTree"] +experiment_name=["Вставка", "Поиск", "Удаление"] +mode_of_data=["Случайный", "Отсортированный"] + +records_sorted, records_shuffled = generate() +container_shuffled=[]#хранилище структур со случайными данными +container_sorted=[]#хранилище структур с отсортированными данными +names=[record[0] for record in records_shuffled] +#Данные для задания Б +random_names=random.sample(names, 100) +missing_names=[f"None_{i}" for i in range(10)] +names_for_test=random_names+missing_names +#Данные для задания В +names_to_delete=random.sample(names,50) + +for i in range(3): + container_shuffled.append(task_A(structures_name[i], records_shuffled)[1]) + container_sorted.append(task_A(structures_name[i], records_sorted)[1]) + for j in range(5): + # Реализация задания А + result_shuffled = task_A(structures_name[i], records_shuffled)[0] + results.append([structures_name[i], mode_of_data[0], experiment_name[0], result_shuffled]) + + result_sorted= task_A(structures_name[i], records_sorted)[0] + results.append([structures_name[i], mode_of_data[1], experiment_name[0], result_sorted]) + print(f"{structures_name[i]}: Время вставки всех записей {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}") + # Реализация задания Б + result_shuffled = task_B(structures_name[i], container_shuffled[i], names_for_test) + results.append([structures_name[i], mode_of_data[0], experiment_name[1], result_shuffled]) + + result_sorted = task_B(structures_name[i], container_sorted[i], names_for_test) + results.append([structures_name[i], mode_of_data[1], experiment_name[1], result_sorted]) + print(f"{structures_name[i]}: Время нахождения 110 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted} ") + #Реализация задания В + shuffled = container_shuffled[i] + sorted = container_sorted[i] + result_shuffled = task_C(structures_name[i], shuffled, names_to_delete) + results.append([structures_name[i], mode_of_data[0], experiment_name[2], result_shuffled]) + + result_sorted = task_C(structures_name[i], sorted, names_to_delete) + results.append([structures_name[i], mode_of_data[1], experiment_name[2], result_sorted]) + print(f"{structures_name[i]}: Время удаления 50 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}") + +#4. Сохранение результатов\ +current_dir=Path.cwd() +target=current_dir.parent/"docs"/"data" +csv_file=target /"results.csv" +with open(csv_file, "w", newline="",encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerows(results) + +#Построение графиков +df = pd.read_csv(csv_file) +df_avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index() +fig, axes = plt.subplots(1, 3, figsize=(18, 6), sharey=True) +for i, experiment in enumerate(experiment_name): + data_experiment = df_avg[df_avg["Операция"] == experiment] + sns.barplot(ax=axes[i],data=data_experiment, x="Структура",y="Время (сек)",hue="Режим") + axes[i].set_title(experiment) + axes[i].set_ylabel("Среднее время (сек)") + axes[i].set_yscale("log") +plt.tight_layout() +png_file= target/"graphics.png" +plt.savefig(png_file, dpi=300, bbox_inches='tight') +plt.show() \ No newline at end of file diff --git a/YanyaevAA/task2/docs/Report_2.md b/YanyaevAA/task2/docs/Report_2.md new file mode 100644 index 0000000..46fce01 --- /dev/null +++ b/YanyaevAA/task2/docs/Report_2.md @@ -0,0 +1,238 @@ +# Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами) +Цель работы: +Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры. +## Выбранные паттерны проектирования +- Builder: шаблон проектирования, который инкапсулирует создание объекта и позволяет разделить его на различные этапы. В программе позволяет загружать лабиринт из файла. +- Strategy: поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путём определения соответствующего класса. В программе помогает менять способ поиска пути. +- Observer: это поведенческий паттерн, который позволяет объектам оповещать другие объекты об изменениях своего состояния. В программе служит для визуализации лабиринта и пути к выходу, а также для уведомляет о событиях ("path_found"). + +## Диаграмма классов +![](data/mermaid_diagram.png) +# Листинги ключевых классов +## Паттерн Builder +```Python +class MazeBuilder(ABC): + @abstractmethod + def buildFromFile(self, filename): + pass + +class TextFileMazeBuilder(MazeBuilder): + def buildFromFile(self, filename): + with open(filename, 'r') as f: + lines = [line.rstrip('\n') for line in f] + height = len(lines) + width = max(len(line) for line in lines) + + grid=[] + start_cell=None + exit_cell=None + for y in range(height): + row=[] + for x in range(width): + char=lines[y][x] + + isWall = (char == '#') + isStart = (char == 'S') + isExit = (char == 'E') + + cell=Cell(x, y, isWall, isStart, isExit) + + if isStart: + start_cell =cell + if isExit: + exit_cell =cell + row.append(cell) + grid.append(row) + return Maze(grid, width, height, start_cell, exit_cell) +``` +## Паттерн Strategy +```Python +class PathFindingStrategy(ABC): + @abstractmethod + def findPath(self,maze, start, exit): + pass + +class BFS(PathFindingStrategy): + def findPath(self, maze, start, exit): + queue = deque([start]) + traveled_path={start: None} + + while queue: + current = queue.popleft() + if current==exit: + path=[] + while current is not None: + path.append(current) + current = traveled_path[current] + return path[::-1], len(traveled_path) + for neighbor in maze.getNeighbors(current): + if neighbor not in traveled_path: + traveled_path[neighbor] = current + queue.append(neighbor) + return [], len(traveled_path) + +class DFS(PathFindingStrategy): + def findPath(self, maze, start, exit): + stack = [start] + traveled_path={start: None} + + while stack: + current = stack.pop() + if current == exit: + path = [] + while current is not None: + path.append(current) + current = traveled_path[current] + return path[::-1], len(traveled_path) + for neighbor in maze.getNeighbors(current): + if neighbor not in traveled_path: + traveled_path[neighbor] = current + stack.append(neighbor) + return [], len(traveled_path) +class AStar(PathFindingStrategy): + def findPath(self, maze, start, exit): + count = 0 + open_set = [(0, count, start)] + traveled_path = {start: None} + g_score = {start: 0} + while open_set: + _,_,current = heapq.heappop(open_set) + if current == exit: + path = [] + while current is not None: + path.append(current) + current = traveled_path[current] + return path[::-1], len(traveled_path) + for neighbor in maze.getNeighbors(current): + g_score_new = g_score[current]+1 + if neighbor not in g_score or g_score_new < g_score[neighbor]: + traveled_path[neighbor] = current + g_score[neighbor] = g_score_new + f_score = g_score_new + abs(neighbor.x - exit.x) + abs(neighbor.y - exit.y) + count += 1 + heapq.heappush(open_set, (f_score, count, neighbor)) + return [],len(traveled_path) +``` +## Паттерн Observer с объектом Event +```Python +class Event: + def __init__(self, event_type, data=None): + self.event_type = event_type + self.data = data + +class Observer(ABC): + @abstractmethod + def update(self, event): + pass + +class ConsoleView(Observer): + def update(self, event): + if event.event_type == "path_found": + stats=event.data + print("Путь найден:") + print("Время выполнения:", stats.time) + print("Количество посещённых клеток:", stats.visited_cells) + print("Длина найденного пути:", stats.path_length) + if event.event_type == "maze_loaded": + print("Загружен новый лабиринт") + + def render(self, maze, path): + for y in range(maze.height): + row_str="" + for x in range(maze.width): + cell=maze.getCell(x, y) + if cell == maze.start: + row_str += "S" + elif cell == maze.exit: + row_str += "E" + elif cell in path: + row_str += "·" + elif cell.isWall: + row_str += "#" + else: + row_str += " " + print(row_str) +``` +## Реализация программы +```Python +mazes = ["10x10.txt","50x50.txt","100x100.txt","empty.txt","without_exit.txt"] +results =[["лабиринт", + "стратегия", + "время_мс", + "посещено_клеток", + "длина_пути"]] +strategies = { + "BFS": BFS(), + "DFS": DFS(), + "AStar": AStar() +} +builder = TextFileMazeBuilder() +n=10 +directory = os.path.join("docs", "data") + +for maze_name in mazes: + print(maze_name) + file_name=os.path.join(directory, maze_name) + maze = builder.buildFromFile(file_name) + viewer=ConsoleView() + for strategy_name, strategy in strategies.items(): + total_time = 0.0 + total_visited = 0 + total_path_length = 0 + + solver = MazeSolver(maze, strategy) + + for i in range(n): + stats = solver.solve() + total_time += stats.time + total_visited += stats.visited_cells + total_path_length += stats.path_length + avg_time = total_time/n + avg_visited = total_visited/n + avg_path_length = total_path_length/n + print("-"*100) + print(f"{maze_name} стратегия: {strategy_name} время_мс: {avg_time} посещено_клеток: {avg_visited} длина_пути: {avg_path_length}") + results.append([maze_name, strategy_name, avg_time, avg_visited, avg_path_length]) + path, _ = strategy.findPath(maze, maze.start, maze.exit) + viewer.render(maze, path) +csv_filename = os.path.join(directory, "maze_results.csv") +with open(csv_filename, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerows(results) +``` +# Результаты экспериментов +В ходе тестирования каждый алгоритм запускался по 10 раз на каждом типе лабиринта + +| Лабиринт | Стратегия | Время (мс) | Посещено клеток | Длина пути | +| :--- | :--- |:------------------------------:| :---: | :---: | +| **10x10.txt** | BFS
DFS
AStar | 0.0264
0.0368
0.0320 | 30.0
43.0
30.0 | 29.0
29.0
29.0 | +| **50x50.txt** | BFS
DFS
AStar | 0.6698
0.4722
0.5986 | 799.0
562.0
539.0 | 316.0
350.0
316.0 | +| **100x100.txt** | BFS
DFS
AStar | 3.0005
0.4454
0.5787 | 3576.0
595.0
536.0 | 196.0
364.0
196.0 | +| **empty.txt** | BFS
DFS
AStar | 0.2904
0.1618
0.4074 | 324.0
324.0
324.0 | 35.0
171.0
35.0 | +| **without_exit.txt** | BFS
DFS
AStar | 0.0407
0.0408
0.0519 | 48.0
48.0
48.0 | 0.0
0.0
0.0 | + +Сравнительные графики: +![](data/maze_graphics.png) +# Анализ эффективности алгоритмов и применимости паттернов. +- **BFS**: + - Время: Алгоритм демонстрирует рост времени с увеличением площади лабиринта и количества ветвлений: минимальное время в лабиринте 10x10: 0.0264; максимальное время в лабиринте 100x100: 3.0005. + - Количество посещенных клеток: Также показывает рост количества посещенных клеток с увеличением площади лабиринта. В лабиринтах 50x50, 100x100 показывает неэффективность алгоритма с точки зрения объема работы: 799 и 3576 соответственно. + - Длина пути: Во всех алгоритмах показывает выбор оптимального пути. +- **DFS**: + - Время: В маленьком лабиринте (10x10) работает медленнее других, но на больших и пустом лабиринтах является быстрейшим алгоритмом. Это происходит, потому что DFS использует стек, который в Python имеет сложность O(1). + - Количество посещенных клеток: На полученных данных, мы видим хорошие значения посещенных клеток, но для такого же по размерам лабиринта, но с другими путями, количество посещенных клеток может измениться. Это происходит из-за того, что количество посещенных клеток зависит от лабиринта, и "повезло" ли алгоритму сразу же наткнуться на коридор, ведущий к выходу. + - Длина пути: На маленьком лабиринте (10x10) показывает одинаковую длину пути со всеми алгоритмами. Но для других лабиринтов показывает пути значительно больше чем у других. DFS не гарантирует оптимальность пути, потому что ищет до первого пути ведущего к выходу, который может быть большим. Но на лабиринте без развилок (10x10) показывает результат на ровне с другими алгоритмами. +- **A***: + - Время: На большинстве лабиринтах показывает средние результат, в лабиринте без выхода и в пустом лабиринте показывает худшие результаты. Это происходит, потому что алгоритм использует приоритетную очередь, которая имеет логарифмическую сложность O(log n), также постоянная перестройка очереди занимает чуть больше времени. + - Количество посещенных клеток: Во всех случаях имеет минимальное количество посещенных клеток. Это происходит благодаря эвристической функции (Манхэттенское расстояние). Она минимизирует количество посещенных клеток, избегая ложные направления. Преимущество алгоритма хорошо видно на больших лабиринтах (50x50, 100x100) + - Длина пути: Также, как и BFS, показывает минимальную длину пути. Алгоритм выбирает кратчайший путь, также благодаря эвристике. +## Выводы по алгоритмам +- **BFS**: Всегда находит кратчайший путь, но для больших лабиринтов тратит много времени, а также для поиска пути исследует большое количество клеток. +- **DFS**: Алгоритм подходит для быстрого нахождения пути для простых или линейных лабиринтов. Но не подходит для выбора кратчайшего пути. +- **A***: Алгоритм показывает наибольшую общую эффективность. Всегда находит кратчайший путь и для поиска посещает наименьшее количество клеток, имеет хорошую скорость выполнения. +## Применяемость паттернов +- **Builder**: Позволяет отделить структуру самого лабиринта от источника его данных. Позволяет свободно, при необходимости, добавлять другие форматы, из которых будет строиться лабиринт. +- **Strategy**: Инкапсулирует алгоритмы поиска пути в отдельные классы, что позволяет свободно добавлять другие алгоритмы поиска +- **Observer**: Реализует механизм уведомления о шагах алгоритма. Позволяет визуализировать процесс поиска клеток, полностью отделяя логику вычислений от графического интерфейса. +# Вывод +Применение принципов И паттернов ООП позволило создать устойчивую к изменениям программу. Можно свободно добавлять новые алгоритмы поиска или новые способы вывода данных, создавая новые подклассы и не меняя ни единой строчки кода. Без ООП стало бы невозможно легко переключиться с чтения текстовых файлов на файлы другого формата или на алгоритм случайной генерации карт. В итоге ООП позволяет расширять код, не затрагивая работоспособность других компонентов. \ No newline at end of file diff --git a/YanyaevAA/task2/docs/data/100x100.txt b/YanyaevAA/task2/docs/data/100x100.txt new file mode 100644 index 0000000..a030319 --- /dev/null +++ b/YanyaevAA/task2/docs/data/100x100.txt @@ -0,0 +1,101 @@ +#################################################################################################### +#S # # # # # # # # # +# ####### # ##### # ############# # ####### # ##### # ############# # ####### # ##### # ########## # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### # # # # # # # ######### # # # ### # # # # # # # ######### # # # ### # # # # # # # ###### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### # # # # # # # ##### # # # # # ### # # # # # # # ##### # # # # # ### # # # # # # # ## # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ####### # ############# ##### # # ####### # ############# ##### # # ####### # ############# # ## +# # # # # # # # # # # +# ################################# # ############################### # ########################## # +# # # # +# ################################# # ############################### # ########################## # +# # # # # # # +# # ############################### # # ############################# # # ######################## # +# # # # # # # # # # # +# # # ########################### # # # # ########################### # # # ###################### # +# # # # # # # # # # # # # # # # # +# # # # ####################### # # # # # # ####################### # # # # # ################## # # +# # # # # # # # # # # # # # # # # # # # # # # +# # # # # ################### # # # # # # # # ################### # # # # # # # ############## # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # ############### # # # # # # # # # # ############### # # # # # # # # # ########## # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # ########### # # # # # # # # # # # # ########### # # # # # # # # # # # ###### # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # ####### # # # # # # # # # # # # # # ####### # # # # # # # # # # # # # ## # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # ### # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # ## # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # ### ##### # # # # # # # ### ##### ### ##### # # # # # # # ### ##### ### # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # ### ######### # # # # # ### ######### ######### # # # # # ### ######### ### # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # ### ############# # # # ### ############# ######### # # # ### ############# ### # # # ## +# # # # # # # # # # # # # # # # # # # # ## +# # # # # ### ############### # # ### ############### ######### # # ### ############### ### # # # ## +# # # # # # # # # # # # # # # # # ## +# # # # ### ################# # ### ################# ######### # ### ################# ### # # # ## +# # # # # # # # # # # # # # ## +# # # ### ##################### ### ################# ######### ### ##################### ### # # ## +# # # # # # # # # # # ## +# # ### ######################### # ################# ######### # # ######################### ### ## +# # # # # # # ## +# ### ############################# ################# ######### # ############################# #### +# # # # ## +### ################################################# ######### # ################################## +# # # # +# ################################################### ######### # ################################## +# # # # # # +# # ################################################# ######### # # ################################ +# # # # # # # # +# # # ############################################### ######### # # # ############################## +# # # # # # # # # # +# # # # ############################################# ######### # # # # ############################ +# # # # # # # # # # # # +# # # # # ########################################### ######### # # # # # ########################## +# # # # # # # # # # # # # # +# # # # # # ######################################### ######### # # # # # # ######################## +# # # # # # # # # # # # # # # # +# # # # # # # ####################################### ######### # # # # # # # ###################### +# # # # # # # # # # # # # # # # # # +# # # # # # # # ##################################### ######### # # # # # # # # #################### +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # ################################### ######### # # # # # # # # # ################## +# # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ################################# ######### # # # # # # # # # # ################ +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ############################### ######### # # # # # # # # # # # ############## +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # ############################# ######### # # # # # # # # # # # # ############ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # ########################### ######### # # # # # # # # # # # # # ########## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # ######################### ######### # # # # # # # # # # # # # # ##### ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # ####################### ######### # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # ################### # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # ############### # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # ########### # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # ####### # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## +# # # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #### +# # # # # # # # # # # # # # # ### # # # # # # # # # # # # # # # # # +# E# +#################################################################################################### \ No newline at end of file diff --git a/YanyaevAA/task2/docs/data/10x10.txt b/YanyaevAA/task2/docs/data/10x10.txt new file mode 100644 index 0000000..3c17166 --- /dev/null +++ b/YanyaevAA/task2/docs/data/10x10.txt @@ -0,0 +1,10 @@ +########## +#S # +######## # +# # +# ######## +# # +######## # +# # +# E# +########## diff --git a/YanyaevAA/task2/docs/data/50x50.txt b/YanyaevAA/task2/docs/data/50x50.txt new file mode 100644 index 0000000..1e119cb --- /dev/null +++ b/YanyaevAA/task2/docs/data/50x50.txt @@ -0,0 +1,51 @@ +################################################## +#S# # # # # # +# # ### # ##### # ### ####### ### ### # ### ### # +# # # # # # # # # # # # # # # # # +##### # ### # # # # ### # # ####### # ####### ### +# # # # # # # # # # # # +### # ####### # # ####### # ### ### # # ### ### # +# # # # # # # # # # # # # +# ######### # ######### # ### ### ####### ### # # +# # # # # # # # # # # # # +# # ### # ### # ####### ### # # # # ### # ##### # +# # # # # # # # # # # # # +### # ### # ######### ### ########### # # # ### # +# # # # # # # # # # # # +# ##### # # # ##### ### # # ######### # ####### # +# # # # # # # # # # # # # # # # +# # # # # # # # # ### # # # # ##### # # # ##### # +# # # # # # # # # # # # # # # # # # # # # +# # # # # # # # ### # # ##### # # # # # # # # # # +# # # # # # # # # # # # # # +##### ####### ### ####### ########### ####### # # +# # # # # # # # # +# ####### ####### # ####### ##### # ### ####### # +# # # # # # # # # # # # +# # ####### ####### ##### # # # # ### # # ######## +# # # # # # # # # # # # # # # +# # # ####### ### ##### # # # # ### # # # # ### # +# # # # # # # # # # # # # # # # # +# # # # ####### ### # ####### ### # # # ##### # # +# # # # # # # # # # # # # # +# # # # # ####### ### # ####### ### # ######### # +# # # # # # # # # # # +####### # # ####### ### # ### ### # ########### # +# # # # # # # # # # # +# ####### # # ####### ### # # # ############### # +# # # # # # # # # +# # ####### ########### ####### # ############# # +# # # # # # # # # +# # # ####### ########### ####### # ######### # # +# # # # # # # # # # # # +# # # # ####### ########### ### # # # ##### # # # +# # # # # # # # # # # # # # +# # # # # ####### ########### # # # ####### # # # +# # # # # # # # # # +# ####### # ####### ############### # ######### # +# # # # # # # +# ######### # ####### ####################### # # +# # # # +############# ################################# # +# E# +################################################## \ No newline at end of file diff --git a/YanyaevAA/task2/docs/data/empty.txt b/YanyaevAA/task2/docs/data/empty.txt new file mode 100644 index 0000000..10bbaf0 --- /dev/null +++ b/YanyaevAA/task2/docs/data/empty.txt @@ -0,0 +1,20 @@ +#################### +#S # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# E# +#################### \ No newline at end of file diff --git a/YanyaevAA/task2/docs/data/maze_graphics.png b/YanyaevAA/task2/docs/data/maze_graphics.png new file mode 100644 index 0000000..5994124 Binary files /dev/null and b/YanyaevAA/task2/docs/data/maze_graphics.png differ diff --git a/YanyaevAA/task2/docs/data/maze_results.csv b/YanyaevAA/task2/docs/data/maze_results.csv new file mode 100644 index 0000000..11add20 --- /dev/null +++ b/YanyaevAA/task2/docs/data/maze_results.csv @@ -0,0 +1,16 @@ +лабиринт,стратегия,время_мс,посещено_клеток,длина_пути +10x10.txt,BFS,0.02643000007083174,30.0,29.0 +10x10.txt,DFS,0.03684999974211678,43.0,29.0 +10x10.txt,AStar,0.0320400002237875,30.0,29.0 +50x50.txt,BFS,0.6697899993014289,799.0,316.0 +50x50.txt,DFS,0.4721500004961854,562.0,350.0 +50x50.txt,AStar,0.5986000000120839,539.0,316.0 +100x100.txt,BFS,3.000480001355754,3576.0,196.0 +100x100.txt,DFS,0.4453900000953581,595.0,364.0 +100x100.txt,AStar,0.5786999998235842,522.0,196.0 +empty.txt,BFS,0.29044999937468674,324.0,35.0 +empty.txt,DFS,0.16180000056920107,324.0,171.0 +empty.txt,AStar,0.40738000025157817,324.0,35.0 +without_exit.txt,BFS,0.04074000025866553,48.0,0.0 +without_exit.txt,DFS,0.040809999700286426,48.0,0.0 +without_exit.txt,AStar,0.05192000025999732,48.0,0.0 diff --git a/YanyaevAA/task2/docs/data/mermaid_diagram.png b/YanyaevAA/task2/docs/data/mermaid_diagram.png new file mode 100644 index 0000000..bb479d1 Binary files /dev/null and b/YanyaevAA/task2/docs/data/mermaid_diagram.png differ diff --git a/YanyaevAA/task2/docs/data/without_exit.txt b/YanyaevAA/task2/docs/data/without_exit.txt new file mode 100644 index 0000000..2c4c62a --- /dev/null +++ b/YanyaevAA/task2/docs/data/without_exit.txt @@ -0,0 +1,15 @@ +############### +#S # +# ########### # +# # # # +# # ####### # # +# # # # # # +# # # ### # # # +# # # #E# # # # +# # # ### # # # +# # # # # # +# # ####### # # +# # # # +# ########### # +# # +############### \ No newline at end of file diff --git a/YanyaevAA/task2/task_2.py b/YanyaevAA/task2/task_2.py new file mode 100644 index 0000000..5c32619 --- /dev/null +++ b/YanyaevAA/task2/task_2.py @@ -0,0 +1,299 @@ +from abc import ABC, abstractmethod +from collections import deque +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns +import heapq +import time +import os +import csv +#Этап 1 +class Cell: + def __init__(self, x, y, isWall=False, isStart=False, isExit=False): + self.x = x + self.y = y + self.isWall = isWall + self.isStart = isStart + self.isExit = isExit + + def isPassable(self): + return not self.isWall + +class Maze: + def __init__(self, cells, width, height, start, exit): + self.width = width + self.height = height + self.cells =cells + self.start = start + self.exit = exit + + def getCell(self, x, y): + if 0 <= x< self.width and 0 <=y< self.height: + return self.cells[y][x] + return None + + def getNeighbors(self, cell: Cell): + neighbors = [] + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] + + for dir_x, dir_y in directions: + neigh_x = cell.x+dir_x + neigh_y = cell.y+dir_y + neighbor = self.getCell(neigh_x, neigh_y) + if neighbor and neighbor.isPassable(): + neighbors.append(neighbor) + return neighbors + +#Этап 2 +class MazeBuilder(ABC): + @abstractmethod + def buildFromFile(self, filename): + pass + +class TextFileMazeBuilder(MazeBuilder): + def buildFromFile(self, filename): + with open(filename, 'r') as f: + lines = [line.rstrip('\n') for line in f] + height = len(lines) + width = max(len(line) for line in lines) + + grid=[] + start_cell=None + exit_cell=None + for y in range(height): + row=[] + for x in range(width): + char=lines[y][x] + + isWall = (char == '#') + isStart = (char == 'S') + isExit = (char == 'E') + + cell=Cell(x, y, isWall, isStart, isExit) + + if isStart: + start_cell =cell + if isExit: + exit_cell =cell + row.append(cell) + grid.append(row) + return Maze(grid, width, height, start_cell, exit_cell) + +#Этап 3 +class PathFindingStrategy(ABC): + @abstractmethod + def findPath(self,maze, start, exit): + pass + +class BFS(PathFindingStrategy): + def findPath(self, maze, start, exit): + queue = deque([start]) + traveled_path={start: None} + + while queue: + current = queue.popleft() + if current==exit: + path=[] + while current is not None: + path.append(current) + current = traveled_path[current] + return path[::-1], len(traveled_path) + for neighbor in maze.getNeighbors(current): + if neighbor not in traveled_path: + traveled_path[neighbor] = current + queue.append(neighbor) + return [], len(traveled_path) + +class DFS(PathFindingStrategy): + def findPath(self, maze, start, exit): + stack = [start] + traveled_path={start: None} + + while stack: + current = stack.pop() + if current == exit: + path = [] + while current is not None: + path.append(current) + current = traveled_path[current] + return path[::-1], len(traveled_path) + for neighbor in maze.getNeighbors(current): + if neighbor not in traveled_path: + traveled_path[neighbor] = current + stack.append(neighbor) + return [], len(traveled_path) +class AStar(PathFindingStrategy): + def findPath(self, maze, start, exit): + count = 0 + open_set = [(0, count, start)] + traveled_path = {start: None} + g_score = {start: 0} + while open_set: + _,_,current = heapq.heappop(open_set) + if current == exit: + path = [] + while current is not None: + path.append(current) + current = traveled_path[current] + return path[::-1], len(traveled_path) + for neighbor in maze.getNeighbors(current): + g_score_new = g_score[current]+1 + if neighbor not in g_score or g_score_new < g_score[neighbor]: + traveled_path[neighbor] = current + g_score[neighbor] = g_score_new + f_score = g_score_new + abs(neighbor.x - exit.x) + abs(neighbor.y - exit.y) + count += 1 + heapq.heappush(open_set, (f_score, count, neighbor)) + return [],len(traveled_path) + +#Этап 4 +class SearchStats: + def __init__(self, time, visited_cells, path_length): + self.time = time + self.visited_cells = visited_cells + self.path_length = path_length + +class MazeSolver: + def __init__(self, maze, strategy): + self.maze = maze + self.strategy = strategy + self.observers = [] + def addObserver(self, observer): + self.observers.append(observer) + def setStrategy(self, strategy): + self.strategy = strategy + def solve(self): + start_cell = self.maze.start + exit_cell = self.maze.exit + + start_time = time.perf_counter() + path, visited_cells = self.strategy.findPath(self.maze, start_cell, exit_cell) + end_time = time.perf_counter() + + time_ms = (end_time - start_time) * 1000 + path_length = len(path) + stats=SearchStats(time_ms, visited_cells, path_length) + event = Event("path_found", data=stats) + for observer in self.observers: + observer.update(event) + + return stats + +#Этап 5 +#5.1 +class Event: + def __init__(self, event_type, data=None): + self.event_type = event_type + self.data = data + +class Observer(ABC): + @abstractmethod + def update(self, event): + pass + +class ConsoleView(Observer): + def update(self, event): + if event.event_type == "path_found": + stats=event.data + print("Путь найден:") + print("Время выполнения:", stats.time) + print("Количество посещённых клеток:", stats.visited_cells) + print("Длина найденного пути:", stats.path_length) + if event.event_type == "move": + x, y = event.data + print(f"Игрок переместился в ячейку: {x}, {y}") + if event.event_type == "maze_loaded": + print("Загружен новый лабиринт") + + def render(self, maze, path): + for y in range(maze.height): + row_str="" + for x in range(maze.width): + cell=maze.getCell(x, y) + if cell == maze.start: + row_str += "S" + elif cell == maze.exit: + row_str += "E" + elif cell in path: + row_str += "·" + elif cell.isWall: + row_str += "#" + else: + row_str += " " + print(row_str) + +#Этап 6 +mazes = ["10x10.txt","50x50.txt","100x100.txt","empty.txt","without_exit.txt"] +results =[["лабиринт", + "стратегия", + "время_мс", + "посещено_клеток", + "длина_пути"]] +strategies = { + "BFS": BFS(), + "DFS": DFS(), + "AStar": AStar() +} +builder = TextFileMazeBuilder() +n=10 +directory = os.path.join("docs", "data") + +for maze_name in mazes: + print(maze_name) + file_name=os.path.join(directory, maze_name) + maze = builder.buildFromFile(file_name) + viewer=ConsoleView() + for strategy_name, strategy in strategies.items(): + total_time = 0.0 + total_visited = 0 + total_path_length = 0 + + solver = MazeSolver(maze, strategy) + + for _ in range(n): + stats = solver.solve() + total_time += stats.time + total_visited += stats.visited_cells + total_path_length += stats.path_length + avg_time = total_time/n + avg_visited = total_visited/n + avg_path_length = total_path_length/n + print(f"{maze_name} стратегия: {strategy_name} время_мс: {avg_time} посещено_клеток: {avg_visited} длина_пути: {avg_path_length}") + results.append([maze_name, strategy_name, avg_time, avg_visited, avg_path_length]) + path, _ = strategy.findPath(maze, maze.start, maze.exit) + viewer.render(maze, path) +csv_filename = os.path.join(directory, "maze_results.csv") +with open(csv_filename, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerows(results) + +#Графики +df = pd.read_csv(csv_filename) +fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(16, 6)) + +sns.barplot(data=df, x='лабиринт', y='время_мс', hue='стратегия', ax=ax1) +ax1.set_title('Время выполнения алгоритмов') +ax1.set_xlabel('Лабиринты') +ax1.set_ylabel('Время (мс)') +ax1.grid(axis='y', linestyle='--', alpha=0.7) +ax1.legend() + +sns.barplot(data=df, x='лабиринт', y='посещено_клеток', hue='стратегия', ax=ax2) +ax2.set_title('Количество посещенных клеток') +ax2.set_xlabel('Лабиринты') +ax2.set_ylabel('Количество клеток') +ax2.grid(axis='y', linestyle='--', alpha=0.7) +ax2.legend() +plt.tight_layout() + +sns.barplot(data=df, x='лабиринт', y='длина_пути', hue='стратегия', ax=ax3) +ax3.set_title('Длина пути') +ax3.set_xlabel('Лабиринты') +ax3.set_ylabel('Количество клеток') +ax3.grid(axis='y', linestyle='--', alpha=0.7) +ax3.legend() +plt.tight_layout() + +img = os.path.join(directory, "maze_graphics.png") +plt.savefig(img, dpi=300) +plt.show()