diff --git a/VaravinVV/.vscode/settings.json b/VaravinVV/.vscode/settings.json new file mode 100644 index 0000000..c9ebf2d --- /dev/null +++ b/VaravinVV/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system" +} \ No newline at end of file diff --git a/VaravinVV/docs/data/performance_plots.png b/VaravinVV/docs/data/performance_plots.png new file mode 100644 index 0000000..f113c69 Binary files /dev/null and b/VaravinVV/docs/data/performance_plots.png differ diff --git a/VaravinVV/docs/data/res.csv b/VaravinVV/docs/data/res.csv new file mode 100644 index 0000000..1673eaa --- /dev/null +++ b/VaravinVV/docs/data/res.csv @@ -0,0 +1,31 @@ +Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec) +LinkedList,random,1,0.020151,0.002076,0.000330 +LinkedList,random,2,0.019539,0.001570,0.000161 +LinkedList,random,3,0.020193,0.001963,0.000151 +LinkedList,random,4,0.020033,0.001751,0.000161 +LinkedList,random,5,0.019602,0.002023,0.000175 +LinkedList,sorted,1,0.020010,0.002227,0.000175 +LinkedList,sorted,2,0.019032,0.001596,0.000122 +LinkedList,sorted,3,0.019683,0.001889,0.000195 +LinkedList,sorted,4,0.019917,0.001636,0.000215 +LinkedList,sorted,5,0.019039,0.001624,0.000141 +HashTable,random,1,0.003015,0.000273,0.000024 +HashTable,random,2,0.002447,0.000214,0.000020 +HashTable,random,3,0.002656,0.000226,0.000026 +HashTable,random,4,0.002447,0.000205,0.000022 +HashTable,random,5,0.002181,0.000381,0.000021 +HashTable,sorted,1,0.002358,0.000309,0.000025 +HashTable,sorted,2,0.002539,0.000205,0.000019 +HashTable,sorted,3,0.002286,0.000234,0.000023 +HashTable,sorted,4,0.002566,0.000223,0.000019 +HashTable,sorted,5,0.002144,0.000230,0.000022 +BST,random,1,0.001556,0.000107,0.000021 +BST,random,2,0.001631,0.000116,0.000019 +BST,random,3,0.001351,0.000106,0.000016 +BST,random,4,0.001378,0.000148,0.000017 +BST,random,5,0.001617,0.000121,0.000017 +BST,sorted,1,0.066839,0.006176,0.000532 +BST,sorted,2,0.064324,0.005361,0.000521 +BST,sorted,3,0.065574,0.005315,0.000562 +BST,sorted,4,0.063277,0.004858,0.000487 +BST,sorted,5,0.058764,0.005325,0.000693 diff --git a/VaravinVV/docs/data/tables.py b/VaravinVV/docs/data/tables.py new file mode 100644 index 0000000..5c70c13 --- /dev/null +++ b/VaravinVV/docs/data/tables.py @@ -0,0 +1,51 @@ +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +df = pd.read_csv('res.csv') + +grouped = df.groupby(['Structure', 'Mode']).agg({ + 'Insert (sec)': ['mean', 'std'], + 'Search (sec)': ['mean', 'std'], + 'Delete (sec)': ['mean', 'std'] +}).reset_index() + +grouped.columns = ['Structure', 'Mode', 'Insert_mean', 'Insert_std', + 'Search_mean', 'Search_std', 'Delete_mean', 'Delete_std'] + +structures = grouped['Structure'].unique() +modes = grouped['Mode'].unique() + +fig, axes = plt.subplots(1, 3, figsize=(15, 5)) +operations = ['Insert', 'Search', 'Delete'] +colors = {'random': 'skyblue', 'sorted': 'lightcoral'} + +for i, op in enumerate(operations): + ax = axes[i] + mean_col = f'{op}_mean' + std_col = f'{op}_std' + x = np.arange(len(structures)) + width = 0.35 + for j, mode in enumerate(modes): + data = grouped[grouped['Mode'] == mode] + means = [data[data['Structure'] == s][mean_col].values[0] for s in structures] + stds = [data[data['Structure'] == s][std_col].values[0] for s in structures] + offset = (j - 0.5) * width + bars = ax.bar(x + offset, means, width, yerr=stds, capsize=3, + label=mode.capitalize(), color=colors[mode]) + + ax.set_xticks(x) + ax.set_xticklabels(structures) + ax.set_ylabel('Time (seconds)') + ax.set_title(f'{op} Time') + ax.legend() + + if op == 'Insert': + ax.set_yscale('log') + ax.set_ylabel('Time (seconds) [log scale]') + +plt.tight_layout() +plt.savefig('performance_plots.png', dpi=150) +plt.show() + +print("Графики сохранены в файл performance_plots.png") \ No newline at end of file diff --git a/VaravinVV/docs/data/task1_1.py b/VaravinVV/docs/data/task1_1.py new file mode 100644 index 0000000..34fb2fd --- /dev/null +++ b/VaravinVV/docs/data/task1_1.py @@ -0,0 +1,282 @@ +import random +import time +import csv +import sys + +sys.setrecursionlimit(20000) + +def ll_insert(head, name, phone): + data = {'name': name, 'phone': phone, "next": None} + + if head is None: + return data + + current = head + while current: + if current['name'] == name: + current['phone'] = phone + return head + if current['next'] is None: + last = current + current = current['next'] + + last['next'] = data + return head + + +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 is None: + return None + + if head['name'] == name: + return head['next'] + + prev = head + current = head['next'] + while current: + if current['name'] == name: + prev['next'] = current['next'] + return head + prev = current + current = current['next'] + return head + + +def ll_list_all(head): + data_list = [] + current = head + while current: + data_list.append({'name': current['name'], 'phone': current['phone']}) + current = current['next'] + data_list.sort(key=lambda x: x['name']) + return data_list + + +def hash_function(name, size): + return hash(name) % size + + +def ht_insert(buckets, name, phone): + index = hash_function(name, len(buckets)) + head = buckets[index] + new_head = ll_insert(head, name, phone) + buckets[index] = new_head + return buckets + + +def ht_find(buckets, name): + index = hash_function(name, len(buckets)) + head = buckets[index] + return ll_find(head, name) + + +def ht_delete(buckets, name): + index = hash_function(name, len(buckets)) + head = buckets[index] + new_head = ll_delete(head, name) + buckets[index] = new_head + return buckets + + +def ht_list_all(buckets): + all_records = [] + for head in buckets: + current = head + while current is not None: + all_records.append((current['name'], current['phone'])) + current = current['next'] + all_records.sort(key=lambda x: x[0]) + return all_records + + +def create_node(name, phone): + return {'name': name, 'phone': phone, 'left': None, 'right': None} + + +def bst_insert(root, name, phone): + if root is None: + return 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 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 = 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_list_all(root): + result = [] + + def inorder(node): + if node is None: + return + inorder(node['left']) + result.append((node['name'], node['phone'])) + inorder(node['right']) + + inorder(root) + return result + +def generate_records(n, seed=50): #почти в точности позаимствовано, просто понял что можно уже существующие в отдельный список заносить + random.seed(seed) + records = [] + for i in range(1, n + 1): + name = f"User_{i:05d}" + phone = "8" + ''.join(str(random.randint(0, 9)) for _ in range(10)) + 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): + 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) + end = time.perf_counter() + insert_time = end - 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) + end = time.perf_counter() + find_time = end - start + + to_delete = random.sample(existing_names, 10) + start = time.perf_counter() + for name in to_delete: + struct = struct_funcs['delete'](struct, name) + end = time.perf_counter() + delete_time = end - start + + 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 results + +def main(): + N = 1000 + base_records = generate_records(N) + shuffled, sorted_records = prepare_datasets(base_records) + + structures = { + 'LinkedList': { + 'name': 'LinkedList', + 'create': lambda: None, + 'insert': ll_insert, + 'find': ll_find, + 'delete': ll_delete, + 'list_all': ll_list_all + }, + 'HashTable': { + 'name': 'HashTable', + 'create': lambda: [None] * 10, + 'insert': ht_insert, + 'find': ht_find, + 'delete': ht_delete, + 'list_all': ht_list_all + }, + 'BST': { + 'name': 'BST', + 'create': lambda: None, + 'insert': bst_insert, + 'find': bst_find, + 'delete': bst_delete, + 'list_all': bst_list_all + } + } + + all_results = [] + repeats = 5 + + for struct_name, funcs in structures.items(): + print(f"Тестирование {struct_name} на случайном порядке...") + res = run_experiment(funcs, shuffled, 'random', repeats) + all_results.extend(res) + + print(f"Тестирование {struct_name} на отсортированном порядке...") + res = run_experiment(funcs, sorted_records, 'sorted', repeats) + all_results.extend(res) + + with open('res.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}" + ]) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/VaravinVV/docs/data/task2/easy.txt b/VaravinVV/docs/data/task2/easy.txt new file mode 100644 index 0000000..ccbfb9a --- /dev/null +++ b/VaravinVV/docs/data/task2/easy.txt @@ -0,0 +1,10 @@ +########## +#S ### # +## ## # # +# ## # # +# #### # # +# # # # +### # +### ### ## +# ### E# +########## \ No newline at end of file diff --git a/VaravinVV/docs/data/task2/empty.txt b/VaravinVV/docs/data/task2/empty.txt new file mode 100644 index 0000000..1a1141e --- /dev/null +++ b/VaravinVV/docs/data/task2/empty.txt @@ -0,0 +1 @@ +S E \ No newline at end of file diff --git a/VaravinVV/docs/data/task2/graphs/results_easy.txt.png b/VaravinVV/docs/data/task2/graphs/results_easy.txt.png new file mode 100644 index 0000000..73c7242 Binary files /dev/null and b/VaravinVV/docs/data/task2/graphs/results_easy.txt.png differ diff --git a/VaravinVV/docs/data/task2/graphs/results_empty.txt.png b/VaravinVV/docs/data/task2/graphs/results_empty.txt.png new file mode 100644 index 0000000..f75ea4d Binary files /dev/null and b/VaravinVV/docs/data/task2/graphs/results_empty.txt.png differ diff --git a/VaravinVV/docs/data/task2/graphs/results_hard.txt.png b/VaravinVV/docs/data/task2/graphs/results_hard.txt.png new file mode 100644 index 0000000..6dc9161 Binary files /dev/null and b/VaravinVV/docs/data/task2/graphs/results_hard.txt.png differ diff --git a/VaravinVV/docs/data/task2/graphs/results_medium.txt.png b/VaravinVV/docs/data/task2/graphs/results_medium.txt.png new file mode 100644 index 0000000..2e390fc Binary files /dev/null and b/VaravinVV/docs/data/task2/graphs/results_medium.txt.png differ diff --git a/VaravinVV/docs/data/task2/graphs/results_noexit.txt.png b/VaravinVV/docs/data/task2/graphs/results_noexit.txt.png new file mode 100644 index 0000000..607b334 Binary files /dev/null and b/VaravinVV/docs/data/task2/graphs/results_noexit.txt.png differ diff --git a/VaravinVV/docs/data/task2/hard.txt b/VaravinVV/docs/data/task2/hard.txt new file mode 100644 index 0000000..f0200a4 --- /dev/null +++ b/VaravinVV/docs/data/task2/hard.txt @@ -0,0 +1,101 @@ +##################################################################################################### +#S # # # # # # # # # # # # # # # # # # # +# # # ####### ########### # # # ######### ######### ##### # ### # ####### ##### # # # # # ### ### # # +# # # # # # # # # # # # # # # # # # # # # # # +### ### # ##### ##### ### ### ####### # # # ##### # # # ####### ##### ##### ### # ##### # ####### # # +# # # # # # # # # # # # # # # # # # # # # +# # # ### # ### # # ####### # # ### ##### ### ### # ####### # ### ### ##### ##### ### ##### ### ### # +# # # # # # # # # # # # # # # # # # # # # +# ### ##### ######### ### ####### ### # ### # ### # ######### # ######### # ### # ### # ##### ####### +# # # # # # # # # # # # # # # # # # # # # # # # # # +### ### ### # ### ######### # ##### ### # # ### ####### # ########### # # ### ############### ####### +# # # # # # # # # # # # # # # # +# ##### # # ####### # # ##### ### # ### ######### # ##### ##### ### ### # ### # # ####### # ### ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # ### # ### # ### # ### # ### ### # # ####### ####### ##### ##### # # # # ##### ### ### ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### # # ####### ##### # ######### # # # # ### # ### ### ### ##### # # ### # # ### # ####### +# # # # # # # # # # # # # # # # # # # # # # # # # # +##### # ##### # # ### # # # # # ####### # ### # ##### ### # ##### # # # # ##### # ### # ##### # # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # ##### # # # ### # ### # ### ### # ##### # ### # # ##### ############# ##### ### ##### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### # # ### # ### ################### ### ### ### # ### # ### # # ### ### ######### ### ####### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +##### ### ##### ####### ### ### ##### # # ### ### ### ### ### # ### ### ### ### ##### # # ### # ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # ### # ### # # # # # ####### # ### ############# ####### ### ### ### # # ### ####### # ### # ### +# # # # # # # # # # # # # # # # # # # # # # # +# # # ##### ### # ### # ### # ### # # # # # ### ### # ### ### # # ### ##### # ### ### # ##### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ##### ##### # # # ############### ##### # ### ### # # ##### ####### ##### ####### ##### ### # # +# # # # # # # # # # # # # # # # # # # # # # # +##### ### ##### # # # # # # ### ##### ### ########### # ### # # # ### ### # # # ### # # ######### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +### # ### # ### # ##### ##### ##### ########### # ### ### ##### # ##### ### ### ##### ######### # # # +# # # # # # # # # # # # # # # # # # # # # # +# ### # ####### # ### ##### # # ############# ### ####### # # ### # # ### # ####### ### # # # # ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # +####### # # ### # ########### # ### ##### ########### # ##### ### # ### ############# # # # # ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### ##### ### # ### # # ### ### ### ### # ##### ##### ### # # ### ### # # ##### ### ####### ####### +# # # # # # # # # # # # # # # # # # # # # # # # +# ##### # # # # ##### # # # ######### ### # ####### # # ####### # ##### ### # ### ### # # ####### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ####### # # # # # ### ####### # # # ####### # ### # ### ### # ##### ##### ### # ##### ### ### # # +# # # # # # # # # # # # # # # # # # # # # # # # +# ##### ### # # ######### ##### ####### ##### # ##### ####### ### ### ####### ##### ### ##### # # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # ######### # # # ####### ### # ####### # # ##### # ### # ### # ########### ############# ##### # # +# # # # # # # # # # # # # # # # # # # # # # # +# ### ##### # ##### # ### # ##### # ### # ### ### # # ### ##### ##### # # # ### # ############# # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +### # ### ### # # ######### ### ### ### ### # # ####### # ### ######### ### ##### # # # # ### # ##### +# # # # # # # # # # # # # # # # # # # # # +### # ######### ######### # ### # # ### # ############# # # # # # # ############# ######### # ####### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # ### ### ### ### ### # ##### # # ##### ####### # # # # ### # # # # ### ### # # # # ##### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### ##### ### ### # # # ### # # ######### ######### ##### ##### # ######### ### # # ### # ### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +### ##### ### # # # ############### # ### ### # # ### ### # ##### # ### # ##### ### ######### # ##### +# # # # # # # # # # # # # # # # # # # # # # # +# ##### ### ### ### # ####### # ### # ### ### ######### # ### ######### # # ### # ### ########### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### ### # # ##### ### # # ### ### ##### ### ### # ########### # ### ####### # # ######### ### ### +# # # # # # # # # # # # # # # # # # # # +##### ### # # # # # # ##### ####### # # # ####### # ####### # ##### ######### # ### # ####### # ##### +# # # # # # # # # # # # # # # # # # # # # # # # +# ### # ### ### ####### # ### # ### # # ####### # ### # ### ##### ##### ### ####### # # # ##### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### # # ############### # # ##### # ##### ########### # # # ### # ### ### ### ### # ### # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +##### ######### ##### ### # ### # ### # ### ### ####### ######### ########### ##### # ### # ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +### # ### ### # ##### # ##### # ##### # # ##### ####### # ####### ### ##### ### ####### ##### ### ### +# # # # # # # # # # # # # # # # # # # # # # # +##### ### # # ##### ####### ### # ##### ####### ### # # # ### ####### ### # ##### ####### # # ##### # +# # # # # # # # # # # # # # # # # # +# ### ####### ##### ####### ### # ### ##### ##### ### # # ### # ### ### # # # ####### # # ##### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### ### ##### ######### # ##### ##### # # # # ### ##### ### # # # # # ######### # ######### # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ####### ### ##### ######### ##### ########### # ### # ### # # # # ##### # # ### ##### ##### ### +# # # # # # # # # # # # # # # # # # # # # # # +# # # ### # ### ##### ### # ########### ##### # ####### # ##### # # ### # # ##### ### # ######### ### +# # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ### # ######### # ### # # # ##### ### # # ### ##### ##### ### ### ############# ####### # # +# # # # # # # # # # # # # # # # # # # # # # # # # +##### ### # ### ### # # # ### ##### # ##### # ##### # ### ##### # ### ### # # # # # ##### # ######### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### # ### # # # ##### # ### ### ##### # ### ##### # ### ####### ### # ##### ### # ##### # # ### +# # # # # # # # # # # # # # # # # # # # # # # +# # # ### ########### ##### # ### # # ### # ######### ####### # # # # ########### ### ####### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ### ############# ### # ### ##### ### ### ##### # # ### ######### ### ### ### # # ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +##### ### # ### # # # # # ### # # ### # # ####### ####### # # ##### # ### # # ##### # ### # ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # ### ##### ### ### # # # ##### ### # ### ### ##### # ##### ### # # ##### # ####### # ######### # +# # # # # # # # # # # # # # # # # # # # # # E# +##################################################################################################### diff --git a/VaravinVV/docs/data/task2/main.py b/VaravinVV/docs/data/task2/main.py new file mode 100644 index 0000000..f3773f4 --- /dev/null +++ b/VaravinVV/docs/data/task2/main.py @@ -0,0 +1,427 @@ +import abc +import heapq +import time +from collections import deque +from dataclasses import dataclass +from typing import List, Optional, Dict, Set, Tuple, Any +import csv +import os +import sys + +class Cell: + #тут что такое клетка + def __init__(self, x: int, y: int, is_wall: bool = False, + is_exit: bool = False, is_start: bool = False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_exit = is_exit + self.is_start = is_start + + def __eq__(self, other): + return isinstance(other, Cell) and self.x == other.x and self.y == other.y + + def __hash__(self): + return hash((self.x, self.y)) + + def is_passable(self) -> bool: + return not self.is_wall + + def __repr__(self) -> str: + return f"Cell({self.x},{self.y})" + + +class Maze: + def __init__(self, width: int, height: int): #что содержит лабиринт, начало конец и тд + self.width = width + self.height = height + self.grid: List[List[Cell]] = [] + self.start_cell: Optional[Cell] = None + self.exit_cell: Optional[Cell] = None + + def set_cell(self, x: int, y: int, cell: Cell) -> None: #ставим клетку куда надо или не ставим если в границы не попала + if not (0 <= x < self.width and 0 <= y < self.height): + raise IndexError("координаты вне границ лабиринта") + self.grid[y][x] = cell + + def get_cell(self, x: int, y: int) -> Optional[Cell]: #тут уже из коррдинат клетку вытаскиваем + if 0 <= x < self.width and 0 <= y < self.height: + return self.grid[y][x] + return None + + def get_neighbors(self, cell: Cell) -> List[Cell]: #если соседняя клетка проходима - добавляем + neighbors = [] + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + nx, ny = cell.x + dx, cell.y + dy + neighbor = self.get_cell(nx, ny) + if neighbor and neighbor.is_passable(): + neighbors.append(neighbor) + return neighbors + + +class MazeBuilder(abc.ABC): + @abc.abstractmethod + def build_from_file(self, filename: str) -> Maze: + pass + + +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename: str) -> Maze: + lines = [] + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + line = line.rstrip('\n') + if line: #игнорируем пустые строки + lines.append(line) + + if not lines: + raise ValueError("Файл пуст") + + height = len(lines) + width = max(len(line) for line in lines) + + maze = Maze(width, height) + #инициализируем сетку пустыми клетками,по умолчанию стенами + maze.grid = [[Cell(x, y, is_wall=True) for x in range(width)] for y in range(height)] + + start_cell = None + exit_cell = None + + for y, line in enumerate(lines): + for x, ch in enumerate(line): + if x >= width: + continue + if ch == '#': + continue + elif ch == ' ': + cell = Cell(x, y, is_wall=False) + elif ch == 'S': + cell = Cell(x, y, is_wall=False, is_start=True) + start_cell = cell + elif ch == 'E': + cell = Cell(x, y, is_wall=False, is_exit=True) + exit_cell = cell + else: + #любой другой символ считаем проходом + cell = Cell(x, y, is_wall=False) + maze.set_cell(x, y, cell) + + if start_cell is None: + raise ValueError("отсутствует стартовая клетка (S)") #invalid check + if exit_cell is None: + raise ValueError("отсутствует выход (E)") + + maze.start_cell = start_cell + maze.exit_cell = exit_cell + return maze + +class PathFindingStrategy(abc.ABC): + @abc.abstractmethod + def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: + pass + +#дальше скорее математика, методы вроде ещё в том семаке разбирали +class BFSStrategy(PathFindingStrategy): + def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: + if start is exit_: + return [start], 1 + + queue = deque([start]) #используйте deque 👍 + visited: Set[Cell] = {start} + parent: Dict[Cell, Optional[Cell]] = {start: None} + + while queue: + current = queue.popleft() + if current is exit_: + #восстановление пути + path = [] + cur = current + while cur is not None: + path.append(cur) + cur = parent[cur] + path.reverse() + return path, len(visited) + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + queue.append(neighbor) + + return [], len(visited) + + +class DFSStrategy(PathFindingStrategy): + def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: + if start is exit_: + return [start], 1 + + stack = [start] + visited: Set[Cell] = {start} + parent: Dict[Cell, Optional[Cell]] = {start: None} + + while stack: + current = stack.pop() + if current is exit_: + path = [] + cur = current + while cur is not None: + path.append(cur) + cur = parent[cur] + path.reverse() + return path, len(visited) + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + parent[neighbor] = current + stack.append(neighbor) + + return [], len(visited) + + +class AStarStrategy(PathFindingStrategy): + @staticmethod + def _heuristic(cell: Cell, target: Cell) -> int: + return abs(cell.x - target.x) + abs(cell.y - target.y) #самая простая эвристика + + def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]: + if start is exit_: + return [start], 1 + counter = 0 + open_set = [(0, counter, start)] + g_score: Dict[Cell, int] = {start: 0} + f_score: Dict[Cell, int] = {start: self._heuristic(start, exit_)} + parent: Dict[Cell, Optional[Cell]] = {start: None} + closed_set: Set[Cell] = set() + visited_count = 0 + + while open_set: + _, _, current = heapq.heappop(open_set) + if current in closed_set: + continue + closed_set.add(current) + visited_count = len(closed_set) + + if current is exit_: + path = [] + cur = current + while cur is not None: + path.append(cur) + cur = parent[cur] + path.reverse() + return path, visited_count + + for neighbor in maze.get_neighbors(current): + if neighbor in closed_set: + continue + tentative_g = g_score[current] + 1 + if neighbor not in g_score or tentative_g < g_score[neighbor]: + parent[neighbor] = current + g_score[neighbor] = tentative_g + f = tentative_g + self._heuristic(neighbor, exit_) + f_score[neighbor] = f + counter += 1 + heapq.heappush(open_set, (f, counter, neighbor)) + + return [], visited_count + +@dataclass +class SearchStats: + time_ms: float #время выполнения в мс + visited_cells: int #количество посещённых клеток + path_length: int #длина найденного пути (0 если пути нет) + path_found: bool #найден ли путь + +class Observer(abc.ABC): #я забыл что я там писать хотел после наблюдателя удачи мне завтра разобрать + @abc.abstractmethod #а я (гугл + хабр) разобрал снова балбесина!!! + def update(self, event_type: str, data: Any = None) -> None: + pass + + +class Subject: + def __init__(self): + self._observers: List[Observer] = [] + + def attach(self, observer: Observer) -> None: + if observer not in self._observers: + self._observers.append(observer) + + def detach(self, observer: Observer) -> None: + if observer in self._observers: + self._observers.remove(observer) + + def notify(self, event_type: str, data: Any = None) -> None: + for obs in self._observers: + obs.update(event_type, data) + + +class MazeSolver(Subject): + def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None): + super().__init__() + self.maze = maze + self._strategy = strategy + + def set_strategy(self, strategy: PathFindingStrategy) -> None: + self._strategy = strategy + + def solve(self) -> Optional[SearchStats]: + if self._strategy is None: + return None + start_time = time.perf_counter() + path, visited = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell) + end_time = time.perf_counter() + time_ms = (end_time - start_time) * 1000.0 + return SearchStats(time_ms, visited, len(path), len(path) > 0) + +class Benchmark: + def __init__(self, maze_files: List[str], runs_per_strategy: int = 5): + self.maze_files = maze_files + self.runs = runs_per_strategy + self.strategies = { + "BFS": BFSStrategy(), + "DFS": DFSStrategy(), + "AStar": AStarStrategy() + } + self.builder = TextFileMazeBuilder() + self.results = [] + + def run(self, output_csv: str): + for maze_file in self.maze_files: + if not os.path.exists(maze_file): + print(f"файл {maze_file} не найден") + continue + try: + maze = self.builder.build_from_file(maze_file) + except Exception as e: + print(f"ошибка загрузки {maze_file}: {e}") + continue + + print(f"обработка лабиринта: {maze_file} (размер {maze.width}x{maze.height})") + for strat_name, strategy in self.strategies.items(): + solver = MazeSolver(maze, strategy) + times = [] + visited_list = [] + path_lengths = [] + path_found = False + for run_idx in range(self.runs): + stats = solver.solve() + if stats is None: + continue + times.append(stats.time_ms) + visited_list.append(stats.visited_cells) + path_lengths.append(stats.path_length) + path_found = stats.path_found + if times: + avg_time = sum(times) / len(times) + avg_visited = sum(visited_list) / len(visited_list) + avg_length = sum(path_lengths) / len(path_lengths) + else: + avg_time = avg_visited = avg_length = 0.0 + self.results.append({ + "лабиринт": os.path.basename(maze_file), + "стратегия": strat_name, + "время_мс": round(avg_time, 3), + "посещено_клеток": round(avg_visited, 1), + "длина_пути": round(avg_length, 1), + "путь_найден": path_found + }) + print(f" {strat_name}: {avg_time:.3f} мс, посещено {avg_visited:.1f}, длина {avg_length:.1f}") + + with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути", "путь_найден"] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=',') + writer.writeheader() + for row in self.results: + writer.writerow(row) + print(f"\nрезультаты сохранены в {output_csv}") + +def interactive_mode(maze_file: str): + builder = TextFileMazeBuilder() + try: + maze = builder.build_from_file(maze_file) + except Exception as e: + print(f"ошибка загрузки лабиринта: {e}") + return + + strategies = { + "1": ("BFS", BFSStrategy()), + "2": ("DFS", DFSStrategy()), + "3": ("A*", AStarStrategy()) + } + print("\nвыберите алгоритм поиска:") + print("1. BFS") + print("2. DFS") + print("3. A*") + choice = input("введите (1/2/3): ").strip() + + if choice not in strategies: + print("неверный выбор, по умолчанию используется BFS.") + strat_name, strategy = strategies["1"] + else: + strat_name, strategy = strategies[choice] + + solver = MazeSolver(maze, strategy) + stats = solver.solve() + if stats is None: + print("ошибка с решением") + return + + path, _ = strategy.find_path(maze, maze.start_cell, maze.exit_cell) + path_set = set(path) + for y in range(maze.height): + row = [] + for x in range(maze.width): + cell = maze.get_cell(x, y) + if cell is maze.start_cell: + row.append('S') + elif cell is maze.exit_cell: + row.append('E') + elif cell in path_set: + row.append('*') + elif cell and cell.is_wall: + row.append('#') + else: + row.append(' ') + print(''.join(row)) + + print(f"\nстатистика ({strat_name}):") + print(f"время выполнения: {stats.time_ms:.3f} мс") + print(f"посещено клеток: {stats.visited_cells}") + print(f"длина пути: {stats.path_length}") + print(f"путь найден: {'да' if stats.path_found else 'нет'}") + + +def main(): + if len(sys.argv) < 2: + print("использование:") + print("режим визуализации: python main.py <файл_лабиринта>") + print("режим замера: python main.py --benchmark <список_лабиринтов> --runs <кол_во_итераций> --output <название_таблицы>.csv") + return + + if sys.argv[1] == "--benchmark": + args = sys.argv[2:] + maze_files = [] + runs = 5 + output = "benchmark_results.csv" + i = 0 + while i < len(args): + if args[i] == "--runs" and i+1 < len(args): + runs = int(args[i+1]) + i += 2 + elif args[i] == "--output" and i+1 < len(args): + output = args[i+1] + i += 2 + else: + maze_files.append(args[i]) + i += 1 + if not maze_files: + print("Ошибка: не указаны файлы лабиринтов.") + return + benchmark = Benchmark(maze_files, runs_per_strategy=runs) + benchmark.run(output) + else: + interactive_mode(sys.argv[1]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/VaravinVV/docs/data/task2/medium.txt b/VaravinVV/docs/data/task2/medium.txt new file mode 100644 index 0000000..0baeea6 --- /dev/null +++ b/VaravinVV/docs/data/task2/medium.txt @@ -0,0 +1,51 @@ +################################################### +#S # # # # +# # # ####### ########### # # # ######### ######### +# # # # # # # # # # # # # # +### ### # ##### ##### ### ### ####### # # # ##### # +# # # # # # # # # # # # # +# # # ### # ### # # ####### # # ### ##### ### ### # +# # # # # # # # # # +# ### ##### ######### ### ####### ### # ### # ### # +# # # # # # # # # # # # # # +### ### ### # ### ######### # ##### ### # # ### ### +# # # # # # # # # # # +# ##### # # ####### # # ##### ### # ### ######### # +# # # # # # # # # # # # # # +### # ### # ### # ### # ### # ### ### ### ######### +# # # # # # # # # # # # # +# # ### # ### # # ####### ##### # ######### ##### # +# # # # # # # # # # # # # +##### # ##### # # ### # # # # # ######### ##### ### +# # # # # # # # # # # # # +# # # # # ##### # # # ### # ### # ####### # ####### +# # # # # # # # # # # # # # # # # +# ##### # # ### # ### ################# # ### ### # +# # # # # # # # # # # # # +##### ### ##### ####### ### ### ##### ### # # ##### +# # # # # # # # # # # # +### # ### # ### # # # # # ##### # # ##### # # ##### +# # # # # # # # # # # # # # # # # +# # # ##### ### # ### # ### # ### # ####### # # # # +# # # # # # # # # # # # # # # +# # # ##### ##### # # # ####### # ### ####### ### # +# # # # # # # # # # # # # +##### ### ##### # # # # # # ##### # # # # ### # # # +# # # # # # # # # # # # # # # # # # # +### # ### # ### # ##### ##### ####### ########### # +# # # # # # # # # # # # +# ### # ####### # ### ##### ##### ### ### ######### +# # # # # # # # # # # # +####### # # ### # ######### # ##### # ##### # ### # +# # # # # # # # # # # # # # +# # ####### ### # ### # # ##### ######### ### ##### +# # # # # # # # # # # # # # +### ### # # # # ##### ### ##### # # # ### # # # # # +# # # # # # # # # # # # # # +### ### # # # # ### # # # ##### ######### ####### # +# # # # # # # # # # # # # +##### # # ### # # ########### ### ##### # # ### # # +# # # # # # # # # # # # # # +### ### # ##### # # ### ### ##### ### # # ### ### # +# # # # # # # # # # # # #E# +################################################### diff --git a/VaravinVV/docs/data/task2/noexit.txt b/VaravinVV/docs/data/task2/noexit.txt new file mode 100644 index 0000000..dd3f0bf --- /dev/null +++ b/VaravinVV/docs/data/task2/noexit.txt @@ -0,0 +1,5 @@ +##### +#S### +##### +###E# +##### \ No newline at end of file diff --git a/VaravinVV/docs/data/task2/res.csv b/VaravinVV/docs/data/task2/res.csv new file mode 100644 index 0000000..12c8979 --- /dev/null +++ b/VaravinVV/docs/data/task2/res.csv @@ -0,0 +1,16 @@ +лабиринт,стратегия,время_мс,посещено_клеток,длина_пути,путь_найден +easy.txt,BFS,0.046,32.0,17.0,True +easy.txt,DFS,0.026,28.0,17.0,True +easy.txt,AStar,0.037,21.0,17.0,True +medium.txt,BFS,1.141,1249.0,101.0,True +medium.txt,DFS,0.547,633.0,101.0,True +medium.txt,AStar,1.26,721.0,101.0,True +hard.txt,BFS,4.703,4997.0,209.0,True +hard.txt,DFS,3.61,4026.0,209.0,True +hard.txt,AStar,6.119,3377.0,209.0,True +noexit.txt,BFS,0.002,1.0,0.0,False +noexit.txt,DFS,0.002,1.0,0.0,False +noexit.txt,AStar,0.005,1.0,0.0,False +empty.txt,BFS,0.003,3.0,3.0,True +empty.txt,DFS,0.003,3.0,3.0,True +empty.txt,AStar,0.004,3.0,3.0,True diff --git a/VaravinVV/docs/data/task2/tables.py b/VaravinVV/docs/data/task2/tables.py new file mode 100644 index 0000000..e17a747 --- /dev/null +++ b/VaravinVV/docs/data/task2/tables.py @@ -0,0 +1,53 @@ +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +import sys +import os + +def plot_results(csv_file: str, output_dir: str = "plots"): + if not os.path.exists(csv_file): + print(f"Файл {csv_file} не найден.") + return + df = pd.read_csv(csv_file, encoding='utf-8') + required = ["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути"] + for col in required: + if col not in df.columns: + print(f"В CSV отсутствует колонка {col}") + return + + os.makedirs(output_dir, exist_ok=True) + + sns.set_style("whitegrid") + + for maze_name in df["лабиринт"].unique(): + maze_df = df[df["лабиринт"] == maze_name] + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + fig.suptitle(f"Сравнение стратегий для лабиринта {maze_name}", fontsize=14) + + ax = axes[0] + sns.barplot(data=maze_df, x="стратегия", y="время_мс", ax=ax, palette="viridis") + ax.set_title("Время выполнения (мс)") + ax.set_ylabel("мс") + + ax = axes[1] + sns.barplot(data=maze_df, x="стратегия", y="посещено_клеток", ax=ax, palette="plasma") + ax.set_title("Количество посещённых клеток") + ax.set_ylabel("клетки") + + ax = axes[2] + sns.barplot(data=maze_df, x="стратегия", y="длина_пути", ax=ax, palette="coolwarm") + ax.set_title("Длина найденного пути") + ax.set_ylabel("шаги") + + plt.tight_layout() + plt.savefig(os.path.join(output_dir, f"results_{maze_name}.png"), dpi=150) + plt.close() + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Использование: python tables.py <папка_для_сохранения>") + print("Пример: python tables.py res.csv plots") + sys.exit(1) + csv_file = sys.argv[1] + out_dir = sys.argv[2] if len(sys.argv) > 2 else "plots" + plot_results(csv_file, out_dir) \ No newline at end of file diff --git a/VaravinVV/docs/report_task2.docx b/VaravinVV/docs/report_task2.docx new file mode 100644 index 0000000..09f44e5 Binary files /dev/null and b/VaravinVV/docs/report_task2.docx differ diff --git a/VaravinVV/docs/task1_report.docx b/VaravinVV/docs/task1_report.docx new file mode 100644 index 0000000..6cce942 Binary files /dev/null and b/VaravinVV/docs/task1_report.docx differ