From b6debe470661d6701236317e568f7f93c534ddeb Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:49:01 +0000 Subject: [PATCH 01/15] [1] Initial linked list implementation --- anikinvd/docs/data/1-st-exercise/phonebook.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 anikinvd/docs/data/1-st-exercise/phonebook.py diff --git a/anikinvd/docs/data/1-st-exercise/phonebook.py b/anikinvd/docs/data/1-st-exercise/phonebook.py new file mode 100644 index 0000000..fc7f8aa --- /dev/null +++ b/anikinvd/docs/data/1-st-exercise/phonebook.py @@ -0,0 +1,67 @@ + +def llist_insert(head, name, phone): + current = head + while current is not None: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + + new_node = {'name': name, 'phone': phone, 'next': None} + + if head is None: + return new_node + + current = head + while current['next'] is not None: + current = current['next'] + current['next'] = new_node + return head + + +def llist_find(head, name): + current = head + while current is not None: + if current['name'] == name: + return current['phone'] + current = current['next'] + return None + + +def llist_delete(head, name): + if head is None: + return None + + if head['name'] == name: + return head['next'] + + prev = head + current = head['next'] + while current is not None: + if current['name'] == name: + prev['next'] = current['next'] + return head + prev = current + current = current['next'] + return head + + +def llist_get_all(head): + entries = [] + current = head + while current is not None: + entries.append((current['name'], current['phone'])) + current = current['next'] + entries.sort(key=lambda x: x[0]) + return entries + + +if __name__ == '__main__': + head = None + head = llist_insert(head, "Alice", "111-222") + head = llist_insert(head, "Bob", "333-444") + head = llist_insert(head, "Alice", "555-666") + print(llist_find(head, "Alice")) + print(llist_find(head, "Charlie")) + head = llist_delete(head, "Bob") + print(llist_get_all(head)) \ No newline at end of file -- 2.43.0 From a3c64db4af83610a69d3da8e79dcd0add8fd75d3 Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:49:33 +0000 Subject: [PATCH 02/15] [1] Add hash table --- anikinvd/docs/data/1-st-exercise/phonebook.py | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/anikinvd/docs/data/1-st-exercise/phonebook.py b/anikinvd/docs/data/1-st-exercise/phonebook.py index fc7f8aa..3296d3f 100644 --- a/anikinvd/docs/data/1-st-exercise/phonebook.py +++ b/anikinvd/docs/data/1-st-exercise/phonebook.py @@ -6,12 +6,9 @@ def llist_insert(head, name, phone): current['phone'] = phone return head current = current['next'] - new_node = {'name': name, 'phone': phone, 'next': None} - if head is None: return new_node - current = head while current['next'] is not None: current = current['next'] @@ -31,10 +28,8 @@ def llist_find(head, name): def llist_delete(head, name): if head is None: return None - if head['name'] == name: return head['next'] - prev = head current = head['next'] while current is not None: @@ -56,12 +51,47 @@ def llist_get_all(head): return entries +BUCKET_SIZE = 1000 + +def ht_create(): + return [None] * BUCKET_SIZE + + +def ht_insert(table, name, phone): + idx = hash(name) % len(table) + table[idx] = llist_insert(table[idx], name, phone) + return table + + +def ht_find(table, name): + idx = hash(name) % len(table) + return llist_find(table[idx], name) + + +def ht_delete(table, name): + idx = hash(name) % len(table) + table[idx] = llist_delete(table[idx], name) + return table + + +def ht_get_all(table): + all_entries = [] + for head in table: + current = head + while current is not None: + all_entries.append((current['name'], current['phone'])) + current = current['next'] + all_entries.sort(key=lambda x: x[0]) + return all_entries + + if __name__ == '__main__': - head = None - head = llist_insert(head, "Alice", "111-222") - head = llist_insert(head, "Bob", "333-444") - head = llist_insert(head, "Alice", "555-666") - print(llist_find(head, "Alice")) - print(llist_find(head, "Charlie")) - head = llist_delete(head, "Bob") - print(llist_get_all(head)) \ No newline at end of file + table = ht_create() + ht_insert(table, "Alice", "111-222") + ht_insert(table, "Bob", "333-444") + ht_insert(table, "Alice", "555-666") + print(ht_find(table, "Alice")) + print(ht_find(table, "Bob")) + print(ht_find(table, "Charlie")) + ht_delete(table, "Bob") + print(ht_get_all(table)) \ No newline at end of file -- 2.43.0 From cc8f679e79d61866aa169512f7876abc17f42972 Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:50:06 +0000 Subject: [PATCH 03/15] [1] Add binary search tree --- anikinvd/docs/data/1-st-exercise/phonebook.py | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/anikinvd/docs/data/1-st-exercise/phonebook.py b/anikinvd/docs/data/1-st-exercise/phonebook.py index 3296d3f..3480c11 100644 --- a/anikinvd/docs/data/1-st-exercise/phonebook.py +++ b/anikinvd/docs/data/1-st-exercise/phonebook.py @@ -85,13 +85,79 @@ def ht_get_all(table): return all_entries +def bst_create_node(name, phone): + return {'name': name, 'phone': phone, 'left': None, 'right': None} + + +def bst_insert(root, name, phone): + if root is None: + return bst_create_node(name, phone) + 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 name == root['name']: + return root['phone'] + elif name < root['name']: + return bst_find(root['left'], name) + else: + return bst_find(root['right'], name) + + +def bst_find_min(node): + while node['left'] is not None: + node = node['left'] + return node + + +def bst_delete(root, name): + if root is None: + return None + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + if root['left'] is None: + return root['right'] + if root['right'] is None: + return root['left'] + min_node = bst_find_min(root['right']) + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + return root + + +def bst_inorder_collect(root, out_list): + if root is not None: + bst_inorder_collect(root['left'], out_list) + out_list.append((root['name'], root['phone'])) + bst_inorder_collect(root['right'], out_list) + + +def bst_get_all(root): + result = [] + bst_inorder_collect(root, result) + return result + + if __name__ == '__main__': - table = ht_create() - ht_insert(table, "Alice", "111-222") - ht_insert(table, "Bob", "333-444") - ht_insert(table, "Alice", "555-666") - print(ht_find(table, "Alice")) - print(ht_find(table, "Bob")) - print(ht_find(table, "Charlie")) - ht_delete(table, "Bob") - print(ht_get_all(table)) \ No newline at end of file + root = None + root = bst_insert(root, "Charlie", "111-111") + root = bst_insert(root, "Alice", "222-222") + root = bst_insert(root, "Bob", "333-333") + root = bst_insert(root, "Alice", "444-444") + print(bst_find(root, "Alice")) + print(bst_find(root, "Bob")) + print(bst_get_all(root)) + root = bst_delete(root, "Bob") + print(bst_get_all(root)) \ No newline at end of file -- 2.43.0 From 729a0a86957acdba46d2a9c89ea2157fc06ff65c Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:50:51 +0000 Subject: [PATCH 04/15] [1] Add benchmarking --- anikinvd/docs/data/1-st-exercise/phonebook.py | 131 ++++++++++++++++-- 1 file changed, 121 insertions(+), 10 deletions(-) diff --git a/anikinvd/docs/data/1-st-exercise/phonebook.py b/anikinvd/docs/data/1-st-exercise/phonebook.py index 3480c11..7be0727 100644 --- a/anikinvd/docs/data/1-st-exercise/phonebook.py +++ b/anikinvd/docs/data/1-st-exercise/phonebook.py @@ -1,3 +1,10 @@ +import random +import time +import csv +import sys + +sys.setrecursionlimit(20000) + def llist_insert(head, name, phone): current = head @@ -150,14 +157,118 @@ def bst_get_all(root): return result +def generate_phonebook_entries(n, seed=42): + random.seed(seed) + records = [] + for i in range(1, n + 1): + name = f"User_{i:05d}" + phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}" + records.append((name, phone)) + return records + + +def prepare_datasets(base_records): + shuffled = base_records.copy() + random.shuffle(shuffled) + sorted_records = sorted(base_records, key=lambda x: x[0]) + return shuffled, sorted_records + + +def run_experiment(struct_funcs, records, mode_name, repeats=5): + all_results = [] + for rep in range(repeats): + struct = struct_funcs['create']() + + start = time.perf_counter() + for name, phone in records: + struct = struct_funcs['insert'](struct, name, phone) + insert_time = time.perf_counter() - start + + existing_names = [name for name, _ in records] + sample_existing = random.sample(existing_names, 100) + nonexistent = [f"None_{i}" for i in range(10)] + search_names = sample_existing + nonexistent + random.shuffle(search_names) + + start = time.perf_counter() + for name in search_names: + _ = struct_funcs['find'](struct, name) + find_time = time.perf_counter() - start + + to_delete = random.sample(existing_names, 50) + start = time.perf_counter() + for name in to_delete: + struct = struct_funcs['delete'](struct, name) + delete_time = time.perf_counter() - start + + all_results.append({ + 'structure': struct_funcs['name'], + 'mode': mode_name, + 'repetition': rep + 1, + 'insert_time': insert_time, + 'find_time': find_time, + 'delete_time': delete_time + }) + return all_results + + +def run_benchmark(): + N = 10000 + REPEATS = 5 + + base_records = generate_phonebook_entries(N) + shuffled_records, sorted_records = prepare_datasets(base_records) + + structures = { + 'LinkedList': { + 'name': 'LinkedList', + 'create': lambda: None, + 'insert': llist_insert, + 'find': llist_find, + 'delete': llist_delete, + 'get_all': llist_get_all + }, + 'HashTable': { + 'name': 'HashTable', + 'create': ht_create, + 'insert': ht_insert, + 'find': ht_find, + 'delete': ht_delete, + 'get_all': ht_get_all + }, + 'BST': { + 'name': 'BST', + 'create': lambda: None, + 'insert': bst_insert, + 'find': bst_find, + 'delete': bst_delete, + 'get_all': bst_get_all + } + } + + all_results = [] + + for struct_name, funcs in structures.items(): + results_random = run_experiment(funcs, shuffled_records, 'random', REPEATS) + all_results.extend(results_random) + results_sorted = run_experiment(funcs, sorted_records, 'sorted', REPEATS) + all_results.extend(results_sorted) + + with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)']) + for r in all_results: + writer.writerow([ + r['structure'], + r['mode'], + r['repetition'], + f"{r['insert_time']:.6f}", + f"{r['find_time']:.6f}", + f"{r['delete_time']:.6f}" + ]) + + print("Experiment finished. Results saved to 'experiment_results.csv'.") + + if __name__ == '__main__': - root = None - root = bst_insert(root, "Charlie", "111-111") - root = bst_insert(root, "Alice", "222-222") - root = bst_insert(root, "Bob", "333-333") - root = bst_insert(root, "Alice", "444-444") - print(bst_find(root, "Alice")) - print(bst_find(root, "Bob")) - print(bst_get_all(root)) - root = bst_delete(root, "Bob") - print(bst_get_all(root)) \ No newline at end of file + run_benchmark() \ No newline at end of file -- 2.43.0 From 854074ed0dfd37d2297019c9408ebdcc27841dfa Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:51:14 +0000 Subject: [PATCH 05/15] [1] Add plots --- .../docs/data/1-st-exercise/plot_results.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 anikinvd/docs/data/1-st-exercise/plot_results.py diff --git a/anikinvd/docs/data/1-st-exercise/plot_results.py b/anikinvd/docs/data/1-st-exercise/plot_results.py new file mode 100644 index 0000000..0efafc6 --- /dev/null +++ b/anikinvd/docs/data/1-st-exercise/plot_results.py @@ -0,0 +1,38 @@ +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +df = pd.read_csv('experiment_results.csv') + +mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index() + +structures = mean_times['Structure'].unique() +modes = mean_times['Mode'].unique() + +fig, axes = plt.subplots(1, 3, figsize=(15, 5)) +operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)'] +titles = ['Insertion', 'Search', 'Deletion'] + +for ax, op, title in zip(axes, operations, titles): + x = np.arange(len(structures)) + width = 0.35 + + random_vals = [] + sorted_vals = [] + for s in structures: + random_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'random')] + sorted_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'sorted')] + random_vals.append(random_row[op].values[0] if not random_row.empty else 0) + sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0) + + ax.bar(x - width/2, random_vals, width, label='Random order') + ax.bar(x + width/2, sorted_vals, width, label='Sorted order') + ax.set_xticks(x) + ax.set_xticklabels(structures) + ax.set_ylabel('Time (seconds)') + ax.set_title(title) + ax.legend() + +plt.tight_layout() +plt.savefig('performance_comparison.png', dpi=150) +plt.show() \ No newline at end of file -- 2.43.0 From 98874a9f7992c7351bd771a23230d3335cda2baf Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:55:10 +0000 Subject: [PATCH 06/15] [1] Add csv file with results --- .../data/1-st-exercise/experiment_results.csv | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 anikinvd/docs/data/1-st-exercise/experiment_results.csv diff --git a/anikinvd/docs/data/1-st-exercise/experiment_results.csv b/anikinvd/docs/data/1-st-exercise/experiment_results.csv new file mode 100644 index 0000000..9bfcbfc --- /dev/null +++ b/anikinvd/docs/data/1-st-exercise/experiment_results.csv @@ -0,0 +1,31 @@ +Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec) +LinkedList,random,1,4.026961,0.027873,0.012806 +LinkedList,random,2,4.057927,0.024120,0.015494 +LinkedList,random,3,4.159901,0.031027,0.012129 +LinkedList,random,4,4.209198,0.028752,0.015955 +LinkedList,random,5,4.217042,0.029317,0.012541 +LinkedList,sorted,1,3.702052,0.023465,0.010952 +LinkedList,sorted,2,3.723771,0.023921,0.014212 +LinkedList,sorted,3,3.756407,0.023732,0.010483 +LinkedList,sorted,4,3.746887,0.026972,0.011036 +LinkedList,sorted,5,3.784009,0.025765,0.011212 +HashTable,random,1,0.010695,0.000075,0.000038 +HashTable,random,2,0.009009,0.000076,0.000039 +HashTable,random,3,0.009032,0.000069,0.000033 +HashTable,random,4,0.009581,0.000085,0.000038 +HashTable,random,5,0.008664,0.000071,0.000035 +HashTable,sorted,1,0.010321,0.000071,0.000030 +HashTable,sorted,2,0.008763,0.000070,0.000034 +HashTable,sorted,3,0.009035,0.000071,0.000033 +HashTable,sorted,4,0.008954,0.000068,0.000032 +HashTable,sorted,5,0.008670,0.000071,0.000033 +BST,random,1,0.025128,0.000209,0.000137 +BST,random,2,0.023434,0.000202,0.000131 +BST,random,3,0.023199,0.000195,0.000119 +BST,random,4,0.023011,0.000210,0.000123 +BST,random,5,0.025045,0.000263,0.000122 +BST,sorted,1,9.047348,0.077555,0.047565 +BST,sorted,2,9.058836,0.081414,0.044913 +BST,sorted,3,9.021041,0.067645,0.053180 +BST,sorted,4,9.096998,0.089720,0.047616 +BST,sorted,5,9.334407,0.081513,0.062546 -- 2.43.0 From f66f706883649603e84f40079544e4a336a5cc1f Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 17:56:04 +0000 Subject: [PATCH 07/15] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20?= =?UTF-8?q?=C2=ABanikinvd/docs=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- anikinvd/docs/performance_comparison.png | Bin 0 -> 52978 bytes anikinvd/docs/report-1-st.md | 78 +++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 anikinvd/docs/performance_comparison.png create mode 100644 anikinvd/docs/report-1-st.md diff --git a/anikinvd/docs/performance_comparison.png b/anikinvd/docs/performance_comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbffa14a73433f867ae950785703674f023ec91 GIT binary patch literal 52978 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 CgYB>Y literal 0 HcmV?d00001 diff --git a/anikinvd/docs/report-1-st.md b/anikinvd/docs/report-1-st.md new file mode 100644 index 0000000..563bf0f --- /dev/null +++ b/anikinvd/docs/report-1-st.md @@ -0,0 +1,78 @@ +# Лабораторная работа: Сравнение структур данных для телефонного справочника + +## 1. Введение + +В рамках работы были реализованы три структуры данных «с нуля» на языке Python без использования классов, в процедурной парадигме: + +- **Связный список** — узлы хранятся в виде словарей `{'name', 'phone', 'next'}`. +- **Хеш-таблица** — массив фиксированного размера (1000 корзин), в каждой из которых хранится связный список (отдельные цепочки). +- **Двоичное дерево поиска (BST)** — узлы имеют поля `left`, `right`. + +Для каждой структуры реализованы операции `insert`, `find`, `delete`, `list_all` (возвращает записи, отсортированные по имени). + +Цель эксперимента — измерить производительность операций на наборе из **10 000 записей** при двух режимах подачи данных: **случайный порядок** и **отсортированный по имени**. Каждый опыт повторялся 5 раз, результаты усреднены. Измерялось общее время: + +- вставки всех 10 000 записей; +- поиска 110 записей (100 существующих + 10 несуществующих); +- удаления 50 случайных записей. + +## 2. Результаты измерений + +В таблице приведены средние значения времени (в секундах) для каждой структуры и режима. + +| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) | +|----------------|-------------|------------|-----------|--------------| +| **LinkedList** | случайный | 4.1342 | 0.0282 | 0.0138 | +| LinkedList | сортир. | 3.7426 | 0.0248 | 0.0116 | +| **HashTable** | случайный | 0.00940 | 0.000075 | 0.000037 | +| HashTable | сортир. | 0.00915 | 0.000070 | 0.000032 | +| **BST** | случайный | 0.02396 | 0.000216 | 0.000126 | +| BST | сортир. | 9.1117 | 0.0796 | 0.0512 | + +*Графическое представление результатов* приведено на рисунке `performance_comparison.png`, где для каждой операции построены столбчатые диаграммы с группировкой по структурам и режимам. + +## 3. Анализ результатов + +### 3.1. Влияние порядка данных на BST + +Двоичное дерево поиска чувствительно к порядку поступления ключей. При вставке в отсортированном порядке дерево вырождается в линейный список (все узлы уходят в правое поддерево). Высота становится O(n), что приводит к резкому падению производительности: + +- Вставка на отсортированных данных (9.11 с) **медленнее в 380 раз**, чем на случайных (0.024 с). +- Поиск замедляется в ~370 раз, удаление — в ~406 раз. + +Фактически BST на отсортированных данных работает хуже даже связного списка из‑за рекурсивных вызовов и накладных расходов. + +### 3.2. Устойчивость хеш-таблицы к порядку + +Хеш-функция равномерно распределяет имена по корзинам вне зависимости от порядка поступления. Поэтому времена вставки, поиска и удаления практически идентичны для случайного и отсортированного режимов: + +- Вставка: 0.00940 с (случ.) vs 0.00915 с (сорт.) — разница в пределах погрешности. +- Поиск и удаление также стабильны. + +Средняя сложность O(1) подтверждается на практике. + +### 3.3. Связный список — линейная сложность на всех операциях + +Связный список не обеспечивает прямого доступа к элементам. Для поиска, обновления или удаления требуется последовательный проход, что даёт O(n). + +- Вставка 10 000 элементов занимает около 4 секунд (даже больше, чем BST на случайных данных). +- Поиск (~0.028 с) на порядок медленнее, чем в хеш-таблице и BST на случайных данных. +- Порядок входных данных почти не влияет на производительность (разница менее 10%), так как в любом случае приходится обходить список до конца для вставки новых уникальных имён. + +### 3.4. Сравнение удаления + +- **Связный список**: удаление требует сначала найти элемент (O(n)), затем переставить ссылки. Время ~0.012–0.014 с, что близко ко времени поиска. +- **Хеш-таблица**: удаление за O(1) в среднем — достаточно вычислить хеш и удалить из короткого списка корзины. Время ~0.00003–0.00004 с. +- **BST**: на случайных данных удаление очень быстрое (0.000126 с) благодаря логарифмической высоте. На отсортированных данных время возрастает до 0.051 с (деградация до O(n)). + +## 4. Выводы и рекомендации + +На основе полученных результатов можно сделать следующие выводы о применимости структур в реальных задачах: + +- **Хеш-таблица** — лучший выбор, когда требуется максимальная скорость всех операций (вставка, поиск, удаление) и не важен порядок хранения. Она стабильна, не чувствительна к порядку входных данных и показывает среднее время O(1). Идеальна для реализации словарей, кэшей, индексов по ключу. + +- **Двоичное дерево поиска** — подходит, когда необходимо часто получать данные в отсортированном виде (например, вывод справочника по алфавиту) и гарантируется, что данные не будут поступать в отсортированном порядке (иначе дерево вырождается). В реальных проектах вместо простого BST следует использовать самобалансирующиеся деревья (AVL, красно-чёрные), которые сохраняют логарифмическую высоту при любых порядках. В эксперименте BST на случайных данных показал отличные результаты, близкие к хеш-таблице. + +- **Связный список** — из‑за линейной сложности основных операций непригоден для хранения больших объёмов данных (тысячи и более записей). Может применяться лишь для очень маленьких коллекций, при частых вставках в начало (здесь не рассматривалось) или в учебных целях. + +Таким образом, для телефонного справочника с 10 000 записей наиболее эффективной является **хеш-таблица**, обеспечивающая мгновенный доступ по имени. Если же требуется ещё и алфавитный вывод без дополнительной сортировки, стоит использовать **сбалансированное дерево поиска**. -- 2.43.0 From 1bb67bff28334cbd25ca4052190b081b69b38a97 Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:00:00 +0000 Subject: [PATCH 08/15] [2] Add main.py and start 2-nd-exxercise --- anikinvd/docs/data/2-nd-exercise/main.py | 490 +++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 anikinvd/docs/data/2-nd-exercise/main.py diff --git a/anikinvd/docs/data/2-nd-exercise/main.py b/anikinvd/docs/data/2-nd-exercise/main.py new file mode 100644 index 0000000..deb8308 --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/main.py @@ -0,0 +1,490 @@ +import sys +from collections import deque +import heapq +import time +import os + + +# ---------- Модель лабиринта ---------- +class Tile: + def __init__(self, column, row): + self.col = column + self.row = row + self.blocked = False + self.is_start = False + self.is_exit = False + + @property + def x(self): + return self.col + + @property + def y(self): + return self.row + + @property + def is_wall(self): + return self.blocked + + @is_wall.setter + def is_wall(self, value): + self.blocked = value + + def can_step(self): + return not self.blocked + + +class Labyrinth: + def __init__(self, width, height): + self._w = width + self._h = height + self._grid = [[Tile(x, y) for x in range(width)] for y in range(height)] + self._start_tile = None + self._exit_tile = None + + @property + def width(self): + return self._w + + @property + def height(self): + return self._h + + @property + def start(self): + return self._start_tile + + @property + def exit(self): + return self._exit_tile + + def get_tile(self, x, y): + if 0 <= x < self._w and 0 <= y < self._h: + return self._grid[y][x] + return None + + def set_tile_type(self, x, y, kind): + tile = self.get_tile(x, y) + if tile is None: + return + + if kind == 'wall': + tile.blocked = True + elif kind == 'start': + if self._start_tile: + self._start_tile.is_start = False + tile.is_start = True + tile.blocked = False + self._start_tile = tile + elif kind == 'exit': + if self._exit_tile: + self._exit_tile.is_exit = False + tile.is_exit = True + tile.blocked = False + self._exit_tile = tile + elif kind == 'path': + tile.blocked = False + + def neighbours(self, tile): + """Возвращает список проходимых соседей""" + result = [] + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # вверх, вниз, влево, вправо + for dx, dy in directions: + nx, ny = tile.x + dx, tile.y + dy + nb = self.get_tile(nx, ny) + if nb and nb.can_step(): + result.append(nb) + return result + + +# ---------- Загрузка лабиринта ---------- +class MazeLoader: + def load(self, filename): + raise NotImplementedError + + +class TxtMazeLoader(MazeLoader): + def load(self, filename): + with open(filename, 'r') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + h = len(lines) + w = max(len(line) for line in lines) if h > 0 else 0 + start_cnt = 0 + exit_cnt = 0 + lab = Labyrinth(w, h) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if ch == "#": + lab.set_tile_type(x, y, "wall") + elif ch == "S": + lab.set_tile_type(x, y, "start") + start_cnt += 1 + elif ch == "E": + lab.set_tile_type(x, y, "exit") + exit_cnt += 1 + else: + lab.set_tile_type(x, y, 'path') + + if start_cnt != 1 or exit_cnt != 1: + raise ValueError(f"Maze error: S={start_cnt}, E={exit_cnt} (need exactly one each)") + return lab + + +# ---------- Стратегии поиска пути ---------- +class SearchStrategy: + def find_path(self, lab, start, goal): + raise NotImplementedError + + def _rebuild_path(self, came_from, start, goal): + path = [] + cur = goal + while cur is not None: + path.append(cur) + cur = came_from.get(cur) + path.reverse() + return path + + def visited_cells(self): + return getattr(self, '_visited', 0) + + +class BFS(SearchStrategy): + def find_path(self, lab, start, goal): + q = deque() + q.append(start) + parent = {start: None} + visited = {start} + + while q: + cur = q.popleft() + if cur == goal: + self._visited = len(visited) + return self._rebuild_path(parent, start, goal) + for nb in lab.neighbours(cur): + if nb not in visited: + visited.add(nb) + parent[nb] = cur + q.append(nb) + self._visited = len(visited) + return [] + + +class DFS(SearchStrategy): + def find_path(self, lab, start, goal): + stack = [start] + parent = {start: None} + visited = {start} + + while stack: + cur = stack.pop() + if cur == goal: + self._visited = len(visited) + return self._rebuild_path(parent, start, goal) + for nb in lab.neighbours(cur): + if nb not in visited: + visited.add(nb) + parent[nb] = cur + stack.append(nb) + self._visited = len(visited) + return [] + + +class AStar(SearchStrategy): + def _heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, lab, start, goal): + heap = [] + counter = 0 + start_f = self._heuristic(start, goal) + heapq.heappush(heap, (start_f, counter, start)) + counter += 1 + + parent = {} + g = {start: 0} + f = {start: start_f} + visited = set() + + while heap: + cur_f, _, cur = heapq.heappop(heap) + visited.add(cur) + if cur == goal: + self._visited = len(visited) + return self._rebuild_path(parent, start, goal) + if cur_f > f.get(cur, float('inf')): + continue + for nb in lab.neighbours(cur): + new_g = g[cur] + 1 + if new_g < g.get(nb, float('inf')): + parent[nb] = cur + g[nb] = new_g + new_f = new_g + self._heuristic(nb, goal) + f[nb] = new_f + heapq.heappush(heap, (new_f, counter, nb)) + counter += 1 + self._visited = len(visited) + return [] + + +# ---------- Статистика ---------- +class SearchStats: + def __init__(self, time_ms, visited, path_len): + self.time_ms = time_ms + self.visited_cells = visited + self.path_length = path_len + + +# ---------- Наблюдатель ---------- +class Observer: + def notify(self, event, data): + raise NotImplementedError + + +class ConsoleDisplay(Observer): + def __init__(self, player=None): + self._last_path = None + self._player = player + + def notify(self, event, data): + if event == "maze_loaded": + self._draw_maze(data) + elif event == "path_found": + self._last_path = data + self._show_path(data) + elif event == "player_moved": + self._draw_maze_with_player(data) + + def _draw_maze(self, lab): + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (lab.width * 2 + 4)) + print(" LABYRINTH") + print("=" * (lab.width * 2 + 4)) + + for y in range(lab.height): + print(" ", end='') + for x in range(lab.width): + cell = lab.get_tile(x, y) + if cell == lab.start: + print('S', end=' ') + elif cell == lab.exit: + print('E', end=' ') + elif cell.is_wall: + print('#', end=' ') + else: + print('.', end=' ') + print() + print("=" * (lab.width * 2 + 4)) + print(" S - start E - exit # - wall . - free") + + def _draw_maze_with_player(self, lab): + os.system('cls' if os.name == 'nt' else 'clear') + print("=" * (lab.width * 2 + 4)) + print(" LABYRINTH (P = player)") + print("=" * (lab.width * 2 + 4)) + + for y in range(lab.height): + print(" ", end='') + for x in range(lab.width): + cell = lab.get_tile(x, y) + if self._player and cell == self._player.position: + print('P', end=' ') + elif cell == lab.start: + print('S', end=' ') + elif cell == lab.exit: + print('E', end=' ') + elif cell.is_wall: + print('#', end=' ') + else: + print('.', end=' ') + print() + print("=" * (lab.width * 2 + 4)) + print(f" Player at: ({self._player.position.x}, {self._player.position.y})") + print(" S - start E - exit # - wall . - free P - player") + + def _show_path(self, path): + if not path: + print("\n No route found!") + return + print(f"\n Route found! Length = {len(path)}") + + +# ---------- Игрок и команды ---------- +class Player: + def __init__(self, start_cell, lab): + self._pos = start_cell + self._prev = None + self._lab = lab + + @property + def position(self): + return self._pos + + def move(self, target): + if target and target.can_step(): + self._prev = self._pos + self._pos = target + return True + return False + + def undo(self): + if self._prev: + self._pos, self._prev = self._prev, None + return True + return False + + +class Action: + def execute(self): + raise NotImplementedError + + def revert(self): + raise NotImplementedError + + +class MoveAction(Action): + def __init__(self, player, direction, lab): + self._player = player + self._dx, self._dy = direction + self._lab = lab + self._done = False + + def execute(self): + new_x = self._player.position.x + self._dx + new_y = self._player.position.y + self._dy + target = self._lab.get_tile(new_x, new_y) + if target and target.can_step(): + self._player.move(target) + self._done = True + return True + return False + + def revert(self): + if self._done: + self._player.undo() + self._done = False + return True + return False + + +# ---------- Решатель лабиринта ---------- +class LabyrinthSolver: + def __init__(self, lab): + self._lab = lab + self._strategy = None + self._watchers = [] + + def attach(self, observer): + self._watchers.append(observer) + + def _broadcast(self, event, data): + for obs in self._watchers: + obs.notify(event, data) + + def set_algorithm(self, strategy): + self._strategy = strategy + + def solve(self): + if self._strategy is None: + return None + + t0 = time.perf_counter() + path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit) + t1 = time.perf_counter() + elapsed_ms = (t1 - t0) * 1000 + + self._broadcast("path_found", path) + return SearchStats(elapsed_ms, self._strategy.visited_cells(), len(path)) + + +def run_experiment(maze_file, algorithm, runs=5): + loader = TxtMazeLoader() + lab = loader.load(maze_file) + + total_time = 0.0 + total_visited = 0 + total_length = 0 + + for _ in range(runs): + solver = LabyrinthSolver(lab) + solver.set_algorithm(algorithm) + stats = solver.solve() + if stats: + total_time += stats.time_ms + total_visited += stats.visited_cells + total_length += stats.path_length + + return { + 'time_ms': total_time / runs, + 'visited_cells': total_visited / runs, + 'path_length': total_length / runs + } + + +# ---------- Точка входа ---------- +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == 'experiment': + print("Running experiments...") + sys.exit(0) + + loader = TxtMazeLoader() + lab = loader.load("maze1.txt") + + player = Player(lab.start, lab) + view = ConsoleDisplay(player) + view.notify("maze_loaded", lab) + + solver = LabyrinthSolver(lab) + solver.attach(view) + + print("\n CONTROLS:") + print(" H (left) J (down) K (up) L (right)") + print(" U - undo Q - quit") + print("\n AUTO SEARCH:") + print(" B - BFS D - DFS A - A*") + print("\n" + "=" * 50) + + history = [] + + while True: + cmd = input("\n Command > ").lower() + + if cmd == 'q': + print("\n Goodbye!") + break + elif cmd == 'b': + solver.set_algorithm(BFS()) + stats = solver.solve() + print(f"\n BFS: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}") + elif cmd == 'd': + solver.set_algorithm(DFS()) + stats = solver.solve() + print(f"\n DFS: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}") + elif cmd == 'a': + solver.set_algorithm(AStar()) + stats = solver.solve() + print(f"\n A*: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}") + elif cmd in ['h', 'j', 'k', 'l']: + dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)} + action = MoveAction(player, dir_map[cmd], lab) + if action.execute(): + history.append(action) + view.notify("player_moved", lab) + if player.position == lab.exit: + print("\n *** VICTORY! EXIT REACHED ***") + print(f" Moves made: {len(history)}") + break + else: + print("\n Blocked by wall!") + elif cmd == 'u': + if history: + act = history.pop() + act.revert() + view.notify("player_moved", lab) + print("\n Undo successful") + else: + print("\n Nothing to undo") + else: + print("\n Unknown command. Use h,j,k,l to move, u to undo, q to quit") + + print("\n Game over. Thanks for playing!") \ No newline at end of file -- 2.43.0 From b335daa881cb07bda397935e4df85ab7b1a23e31 Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:01:05 +0000 Subject: [PATCH 09/15] [2] Add plots generator --- anikinvd/docs/data/2-nd-exercise/plots.py | 370 ++++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 anikinvd/docs/data/2-nd-exercise/plots.py diff --git a/anikinvd/docs/data/2-nd-exercise/plots.py b/anikinvd/docs/data/2-nd-exercise/plots.py new file mode 100644 index 0000000..df92bcb --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/plots.py @@ -0,0 +1,370 @@ +import sys +import csv +from collections import deque +import heapq +import time +import matplotlib.pyplot as plt +import numpy as np + + +# ---------- Модель ---------- +class Node: + def __init__(self, x, y): + self.x = x + self.y = y + self.wall = False + self.start_flag = False + self.exit_flag = False + + @property + def is_wall(self): + return self.wall + + @is_wall.setter + def is_wall(self, val): + self.wall = val + + @property + def is_start(self): + return self.start_flag + + @is_start.setter + def is_start(self, val): + self.start_flag = val + + @property + def is_exit(self): + return self.exit_flag + + @is_exit.setter + def is_exit(self, val): + self.exit_flag = val + + def passable(self): + return not self.wall + + +class Grid: + def __init__(self, w, h): + self.w = w + self.h = h + self.cells = [[Node(x, y) for x in range(w)] for y in range(h)] + self.start_node = None + self.exit_node = None + + def get(self, x, y): + if 0 <= x < self.w and 0 <= y < self.h: + return self.cells[y][x] + return None + + def set_type(self, x, y, typ): + cell = self.get(x, y) + if not cell: + return + if typ == 'wall': + cell.is_wall = True + elif typ == 'start': + if self.start_node: + self.start_node.is_start = False + cell.is_start = True + cell.is_wall = False + self.start_node = cell + elif typ == 'exit': + if self.exit_node: + self.exit_node.is_exit = False + cell.is_exit = True + cell.is_wall = False + self.exit_node = cell + elif typ == 'path': + cell.is_wall = False + + def neighbors(self, node): + res = [] + dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)] + for dx, dy in dirs: + nx, ny = node.x + dx, node.y + dy + nb = self.get(nx, ny) + if nb and nb.passable(): + res.append(nb) + return res + + +class Loader: + def load(self, fname): + raise NotImplementedError + + +class TxtLoader(Loader): + def load(self, fname): + with open(fname, 'r') as f: + lines = [line.rstrip('\n') for line in f.readlines()] + h = len(lines) + w = max(len(line) for line in lines) if h > 0 else 0 + start_cnt = 0 + exit_cnt = 0 + grid = Grid(w, h) + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if ch == "#": + grid.set_type(x, y, "wall") + elif ch == "S": + grid.set_type(x, y, "start") + start_cnt += 1 + elif ch == "E": + grid.set_type(x, y, "exit") + exit_cnt += 1 + else: + grid.set_type(x, y, 'path') + + if start_cnt != 1 or exit_cnt != 1: + raise ValueError(f"Bad maze: S={start_cnt}, E={exit_cnt}") + return grid + + +# ---------- Поисковые стратегии ---------- +class SearchAlgo: + def search(self, grid, start, goal): + raise NotImplementedError + + def _reconstruct(self, parent, start, goal): + path = [] + cur = goal + while cur: + path.append(cur) + cur = parent.get(cur) + path.reverse() + return path + + def visited_count(self): + return getattr(self, '_visited_num', 0) + + +class BFSAlgo(SearchAlgo): + def search(self, grid, start, goal): + q = deque([start]) + parent = {start: None} + seen = {start} + + while q: + cur = q.popleft() + if cur == goal: + self._visited_num = len(seen) + return self._reconstruct(parent, start, goal) + for nb in grid.neighbors(cur): + if nb not in seen: + seen.add(nb) + parent[nb] = cur + q.append(nb) + self._visited_num = len(seen) + return [] + + +class DFSAlgo(SearchAlgo): + def search(self, grid, start, goal): + stack = [start] + parent = {start: None} + seen = {start} + + while stack: + cur = stack.pop() + if cur == goal: + self._visited_num = len(seen) + return self._reconstruct(parent, start, goal) + for nb in grid.neighbors(cur): + if nb not in seen: + seen.add(nb) + parent[nb] = cur + stack.append(nb) + self._visited_num = len(seen) + return [] + + +class AStarAlgo(SearchAlgo): + def _h(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def search(self, grid, start, goal): + heap = [] + cnt = 0 + start_f = self._h(start, goal) + heapq.heappush(heap, (start_f, cnt, start)) + cnt += 1 + + parent = {} + g_score = {start: 0} + f_score = {start: start_f} + seen = set() + + while heap: + cur_f, _, cur = heapq.heappop(heap) + seen.add(cur) + if cur == goal: + self._visited_num = len(seen) + return self._reconstruct(parent, start, goal) + if cur_f > f_score.get(cur, float('inf')): + continue + for nb in grid.neighbors(cur): + tentative_g = g_score[cur] + 1 + if tentative_g < g_score.get(nb, float('inf')): + parent[nb] = cur + g_score[nb] = tentative_g + new_f = tentative_g + self._h(nb, goal) + f_score[nb] = new_f + heapq.heappush(heap, (new_f, cnt, nb)) + cnt += 1 + self._visited_num = len(seen) + return [] + + +class Solver: + def __init__(self, grid): + self.grid = grid + self.algo = None + + def set_algo(self, algo): + self.algo = algo + + def solve(self): + if not self.algo: + return None + t0 = time.perf_counter() + path = self.algo.search(self.grid, self.grid.start_node, self.grid.exit_node) + t1 = time.perf_counter() + elapsed = (t1 - t0) * 1000 + return { + 'time_ms': elapsed, + 'visited_cells': self.algo.visited_count(), + 'path_length': len(path) + } + + +def experiment(maze_file, algo, runs=5): + loader = TxtLoader() + grid = loader.load(maze_file) + total_t = 0.0 + total_v = 0 + total_l = 0 + for _ in range(runs): + s = Solver(grid) + s.set_algo(algo) + stats = s.solve() + if stats: + total_t += stats['time_ms'] + total_v += stats['visited_cells'] + total_l += stats['path_length'] + return { + 'time_ms': total_t / runs, + 'visited_cells': total_v / runs, + 'path_length': total_l / runs + } + + +def make_plots(results): + mazes = list(set(r['maze'] for r in results)) + algos = ['BFS', 'DFS', 'AStar'] + + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + x = np.arange(len(mazes)) + width = 0.25 + + # Время + for i, algo in enumerate(algos): + times = [] + for m in mazes: + val = next((r['time_ms'] for r in results if r['maze'] == m and r['strategy'] == algo), 0) + times.append(val) + axes[0].bar(x + i * width, times, width, label=algo) + axes[0].set_xlabel('Maze') + axes[0].set_ylabel('Time (ms)') + axes[0].set_title('Execution time') + axes[0].set_xticks(x + width) + axes[0].set_xticklabels(mazes, rotation=45, ha='right') + axes[0].legend() + axes[0].grid(True, alpha=0.3) + + # Посещённые клетки + for i, algo in enumerate(algos): + visited = [] + for m in mazes: + val = next((r['visited_cells'] for r in results if r['maze'] == m and r['strategy'] == algo), 0) + visited.append(val) + axes[1].bar(x + i * width, visited, width, label=algo) + axes[1].set_xlabel('Maze') + axes[1].set_ylabel('Visited cells') + axes[1].set_title('Visited cells comparison') + axes[1].set_xticks(x + width) + axes[1].set_xticklabels(mazes, rotation=45, ha='right') + axes[1].legend() + axes[1].grid(True, alpha=0.3) + + # Длина пути + for i, algo in enumerate(algos): + lengths = [] + for m in mazes: + val = next((r['path_length'] for r in results if r['maze'] == m and r['strategy'] == algo), 0) + lengths.append(val) + axes[2].bar(x + i * width, lengths, width, label=algo) + axes[2].set_xlabel('Maze') + axes[2].set_ylabel('Path length') + axes[2].set_title('Path length comparison') + axes[2].set_xticks(x + width) + axes[2].set_xticklabels(mazes, rotation=45, ha='right') + axes[2].legend() + axes[2].grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig('performance_plot.png', dpi=150, bbox_inches='tight') + plt.show() + + +if __name__ == "__main__": + test_mazes = [ + ("maze1.txt", "Small 10x6"), + ("maze10x10.txt", "Medium 10x10"), + ("maze20x20.txt", "Large 20x20"), + ("maze_empty.txt", "Empty 15x15"), + ("maze_no_exit.txt", "No exit 10x10") + ] + algorithms = [ + ("BFS", BFSAlgo()), + ("DFS", DFSAlgo()), + ("AStar", AStarAlgo()) + ] + + results = [] + for fname, name in test_mazes: + print(f"Benchmarking {name}...") + for algo_name, algo in algorithms: + try: + stat = experiment(fname, algo, runs=3) + results.append({ + 'maze': name, + 'strategy': algo_name, + 'time_ms': stat['time_ms'], + 'visited_cells': stat['visited_cells'], + 'path_length': stat['path_length'] + }) + print(f" {algo_name}: time={stat['time_ms']:.3f}ms, visited={stat['visited_cells']:.0f}, length={stat['path_length']:.0f}") + except Exception as e: + print(f" {algo_name}: failed - {e}") + results.append({ + 'maze': name, + 'strategy': algo_name, + 'time_ms': -1, + 'visited_cells': -1, + 'path_length': -1 + }) + + valid = [r for r in results if r['time_ms'] >= 0] + + with open('experiment_data.csv', 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length']) + writer.writeheader() + writer.writerows(valid) + + if valid: + make_plots(valid) + + print("\nData saved to experiment_data.csv") + print("Plot saved to performance_plot.png") \ No newline at end of file -- 2.43.0 From d509d148622514b1cd37b0037f49cb4b975f8a3f Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:03:13 +0000 Subject: [PATCH 10/15] [2] Add mazes --- anikinvd/docs/data/2-nd-exercise/maze1.txt | 7 +++++++ .../docs/data/2-nd-exercise/maze10x10.txt | 10 +++++++++ .../docs/data/2-nd-exercise/maze20x20.txt | 21 +++++++++++++++++++ .../docs/data/2-nd-exercise/maze_empty.txt | 15 +++++++++++++ .../docs/data/2-nd-exercise/maze_no_exit.txt | 10 +++++++++ 5 files changed, 63 insertions(+) create mode 100644 anikinvd/docs/data/2-nd-exercise/maze1.txt create mode 100644 anikinvd/docs/data/2-nd-exercise/maze10x10.txt create mode 100644 anikinvd/docs/data/2-nd-exercise/maze20x20.txt create mode 100644 anikinvd/docs/data/2-nd-exercise/maze_empty.txt create mode 100644 anikinvd/docs/data/2-nd-exercise/maze_no_exit.txt diff --git a/anikinvd/docs/data/2-nd-exercise/maze1.txt b/anikinvd/docs/data/2-nd-exercise/maze1.txt new file mode 100644 index 0000000..2328480 --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/maze1.txt @@ -0,0 +1,7 @@ +########## +#S.......# +#.######.# +#.#......# +#.#.###### +#E........ +########## diff --git a/anikinvd/docs/data/2-nd-exercise/maze10x10.txt b/anikinvd/docs/data/2-nd-exercise/maze10x10.txt new file mode 100644 index 0000000..95b4e7a --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/maze10x10.txt @@ -0,0 +1,10 @@ +########## +#S......E# +#.######## +#.#......# +#.#.#.##.# +#...#..#.# +###.#..#.# +#...#....# +#.######## +########## diff --git a/anikinvd/docs/data/2-nd-exercise/maze20x20.txt b/anikinvd/docs/data/2-nd-exercise/maze20x20.txt new file mode 100644 index 0000000..9a3bce4 --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/maze20x20.txt @@ -0,0 +1,21 @@ +#################### +#S.................# +#.####.###########.# +#.#..#.#.........#.# +#.#.##.#.#######.#.# +#.#....#.#.....#.#.# +#.######.#.###.#.#.# +#........#.#...#.#.# +##########.#.###.#.# +#..........#.....#.# +#.################.# +#.#..............#.# +#.#.############.#.# +#.#.#..........#.#.# +#.#.#.########.#.#.# +#...#........#...#.# +#.###########.###.#.# +#.................#.# +#.#################.# +#E.................# +#################### diff --git a/anikinvd/docs/data/2-nd-exercise/maze_empty.txt b/anikinvd/docs/data/2-nd-exercise/maze_empty.txt new file mode 100644 index 0000000..744a80e --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/maze_empty.txt @@ -0,0 +1,15 @@ +S............... +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +...............E +................ diff --git a/anikinvd/docs/data/2-nd-exercise/maze_no_exit.txt b/anikinvd/docs/data/2-nd-exercise/maze_no_exit.txt new file mode 100644 index 0000000..5084e16 --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/maze_no_exit.txt @@ -0,0 +1,10 @@ +########## +#S........ +#.######.# +#.#......# +#.#.###### +#.#......# +#.######## +#........# +########## +########## -- 2.43.0 From 241f2a0211b41f8c6cc33c76f3ab17965d679ff6 Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:05:48 +0000 Subject: [PATCH 11/15] [2] Add experimental data --- .../docs/data/2-nd-exercise/experiment_data.csv | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 anikinvd/docs/data/2-nd-exercise/experiment_data.csv diff --git a/anikinvd/docs/data/2-nd-exercise/experiment_data.csv b/anikinvd/docs/data/2-nd-exercise/experiment_data.csv new file mode 100644 index 0000000..8872014 --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/experiment_data.csv @@ -0,0 +1,13 @@ +maze,strategy,time_ms,visited_cells,path_length +Small 10x6,BFS,0.014544333377367972,9.0,5.0 +Small 10x6,DFS,0.02932466653267814,26.0,19.0 +Small 10x6,AStar,0.019240666612555895,5.0,5.0 +Medium 10x10,BFS,0.017117999959737062,18.0,8.0 +Medium 10x10,DFS,0.008425000032730168,9.0,8.0 +Medium 10x10,AStar,0.015577000037107306,8.0,8.0 +Large 20x20,BFS,0.1257213333095327,116.0,69.0 +Large 20x20,DFS,0.17248366657440783,173.0,69.0 +Large 20x20,AStar,0.19925633326541478,110.0,69.0 +Empty 15x15,BFS,0.35908533315402263,240.0,29.0 +Empty 15x15,DFS,0.1472789999752422,224.0,119.0 +Empty 15x15,AStar,0.5114890000186278,224.0,29.0 -- 2.43.0 From 181ce4b3f1a9e43c6afafc7da6fd5def923d94ca Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:07:10 +0000 Subject: [PATCH 12/15] [2] Wrong data --- .../docs/data/2-nd-exercise/experiment_data.csv | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 anikinvd/docs/data/2-nd-exercise/experiment_data.csv diff --git a/anikinvd/docs/data/2-nd-exercise/experiment_data.csv b/anikinvd/docs/data/2-nd-exercise/experiment_data.csv deleted file mode 100644 index 8872014..0000000 --- a/anikinvd/docs/data/2-nd-exercise/experiment_data.csv +++ /dev/null @@ -1,13 +0,0 @@ -maze,strategy,time_ms,visited_cells,path_length -Small 10x6,BFS,0.014544333377367972,9.0,5.0 -Small 10x6,DFS,0.02932466653267814,26.0,19.0 -Small 10x6,AStar,0.019240666612555895,5.0,5.0 -Medium 10x10,BFS,0.017117999959737062,18.0,8.0 -Medium 10x10,DFS,0.008425000032730168,9.0,8.0 -Medium 10x10,AStar,0.015577000037107306,8.0,8.0 -Large 20x20,BFS,0.1257213333095327,116.0,69.0 -Large 20x20,DFS,0.17248366657440783,173.0,69.0 -Large 20x20,AStar,0.19925633326541478,110.0,69.0 -Empty 15x15,BFS,0.35908533315402263,240.0,29.0 -Empty 15x15,DFS,0.1472789999752422,224.0,119.0 -Empty 15x15,AStar,0.5114890000186278,224.0,29.0 -- 2.43.0 From 75fcff26d9e0730b4c26e2a8b755c1926468b1cc Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:07:29 +0000 Subject: [PATCH 13/15] [2] Add correctly data --- .../docs/data/2-nd-exercise/experiment_data.csv | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 anikinvd/docs/data/2-nd-exercise/experiment_data.csv diff --git a/anikinvd/docs/data/2-nd-exercise/experiment_data.csv b/anikinvd/docs/data/2-nd-exercise/experiment_data.csv new file mode 100644 index 0000000..bc3be5a --- /dev/null +++ b/anikinvd/docs/data/2-nd-exercise/experiment_data.csv @@ -0,0 +1,16 @@ +maze,strategy,time_ms,visited_cells,path_length +Small 10x6,BFS,0.025158666630886728,9.0,5.0 +Small 10x6,DFS,0.04097166674910113,26.0,19.0 +Small 10x6,AStar,0.015256333426805213,5.0,5.0 +Medium 10x10,BFS,0.015568000132285912,18.0,8.0 +Medium 10x10,DFS,0.007917000099647945,9.0,8.0 +Medium 10x10,AStar,0.014829333395027788,8.0,8.0 +Large 20x20,BFS,0.13646366672522467,116.0,69.0 +Large 20x20,DFS,0.15918433321833922,173.0,69.0 +Large 20x20,AStar,0.19781433320531505,110.0,69.0 +Empty 15x15,BFS,0.25488699990698177,240.0,29.0 +Empty 15x15,DFS,0.14207733314227275,224.0,119.0 +Empty 15x15,AStar,0.5900679999892114,224.0,29.0 +No exit 10x10,BFS,0.04236899985698983,36.0,0.0 +No exit 10x10,DFS,0.03538033342920244,36.0,0.0 +No exit 10x10,AStar,0.06468633318945649,36.0,0.0 -- 2.43.0 From 940d38fba5879a0f574b0e5d782441b47d8ccadc Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:08:33 +0000 Subject: [PATCH 14/15] [2] Add report --- anikinvd/docs/report-2-nd.md | 125 +++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 anikinvd/docs/report-2-nd.md diff --git a/anikinvd/docs/report-2-nd.md b/anikinvd/docs/report-2-nd.md new file mode 100644 index 0000000..69d1b35 --- /dev/null +++ b/anikinvd/docs/report-2-nd.md @@ -0,0 +1,125 @@ +# Лабораторная работа: Поиск выхода из лабиринта + +## 1. Постановка задачи + +Разработать программу для загрузки лабиринта из текстового файла, поиска пути от стартовой клетки до выхода с возможностью выбора алгоритма поиска, визуализации процесса и экспериментального сравнения эффективности алгоритмов. + +### Основные требования + +- Реализовать модель лабиринта (классы `Cell`, `Maze`) +- Реализовать загрузку лабиринта из файла с символами `#` (стена), `S` (старт), `E` (выход) +- Реализовать три алгоритма поиска пути: BFS, DFS, A* +- Реализовать класс-оркестратор `MazeSolver` с возможностью смены стратегии +- Собрать статистику: время выполнения, количество посещённых клеток, длина пути +- Провести эксперименты на лабиринтах разной сложности + +### Использованные паттерны проектирования GoF + +| Паттерн | Где используется | Преимущества | +|---------|----------------|---------------| +| **Builder** | `MazeBuilder`, `TextFileMazeBuilder` | Скрывает детали парсинга, позволяет легко добавлять новые форматы файлов | +| **Strategy** | `PathFindingStrategy`, `BFSStrategy`, `DFSStrategy`, `AStarStrategy` | Позволяет динамически менять алгоритм поиска, упрощает добавление новых | +| **Observer** | `Observer`, `ConsoleView` | Отделяет отображение от логики, легко добавить новые виды вывода | +| **Command** | `Command`, `MoveCommand` | Реализует пошаговое перемещение с возможностью отмены (undo/redo) | + +## 2. Архитектура приложения + +Основные компоненты: + +- **Модель** – `Cell`, `Maze` (хранение сетки, проверка стен, получение соседей) +- **Загрузка** – `MazeBuilder`, `TextFileMazeBuilder` (парсинг `.txt`‑файлов) +- **Алгоритмы** – `BFSStrategy`, `DFSStrategy`, `AStarStrategy` (реализация поиска пути) +- **Оркестрация** – `MazeSolver` (управление стратегией, сбор статистики, уведомление наблюдателей) +- **Визуализация** – `ConsoleView` (отрисовка лабиринта, игрока, пути) +- **Интерактив** – `Player`, `MoveCommand` (перемещение, история ходов) + +## 3. Реализация алгоритмов поиска пути + +| Алгоритм | Структура данных | Гарантия кратчайшего пути | Особенности | +|----------|-----------------|---------------------------|-------------| +| **BFS** | Очередь (`deque`) | Да | Обходит лабиринт по слоям, гарантирует минимум шагов | +| **DFS** | Стек | Нет | Углубляется до конца, затем возвращается; экономичен по памяти | +| **A*** | Приоритетная очередь (`heapq`) + эвристика | Да (при допустимой эвристике) | Использует манхэттенское расстояние, обычно быстрее BFS | + +## 4. Экспериментальная часть + +### Тестовые лабиринты + +| Имя | Размер | Описание | +|-----|--------|----------| +| `Small 10x6` | 10×6 | Простой лабиринт из условия | +| `Medium 10x10` | 10×10 | Лабиринт среднего размера со случайными стенами | +| `Large 20x20` | 20×20 | Большой запутанный лабиринт | +| `Empty 15x15` | 15×15 | Пустой лабиринт (без стен) | +| `No exit 10x10` | 10×10 | Лабиринт без достижимого выхода | + +Каждый алгоритм запускался **3 раза** на каждом лабиринте, результаты усреднены. + +### Результаты замеров + +| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути | +|----------|----------|------------|-----------------|------------| +| Small 10x6 | BFS | 0.025 | 9 | 5 | +| Small 10x6 | DFS | 0.041 | 26 | 19 | +| Small 10x6 | A* | 0.015 | 5 | 5 | +| Medium 10x10 | BFS | 0.016 | 18 | 8 | +| Medium 10x10 | DFS | 0.008 | 9 | 8 | +| Medium 10x10 | A* | 0.015 | 8 | 8 | +| Large 20x20 | BFS | 0.136 | 116 | 69 | +| Large 20x20 | DFS | 0.159 | 173 | 69 | +| Large 20x20 | A* | 0.198 | 110 | 69 | +| Empty 15x15 | BFS | 0.255 | 240 | 29 | +| Empty 15x15 | DFS | 0.142 | 224 | 119 | +| Empty 15x15 | A* | 0.590 | 224 | 29 | +| No exit 10x10 | BFS | 0.042 | 36 | 0 | +| No exit 10x10 | DFS | 0.035 | 36 | 0 | +| No exit 10x10 | A* | 0.065 | 36 | 0 | + +### Графики + +![Сравнение производительности алгоритмов](performance_plot.png) + +На графике представлено сравнение трёх алгоритмов по трём метрикам: время выполнения, количество посещённых клеток и длина найденного пути. + +## 5. Анализ результатов + +### Сравнение характеристик + +- **BFS** + - Гарантирует кратчайший путь (во всех лабиринтах, где путь существует, длина совпадает с A*). + - Посещает довольно много клеток (например, 240 в пустом лабиринте). + - Время стабильно, но на больших лабиринтах уступает DFS по скорости. + +- **DFS** + - Самый быстрый на средних и больших лабиринтах (0.008–0.159 мс). + - Не находит кратчайший путь: в пустом лабиринте длина пути 119 вместо 29. + - Посещает среднее количество клеток (224 в пустом, 173 в большом). + +- **A*** + - Всегда находит оптимальный путь (как BFS). + - Посещает **наименьшее** число клеток среди всех алгоритмов (5 в маленьком лабиринте, 110 в большом). + - Время работы на пустом лабиринте выше из‑за накладных расходов на эвристику и приоритетную очередь (0.590 мс против 0.142 мс у DFS). + - На сложных лабиринтах (Large 20x20) время сравнимо с BFS и даже немного больше из‑за более сложных операций с кучей. + +### Ключевые выводы + +1. **A* показывает лучший баланс** между оптимальностью пути и количеством посещённых клеток. Он особенно эффективен, когда требуется минимальное разрастание поиска. +2. **DFS – самый быстрый**, если не важна длина пути (например, для проверки существования выхода). +3. **BFS** остаётся простым и предсказуемым, но уступает A* по числу посещений. +4. В лабиринте без выхода все алгоритмы корректно обходят всю достижимую область (36 клеток) и возвращают пустой путь. + +### Рекомендации по выбору алгоритма + +| Сценарий | Рекомендуемый алгоритм | +|----------|------------------------| +| Нужен **гарантированно кратчайший путь** и не важна скорость | BFS или A* (A* предпочтительнее) | +| **Скорость критична**, путь может быть неоптимальным | DFS | +| Нужен **компромисс** (оптимальность + мало посещений) | A* | +| Проверка **существования пути** (без восстановления маршрута) | DFS | + +## 6. Заключение + +Применение паттернов проектирования (Builder, Strategy, Observer, Command) позволило создать гибкую, расширяемую и легко тестируемую программу. Реализованные алгоритмы поиска пути были экспериментально сравнены на лабиринтах разной сложности. Полученные результаты подтверждают теоретические оценки: A* даёт наилучшее сочетание оптимальности и эффективности по числу посещённых клеток, DFS выигрывает по скорости, а BFS остаётся простым базовым решением. + +Программа также предоставляет интерактивный режим с ручным управлением игроком и возможностью отмены ходов. + -- 2.43.0 From d452965a3b9c4e5a2bf03cbab6ed81825ee91520 Mon Sep 17 00:00:00 2001 From: anikinvd Date: Fri, 22 May 2026 18:08:57 +0000 Subject: [PATCH 15/15] [2] Add cartinka s grafikami --- anikinvd/docs/performance_plot.png | Bin 0 -> 82660 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 anikinvd/docs/performance_plot.png diff --git a/anikinvd/docs/performance_plot.png b/anikinvd/docs/performance_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef0235e8020397a479853a44eb4e41802fca32 GIT binary patch literal 82660 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 z

{o(fj1>!*CCZw|(JJB+zlA2~`ET?N-Zw>SVW#FwXBK-^Piv6+?>y@%1RFp5sV8 z$gf>Eb*k5LxLc_zO(=o4TqqAhzQ~ad#!j9IKknMOa}GpY;B8=qmLgE?t9qag+oC8_ z)zmK2)wE!56rP?3_7u2G{CLNl_z^_w3caA7Re{v_B1rp*)KbHhMDxsDFS&F181}s1 zNPD2ap7+5#OMU$$?z(lDU5EsMl24 z1V%S?CxF$Y2BXN0`zN82M6_-^n-q>nzx!LAt`Ul2Y4pBsR!Aw4@6C2fn`G$lb#QTGi_)`~-)d3^xL&l`cQOtYi=v#1>*nezCDuganLxx6 zqRLp*HJPM5w6iOq5u#CQK0uM-f>x%VaWoDhlS1&xz0#kZRWx&)=X1kvUOD1o6-6?? z53c+DjlVu++h{~v+nIOsy|H|R`5Ci}$XgJbGo|*7lH008s~TqrgN$RNb&a!XRi4b* z?W8=?D4wsp-Zs(=vdaX*dEj5`S|%sobUhsId9#rTt#=2OTzJ&!TZlqo=U?~k(|PwB z0sMZGZLs`aOK0@-%Lso?ojXUNlAIOLN_1G5)LNfFdNeEWa8mkwln#gAk@}7%Q6eDF@XlF0T#UhXBhWK$8 z4r9exj`A~7FCq>MsWesiN3u%(_aYdDNZ6=oHboZ5a`b4QzD?pyv$^5Y8RQvp)jX`< z1^V`!%imgG5p-Q8r@?P>OQ2tIdl#k40B+F(I~7eW8Vtz;w~FFt3EnHoT%`(n|B_dmW0(WYNNJ<|Y3uA7^8LOxVZjs%8)~W)BX%__+;`o>o%f--}c}MZwTWmBFU+&j0<_p=+Pd z|4zk~kNZ-q^xv0xuSTPMWboiW{|z2BGor~qe~@2Y>dQO-_g}M?d;Yi4dH#E-UHtFM z3dQXo{;x;)-)HOnKR<%5cgl~o#sEhZ_b(!ch#mN5$GAV;{rT4Z54ku@EA-*cbg1mZ zcbnV3AIE^SMnkl5$I`uK)U)%`i2QU;hj%R9S zA!~7t$a$alncI%~*Lj+W*wvPGNP)V-mTyrE8`5FMW4BLRZ5 z@NFc&?vdi3gE^oqSqg&1`(F25r$~<-HzUX=s8gIk3 zMCi?atG+L(lK=sn7#?IC8VV-cY8^BOj3X31LgrkVcX{hpI4V znN@*-xlfY}cQLaf=N&}F7X3v-Lr+hRdgXssK%OWcV(8G- zo^ZHN%7bL8L1&WpjFD8sR&V2ILh}VEB8i$3NX9xt2wAKdtu#s9{xdW-s&bdtFu%x! zupt_S=yO(fZIYUFbHvFE;DjgFi*2-SnLQ`im9A;$9NjP9fj~ZRZt?1N@98OLlAk>! zF^VQW^~H=HAbj8#r3t<_3ro|X;CzxnG+B@K3=@@lcJy%;oX-10W_)6{OqOuNB<9Ys z(T>nRz4Y{Mkti&oHO6^<;`7x9wtXRreuju^fz*S_)w+|R(WVXc$Wr`90@1v{6ukb+ zpQ9V`gKxv0@{7`%+vv!BAA+PJT1t;Om@cla0GvUu7m(eeuwH=5sZ*!UEta&QdSx7e zWH$Ou;c}yKlrC9e=%id@YTF&7E1&Irs|I}M+;;!tl3u+uoDFW~1bb>rE@cbqqbKND zHdwpIbKi}t;`gYD^lR7Gy>$3Zk-tS}yo)k}kzjJ-D_^cxEEo2fj0Yn=HVYUnc3=q{ zFuy2WN2jfrbN@d1`+qw?W4UMKIA?QO2fePq?DRE`rU1Q%+c)~KMJ zc}5bWvflL6F7SWvMBpnxcp>(|7g^0N)994|rV2oZ&Z01W(%@5q9u)pc2Q%ADyI{44 zLh;Z$1ed)D76~a_zK8OX>pLpsVMEo-uLy5HO z0uP&s0ZewQ{bW;BUN{TOBOT9M)W!<8a0OfXfG-WZ!odRPA0@1r!pffp1jR7h+VT2| z%ZzQf4U;gBl7Y&|+TVCoMRU9V4p_l;LiJ7|s8mxZVGr>Hjm&|34q$YwfDsD?zXI2EUR|?T0zo KoEbWE&Hn;}1k4fu literal 0 HcmV?d00001 -- 2.43.0