diff --git a/BolonkinNM/Task 1/.idea/.gitignore b/BolonkinNM/Task 1/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/BolonkinNM/Task 1/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/BolonkinNM/Task 1/.idea/ds_project_archive.iml b/BolonkinNM/Task 1/.idea/ds_project_archive.iml new file mode 100644 index 0000000..8e5446a --- /dev/null +++ b/BolonkinNM/Task 1/.idea/ds_project_archive.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/BolonkinNM/Task 1/.idea/inspectionProfiles/profiles_settings.xml b/BolonkinNM/Task 1/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/BolonkinNM/Task 1/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/BolonkinNM/Task 1/.idea/misc.xml b/BolonkinNM/Task 1/.idea/misc.xml new file mode 100644 index 0000000..b56db6e --- /dev/null +++ b/BolonkinNM/Task 1/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/BolonkinNM/Task 1/.idea/modules.xml b/BolonkinNM/Task 1/.idea/modules.xml new file mode 100644 index 0000000..699d578 --- /dev/null +++ b/BolonkinNM/Task 1/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/BolonkinNM/Task 1/README.md b/BolonkinNM/Task 1/README.md new file mode 100644 index 0000000..7e15dad --- /dev/null +++ b/BolonkinNM/Task 1/README.md @@ -0,0 +1,18 @@ +# Задание 1 — структуры данных + +Процедурная реализация: +- linked_list.py +- hash_table.py +- bst.py + +Эксперименты и отчёты: +- experiments.py +- plot_results.py +- results.csv +- docs/report.md +- docs/data/*.png + +Запуск: +```bash +python main.py +``` diff --git a/BolonkinNM/Task 1/Task 1.py b/BolonkinNM/Task 1/Task 1.py new file mode 100644 index 0000000..b0337c1 --- /dev/null +++ b/BolonkinNM/Task 1/Task 1.py @@ -0,0 +1,82 @@ +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +col_names = ['T(C)', 'Td(C)', 'HR%', 'ff(kmh)', 'Gust(kmh)', 'P_0(HPa)', 'P_sea(HPa)'] + +data = pd.read_csv( + "data_meteo.txt", + sep=r'\s+', + skiprows=1, + usecols=[2, 3, 4, 5, 6, 7, 8], + names=col_names, + engine='python' +) + +print("загружено записей (строк):", len(data)) + +data = data.apply(pd.to_numeric, errors='coerce') +n_before = len(data) +data = data.dropna(subset=col_names) +n_after = len(data) + +print(f"после приведения к числам и dropna: {n_after} строк (удалено {n_before - n_after})") + +def correlation(vec1, vec2, center=False): + v1 = np.asarray(vec1, dtype=float) + v2 = np.asarray(vec2, dtype=float) + + if center: + v1 = v1 - np.mean(v1) + v2 = v2 - np.mean(v2) + + dot_product = np.sum(v1 * v2) + norm1 = np.sqrt(np.sum(v1 ** 2)) + norm2 = np.sqrt(np.sum(v2 ** 2)) + + if norm1 == 0 or norm2 == 0: + return np.nan + + return dot_product / (norm1 * norm2) + +n = len(col_names) + +raw_matrix = np.zeros((n, n)) +center_matrix = np.zeros((n, n)) + +for i in range(n): + for j in range(n): + r_raw = correlation(data.iloc[:, i], data.iloc[:, j], center=False) + r_center = correlation(data.iloc[:, i], data.iloc[:, j], center=True) + + raw_matrix[i, j] = np.round(r_raw, 3) if not np.isnan(r_raw) else np.nan + center_matrix[i, j] = np.round(r_center, 3) if not np.isnan(r_center) else np.nan + +df_raw = pd.DataFrame(raw_matrix, index=col_names, columns=col_names) +df_center = pd.DataFrame(center_matrix, index=col_names, columns=col_names) + +print("\nкорреляция (raw):") +print(df_raw.to_string()) + +print("\nкорреляция (centered):") +print(df_center.to_string()) + +print("\nразница (centered - raw):") +print((df_center - df_raw).round(3).to_string()) + +plt.figure(figsize=(14, 8)) + +for col in col_names: + centered_values = data[col] - data[col].mean() + plt.plot(centered_values.values, label=col, marker='.', linewidth=1, markersize=4) + +plt.axhline(0, linestyle='--') +plt.title("центрированные метеоданные") +plt.xlabel("номер измерения") +plt.ylabel("отклонение от среднего") +plt.legend() +plt.grid() +plt.tight_layout() + +plt.savefig("meteo_analysis.png", dpi=150) +plt.show() \ No newline at end of file diff --git a/BolonkinNM/Task 1/bst.py b/BolonkinNM/Task 1/bst.py new file mode 100644 index 0000000..221fd67 --- /dev/null +++ b/BolonkinNM/Task 1/bst.py @@ -0,0 +1,118 @@ +from typing import Any, Dict, List, Optional + + +Node = Dict[str, Any] + + +def _make_node(name: str, phone: str) -> Node: + return {"name": name, "phone": phone, "left": None, "right": None} + + +def bst_insert(root: Optional[Node], name: str, phone: str) -> Node: + new_node = _make_node(name, phone) + + if root is None: + return new_node + + current = root + parent = None + + while current is not None: + parent = current + if name < current["name"]: + current = current["left"] + elif name > current["name"]: + current = current["right"] + else: + current["phone"] = phone + return root + + if name < parent["name"]: + parent["left"] = new_node + else: + parent["right"] = new_node + + return root + + +def bst_find(root: Optional[Node], name: str) -> Optional[str]: + current = root + while current is not None: + if name < current["name"]: + current = current["left"] + elif name > current["name"]: + current = current["right"] + else: + return current["phone"] + return None + + +def _find_min_node(node: Node) -> Node: + current = node + while current["left"] is not None: + current = current["left"] + return current + + +def bst_delete(root: Optional[Node], name: str) -> Optional[Node]: + if root is None: + return None + + parent = None + current = root + + while current is not None and current["name"] != name: + parent = current + if name < current["name"]: + current = current["left"] + else: + current = current["right"] + + if current is None: + return root + + if current["left"] is None or current["right"] is None: + child = current["left"] if current["left"] is not None else current["right"] + + if parent is None: + return child + + if parent["left"] is current: + parent["left"] = child + else: + parent["right"] = child + return root + + succ_parent = current + successor = current["right"] + while successor["left"] is not None: + succ_parent = successor + successor = successor["left"] + + current["name"] = successor["name"] + current["phone"] = successor["phone"] + + successor_child = successor["right"] + if succ_parent["left"] is successor: + succ_parent["left"] = successor_child + else: + succ_parent["right"] = successor_child + + return root + + +def bst_list_all(root: Optional[Node]) -> List[Dict[str, str]]: + result: List[Dict[str, str]] = [] + stack: List[Node] = [] + current = root + + while current is not None or stack: + while current is not None: + stack.append(current) + current = current["left"] + + current = stack.pop() + result.append({"name": current["name"], "phone": current["phone"]}) + current = current["right"] + + return result diff --git a/BolonkinNM/Task 1/docs/data/delete.png b/BolonkinNM/Task 1/docs/data/delete.png new file mode 100644 index 0000000..b5307a7 Binary files /dev/null and b/BolonkinNM/Task 1/docs/data/delete.png differ diff --git a/BolonkinNM/Task 1/docs/data/find.png b/BolonkinNM/Task 1/docs/data/find.png new file mode 100644 index 0000000..d0d4e81 Binary files /dev/null and b/BolonkinNM/Task 1/docs/data/find.png differ diff --git a/BolonkinNM/Task 1/docs/data/insert.png b/BolonkinNM/Task 1/docs/data/insert.png new file mode 100644 index 0000000..b2832b9 Binary files /dev/null and b/BolonkinNM/Task 1/docs/data/insert.png differ diff --git a/BolonkinNM/Task 1/docs/data/results.csv b/BolonkinNM/Task 1/docs/data/results.csv new file mode 100644 index 0000000..4ccfb69 --- /dev/null +++ b/BolonkinNM/Task 1/docs/data/results.csv @@ -0,0 +1,109 @@ +Структура,Режим,Операция,Замер,Время (сек) +LinkedList,случайный,insert,1,4.2622492010 +LinkedList,случайный,find,1,0.0314994130 +LinkedList,случайный,delete,1,0.0149069000 +LinkedList,случайный,insert,2,4.0154580330 +LinkedList,случайный,find,2,0.0393284500 +LinkedList,случайный,delete,2,0.0210732100 +LinkedList,случайный,insert,3,4.0436019780 +LinkedList,случайный,find,3,0.0344933660 +LinkedList,случайный,delete,3,0.0152639850 +LinkedList,случайный,insert,4,3.7182993220 +LinkedList,случайный,find,4,0.0327698850 +LinkedList,случайный,delete,4,0.0149959540 +LinkedList,случайный,insert,5,3.7082228200 +LinkedList,случайный,find,5,0.0303762490 +LinkedList,случайный,delete,5,0.0141406560 +LinkedList,случайный,insert,среднее,3.9495662708 +LinkedList,случайный,find,среднее,0.0336934726 +LinkedList,случайный,delete,среднее,0.0160761410 +HashTable,случайный,insert,1,0.2059865770 +HashTable,случайный,find,1,0.0014966100 +HashTable,случайный,delete,1,0.0006891700 +HashTable,случайный,insert,2,0.2024331460 +HashTable,случайный,find,2,0.0015934880 +HashTable,случайный,delete,2,0.0007212620 +HashTable,случайный,insert,3,0.2126128040 +HashTable,случайный,find,3,0.0016566220 +HashTable,случайный,delete,3,0.0008358420 +HashTable,случайный,insert,4,0.2157934910 +HashTable,случайный,find,4,0.0015542810 +HashTable,случайный,delete,4,0.0007269120 +HashTable,случайный,insert,5,0.2079924580 +HashTable,случайный,find,5,0.0013696990 +HashTable,случайный,delete,5,0.0006616050 +HashTable,случайный,insert,среднее,0.2089636952 +HashTable,случайный,find,среднее,0.0015341400 +HashTable,случайный,delete,среднее,0.0007269582 +BST,случайный,insert,1,0.0166981280 +BST,случайный,find,1,0.0001569360 +BST,случайный,delete,1,0.0000917280 +BST,случайный,insert,2,0.0184119040 +BST,случайный,find,2,0.0001517110 +BST,случайный,delete,2,0.0001163770 +BST,случайный,insert,3,0.0174662270 +BST,случайный,find,3,0.0001582930 +BST,случайный,delete,3,0.0000892660 +BST,случайный,insert,4,0.0191369100 +BST,случайный,find,4,0.0002087170 +BST,случайный,delete,4,0.0001067050 +BST,случайный,insert,5,0.0184276900 +BST,случайный,find,5,0.0002767720 +BST,случайный,delete,5,0.0001067660 +BST,случайный,insert,среднее,0.0180281718 +BST,случайный,find,среднее,0.0001904858 +BST,случайный,delete,среднее,0.0001021684 +LinkedList,отсортированный,insert,1,2.9875078340 +LinkedList,отсортированный,find,1,0.0237300610 +LinkedList,отсортированный,delete,1,0.0111698260 +LinkedList,отсортированный,insert,2,3.0573987940 +LinkedList,отсортированный,find,2,0.0243270360 +LinkedList,отсортированный,delete,2,0.0115366030 +LinkedList,отсортированный,insert,3,2.9641987260 +LinkedList,отсортированный,find,3,0.0236313330 +LinkedList,отсортированный,delete,3,0.0112848510 +LinkedList,отсортированный,insert,4,3.0345914950 +LinkedList,отсортированный,find,4,0.0240271220 +LinkedList,отсортированный,delete,4,0.0112117310 +LinkedList,отсортированный,insert,5,2.9481954700 +LinkedList,отсортированный,find,5,0.0239006100 +LinkedList,отсортированный,delete,5,0.0110857710 +LinkedList,отсортированный,insert,среднее,2.9983784638 +LinkedList,отсортированный,find,среднее,0.0239232324 +LinkedList,отсортированный,delete,среднее,0.0112577564 +HashTable,отсортированный,insert,1,0.1997087560 +HashTable,отсортированный,find,1,0.0017550400 +HashTable,отсортированный,delete,1,0.0008407980 +HashTable,отсортированный,insert,2,0.1968675190 +HashTable,отсортированный,find,2,0.0019886760 +HashTable,отсортированный,delete,2,0.0008920910 +HashTable,отсортированный,insert,3,0.1907563580 +HashTable,отсортированный,find,3,0.0018447440 +HashTable,отсортированный,delete,3,0.0008684640 +HashTable,отсортированный,insert,4,0.2625327630 +HashTable,отсортированный,find,4,0.0016053140 +HashTable,отсортированный,delete,4,0.0008098670 +HashTable,отсортированный,insert,5,0.1936840590 +HashTable,отсортированный,find,5,0.0019015160 +HashTable,отсортированный,delete,5,0.0009053780 +HashTable,отсортированный,insert,среднее,0.2087098910 +HashTable,отсортированный,find,среднее,0.0018190580 +HashTable,отсортированный,delete,среднее,0.0008633196 +BST,отсортированный,insert,1,4.2195800190 +BST,отсортированный,find,1,0.0389314570 +BST,отсортированный,delete,1,0.0190308920 +BST,отсортированный,insert,2,4.1356184250 +BST,отсортированный,find,2,0.0383339310 +BST,отсортированный,delete,2,0.0194247740 +BST,отсортированный,insert,3,4.1204731890 +BST,отсортированный,find,3,0.0388593320 +BST,отсортированный,delete,3,0.0215428460 +BST,отсортированный,insert,4,4.2120902370 +BST,отсортированный,find,4,0.0378190250 +BST,отсортированный,delete,4,0.0188528460 +BST,отсортированный,insert,5,4.1304951260 +BST,отсортированный,find,5,0.0359927840 +BST,отсортированный,delete,5,0.0179617110 +BST,отсортированный,insert,среднее,4.1636513992 +BST,отсортированный,find,среднее,0.0379873058 +BST,отсортированный,delete,среднее,0.0193626138 diff --git a/BolonkinNM/Task 1/docs/report.md b/BolonkinNM/Task 1/docs/report.md new file mode 100644 index 0000000..0a757c7 --- /dev/null +++ b/BolonkinNM/Task 1/docs/report.md @@ -0,0 +1,101 @@ +# Отчёт по заданию 1 — структуры данных + +## Цель работы + +Реализовать три структуры данных с нуля в процедурном стиле: + +- связный список; +- хеш-таблицу; +- двоичное дерево поиска. + +Также были выполнены измерения времени для операций `insert`, `find`, `delete` и построены графики по результатам эксперимента. + +## Реализованные структуры + +### Связный список + +Узел хранится как словарь: + +```python +{"name": "Имя", "phone": "123", "next": None} +``` + +### Хеш-таблица + +Хранится как список бакетов фиксированной длины, где каждый бакет — голова связного списка или `None`. + +### Двоичное дерево поиска + +Узел хранится как словарь: + +```python +{"name": "Имя", "phone": "123", "left": None, "right": None} +``` + +Для BST использованы итеративные операции, чтобы корректно работать и на отсортированных данных. + +## Методика эксперимента + +- Количество записей: `N = 10000` +- Режимы данных: + - случайный порядок; + - отсортированный порядок. +- Каждое измерение повторялось **5 раз**. +- В CSV сохранены: + - все отдельные замеры; + - среднее время для каждой операции, структуры и режима. + +Операции: + +- вставка всех записей; +- поиск 100 существующих и 10 отсутствующих имён; +- удаление 50 случайных имён. + +## Графики + +![insert](data/insert.png) + +![find](data/find.png) + +![delete](data/delete.png) + +## Средние результаты + +| Режим | Операция | LinkedList | HashTable | BST | Лучший результат | +|---|---:|---:|---:|---:|---| +| случайный | insert | 3.949566 | 0.208964 | 0.018028 | BST | +| случайный | find | 0.033693 | 0.001534 | 0.000190 | BST | +| случайный | delete | 0.016076 | 0.000727 | 0.000102 | BST | +| отсортированный | insert | 2.998378 | 0.208710 | 4.163651 | HashTable | +| отсортированный | find | 0.023923 | 0.001819 | 0.037987 | HashTable | +| отсортированный | delete | 0.011258 | 0.000863 | 0.019363 | HashTable | + +## Анализ результатов + +### Влияние порядка входных данных на BST + +На случайных данных BST работает значительно быстрее, чем на отсортированных. Это связано с тем, что при случайной вставке дерево остаётся ближе к сбалансированному состоянию. + +На отсортированных данных дерево вырождается в цепочку, поэтому вставка становится медленной, а поиск и удаление тоже деградируют по времени. + +### Почему хеш-таблица почти не чувствительна к порядку + +Хеш-таблица распределяет элементы по бакетам через хеш-функцию, поэтому сам порядок входа почти не влияет на скорость. Влияние может появляться только из-за коллизий, но в целом поведение остаётся близким к постоянному времени. + +### Почему связный список всегда медленен при поиске + +Поиск в связном списке выполняется последовательным просмотром элементов. Поэтому при большом количестве записей приходится проходить много узлов, и операция остаётся линейной по времени. + +### Как удаление работает в каждой структуре + +- В связном списке нужно сначала найти нужный узел, затем переназначить ссылку. +- В хеш-таблице сначала выбирается бакет, затем удаление выполняется внутри короткой цепочки. +- В BST удаление зависит от числа потомков: если потомок один или ноль, операция простая; если два — нужно найти преемника. + +## Вывод + +Для частых вставок и особенно частого поиска в реальной задаче чаще всего лучше подходит **хеш-таблица**. + +Если важно получать данные в отсортированном виде, удобнее использовать **BST**. + +**Связный список** подходит для маленьких объёмов данных или очень простых сценариев, но при большом числе записей он проигрывает по скорости поиска. diff --git a/BolonkinNM/Task 1/experiments.py b/BolonkinNM/Task 1/experiments.py new file mode 100644 index 0000000..f5face1 --- /dev/null +++ b/BolonkinNM/Task 1/experiments.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import csv +import random +import time +from pathlib import Path +from typing import Dict, List, Tuple + +from linked_list import ll_insert, ll_find, ll_delete +from hash_table import ht_insert, ht_find, ht_delete +from bst import bst_insert, bst_find, bst_delete +from utils import generate_records, prepare_records_variants + + +Record = Tuple[str, str] + + +def make_missing_names(count: int = 10) -> List[str]: + return [f"None_{i}" for i in range(count)] + + +def pick_existing_names(records: List[Record], count: int, seed: int = 42) -> List[str]: + rng = random.Random(seed) + unique_names = list(dict.fromkeys(name for name, _ in records)) + if len(unique_names) < count: + raise ValueError(f"Not enough unique names: need {count}, got {len(unique_names)}") + return rng.sample(unique_names, count) + + +def pick_delete_names(records: List[Record], count: int = 50, seed: int = 43) -> List[str]: + rng = random.Random(seed) + unique_names = list(dict.fromkeys(name for name, _ in records)) + if len(unique_names) < count: + raise ValueError(f"Not enough unique names: need {count}, got {len(unique_names)}") + return rng.sample(unique_names, count) + + +def build_structure(structure_name: str, records: List[Record], buckets_count: int = 2048): + if structure_name == "linked_list": + structure = None + for name, phone in records: + structure = ll_insert(structure, name, phone) + return structure + + if structure_name == "hash_table": + buckets = [None] * buckets_count + for name, phone in records: + buckets = ht_insert(buckets, name, phone) + return buckets + + if structure_name == "bst": + root = None + for name, phone in records: + root = bst_insert(root, name, phone) + return root + + raise ValueError(f"Unknown structure: {structure_name}") + + +def do_find(structure_name: str, structure: object, existing_names: List[str], missing_names: List[str]) -> None: + if structure_name == "linked_list": + for name in existing_names: + ll_find(structure, name) + for name in missing_names: + ll_find(structure, name) + return + + if structure_name == "hash_table": + for name in existing_names: + ht_find(structure, name) + for name in missing_names: + ht_find(structure, name) + return + + if structure_name == "bst": + for name in existing_names: + bst_find(structure, name) + for name in missing_names: + bst_find(structure, name) + return + + raise ValueError(f"Unknown structure: {structure_name}") + + +def do_delete(structure_name: str, structure: object, delete_names: List[str]): + if structure_name == "linked_list": + for name in delete_names: + structure = ll_delete(structure, name) + return structure + + if structure_name == "hash_table": + for name in delete_names: + structure = ht_delete(structure, name) + return structure + + if structure_name == "bst": + for name in delete_names: + structure = bst_delete(structure, name) + return structure + + raise ValueError(f"Unknown structure: {structure_name}") + + +def measure_once(structure_name: str, records: List[Record], buckets_count: int = 2048) -> Dict[str, float]: + existing_names = pick_existing_names(records, 100, seed=42) + missing_names = make_missing_names(10) + delete_names = pick_delete_names(records, 50, seed=43) + + start = time.perf_counter() + structure = build_structure(structure_name, records, buckets_count=buckets_count) + insert_time = time.perf_counter() - start + + start = time.perf_counter() + do_find(structure_name, structure, existing_names, missing_names) + find_time = time.perf_counter() - start + + start = time.perf_counter() + structure = do_delete(structure_name, structure, delete_names) + delete_time = time.perf_counter() - start + + return {"insert": insert_time, "find": find_time, "delete": delete_time} + + +def run_experiments(n: int = 10000, buckets_count: int = 2048, repeats: int = 5): + records = generate_records(n, repeat_names=False) + records_shuffled, records_sorted = prepare_records_variants(records) + + datasets = [ + ("случайный", records_shuffled), + ("отсортированный", records_sorted), + ] + structures = [ + ("LinkedList", "linked_list"), + ("HashTable", "hash_table"), + ("BST", "bst"), + ] + operations = ("insert", "find", "delete") + + rows = [["Структура", "Режим", "Операция", "Замер", "Время (сек)"]] + + for mode_name, dataset_records in datasets: + for human_name, structure_name in structures: + times_by_op = {op: [] for op in operations} + + for attempt in range(1, repeats + 1): + result = measure_once(structure_name, dataset_records, buckets_count=buckets_count) + for op_name in operations: + elapsed = result[op_name] + times_by_op[op_name].append(elapsed) + rows.append([human_name, mode_name, op_name, attempt, f"{elapsed:.10f}"]) + + for op_name in operations: + avg_time = sum(times_by_op[op_name]) / len(times_by_op[op_name]) + rows.append([human_name, mode_name, op_name, "среднее", f"{avg_time:.10f}"]) + + return rows + + +def save_results_csv(rows, filename: str = "results.csv"): + with open(filename, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerows(rows) + + +def main(): + rows = run_experiments(n=10000, buckets_count=2048, repeats=5) + save_results_csv(rows, "results.csv") + print("Saved results.csv") + + +if __name__ == "__main__": + main() diff --git a/BolonkinNM/Task 1/hash_table.py b/BolonkinNM/Task 1/hash_table.py new file mode 100644 index 0000000..9aa720d --- /dev/null +++ b/BolonkinNM/Task 1/hash_table.py @@ -0,0 +1,44 @@ + + +from typing import Any, Dict, List, Optional + +from linked_list import ll_insert, ll_find, ll_delete, ll_list_all + + +Bucket = Optional[Dict[str, Any]] + + +def _hash_name(name: str, buckets_count: int) -> int: + if buckets_count <= 0: + return 0 + return sum(ord(ch) for ch in name) % buckets_count + + +def ht_insert(buckets: List[Bucket], name: str, phone: str) -> List[Bucket]: + if not buckets: + return buckets + index = _hash_name(name, len(buckets)) + buckets[index] = ll_insert(buckets[index], name, phone) + return buckets + + +def ht_find(buckets: List[Bucket], name: str) -> Optional[str]: + if not buckets: + return None + index = _hash_name(name, len(buckets)) + return ll_find(buckets[index], name) + + +def ht_delete(buckets: List[Bucket], name: str) -> List[Bucket]: + if not buckets: + return buckets + index = _hash_name(name, len(buckets)) + buckets[index] = ll_delete(buckets[index], name) + return buckets + + +def ht_list_all(buckets: List[Bucket]) -> List[Dict[str, str]]: + records: List[Dict[str, str]] = [] + for head in buckets: + records.extend(ll_list_all(head)) + return sorted(records, key=lambda x: x["name"]) diff --git a/BolonkinNM/Task 1/linked_list.py b/BolonkinNM/Task 1/linked_list.py new file mode 100644 index 0000000..0260036 --- /dev/null +++ b/BolonkinNM/Task 1/linked_list.py @@ -0,0 +1,73 @@ + + +from typing import Any, Dict, List, Optional + + +Node = Dict[str, Any] + + +def _make_node(name: str, phone: str) -> Node: + return {"name": name, "phone": phone, "next": None} + + +def sort_records(records: List[Dict[str, str]]) -> List[Dict[str, str]]: + + return sorted(records, key=lambda x: x["name"]) + + +def ll_insert(head: Optional[Node], name: str, phone: str) -> Node: + + new_node = _make_node(name, phone) + + if head is None: + return new_node + + current = head + while current is not None: + if current["name"] == name: + current["phone"] = phone + return head + if current["next"] is None: + current["next"] = new_node + return head + current = current["next"] + + return head + + +def ll_find(head: Optional[Node], name: str) -> Optional[str]: + current = head + while current is not None: + if current["name"] == name: + return current["phone"] + current = current["next"] + return None + + +def ll_delete(head: Optional[Node], name: str) -> Optional[Node]: + 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 ll_list_all(head: Optional[Node]) -> List[Dict[str, str]]: + records: List[Dict[str, str]] = [] + current = head + while current is not None: + records.append({"name": current["name"], "phone": current["phone"]}) + current = current["next"] + return sort_records(records) diff --git a/BolonkinNM/Task 1/main.py b/BolonkinNM/Task 1/main.py new file mode 100644 index 0000000..70de618 --- /dev/null +++ b/BolonkinNM/Task 1/main.py @@ -0,0 +1,21 @@ + + +from __future__ import annotations + +import csv +from pathlib import Path + +from experiments import run_experiments, save_results_csv +from plot_results import build_graphs, load_average_results + + +def main(): + rows = run_experiments(n=10000, buckets_count=2048, repeats=5) + save_results_csv(rows, "results.csv") + averaged = load_average_results("results.csv") + build_graphs(averaged, output_dir="docs/data") + print("Done.") + + +if __name__ == "__main__": + main() diff --git a/BolonkinNM/Task 1/plot_results.py b/BolonkinNM/Task 1/plot_results.py new file mode 100644 index 0000000..f4f3b6c --- /dev/null +++ b/BolonkinNM/Task 1/plot_results.py @@ -0,0 +1,60 @@ + + +from __future__ import annotations + +import csv +from collections import defaultdict +from pathlib import Path + +import matplotlib.pyplot as plt + + +def load_average_results(csv_file: str): + results = [] + with open(csv_file, "r", encoding="utf-8") as f: + reader = csv.DictReader(f) + for row in reader: + if row["Замер"] != "среднее": + continue + results.append({ + "structure": row["Структура"], + "mode": row["Режим"], + "operation": row["Операция"], + "time": float(row["Время (сек)"]), + }) + return results + + +def build_graphs(results, output_dir: str = "docs/data"): + output = Path(output_dir) + output.mkdir(parents=True, exist_ok=True) + + grouped = defaultdict(list) + for row in results: + grouped[row["operation"]].append(row) + + for operation in ("insert", "find", "delete"): + rows = grouped[operation] + labels = [f"{r['structure']}\n{r['mode']}" for r in rows] + values = [r["time"] for r in rows] + + plt.figure(figsize=(11, 6)) + plt.bar(labels, values) + plt.title(f"{operation.capitalize()} comparison") + plt.xlabel("Structure / data order") + plt.ylabel("Time, seconds") + plt.xticks(rotation=20) + plt.tight_layout() + filename = output / f"{operation}.png" + plt.savefig(filename, dpi=160) + plt.close() + print(f"Saved {filename}") + + +def main(): + results = load_average_results("results.csv") + build_graphs(results) + + +if __name__ == "__main__": + main() diff --git a/BolonkinNM/Task 1/requirements.txt b/BolonkinNM/Task 1/requirements.txt new file mode 100644 index 0000000..a9006fd --- /dev/null +++ b/BolonkinNM/Task 1/requirements.txt @@ -0,0 +1 @@ +matplotlib>=3.8 diff --git a/BolonkinNM/Task 1/results.csv b/BolonkinNM/Task 1/results.csv new file mode 100644 index 0000000..cc67b71 --- /dev/null +++ b/BolonkinNM/Task 1/results.csv @@ -0,0 +1,109 @@ +Структура,Режим,Операция,Замер,Время (сек) +LinkedList,случайный,insert,1,2.4210275000 +LinkedList,случайный,find,1,0.0214394000 +LinkedList,случайный,delete,1,0.0108667000 +LinkedList,случайный,insert,2,2.4208055000 +LinkedList,случайный,find,2,0.0216110000 +LinkedList,случайный,delete,2,0.0106216000 +LinkedList,случайный,insert,3,2.4210881000 +LinkedList,случайный,find,3,0.0216503000 +LinkedList,случайный,delete,3,0.0106497000 +LinkedList,случайный,insert,4,2.4530798000 +LinkedList,случайный,find,4,0.0222764000 +LinkedList,случайный,delete,4,0.0108350000 +LinkedList,случайный,insert,5,2.4567773000 +LinkedList,случайный,find,5,0.0219400000 +LinkedList,случайный,delete,5,0.0108697000 +LinkedList,случайный,insert,среднее,2.4345556400 +LinkedList,случайный,find,среднее,0.0217834200 +LinkedList,случайный,delete,среднее,0.0107685400 +HashTable,случайный,insert,1,0.1621210000 +HashTable,случайный,find,1,0.0011201000 +HashTable,случайный,delete,1,0.0005854000 +HashTable,случайный,insert,2,0.1732676000 +HashTable,случайный,find,2,0.0011247000 +HashTable,случайный,delete,2,0.0005818000 +HashTable,случайный,insert,3,0.1638609000 +HashTable,случайный,find,3,0.0011355000 +HashTable,случайный,delete,3,0.0005814000 +HashTable,случайный,insert,4,0.1642886000 +HashTable,случайный,find,4,0.0011268000 +HashTable,случайный,delete,4,0.0005785000 +HashTable,случайный,insert,5,0.1640916000 +HashTable,случайный,find,5,0.0011287000 +HashTable,случайный,delete,5,0.0005787000 +HashTable,случайный,insert,среднее,0.1655259400 +HashTable,случайный,find,среднее,0.0011271600 +HashTable,случайный,delete,среднее,0.0005811600 +BST,случайный,insert,1,0.0153754000 +BST,случайный,find,1,0.0001491000 +BST,случайный,delete,1,0.0000786000 +BST,случайный,insert,2,0.0155821000 +BST,случайный,find,2,0.0001453000 +BST,случайный,delete,2,0.0000724000 +BST,случайный,insert,3,0.0151360000 +BST,случайный,find,3,0.0001437000 +BST,случайный,delete,3,0.0000741000 +BST,случайный,insert,4,0.0153703000 +BST,случайный,find,4,0.0001425000 +BST,случайный,delete,4,0.0000715000 +BST,случайный,insert,5,0.0153753000 +BST,случайный,find,5,0.0001455000 +BST,случайный,delete,5,0.0000723000 +BST,случайный,insert,среднее,0.0153678200 +BST,случайный,find,среднее,0.0001452200 +BST,случайный,delete,среднее,0.0000737800 +LinkedList,отсортированный,insert,1,2.5884851000 +LinkedList,отсортированный,find,1,0.0227221000 +LinkedList,отсортированный,delete,1,0.0111309000 +LinkedList,отсортированный,insert,2,2.5095731000 +LinkedList,отсортированный,find,2,0.0217208000 +LinkedList,отсортированный,delete,2,0.0107773000 +LinkedList,отсортированный,insert,3,2.5642096000 +LinkedList,отсортированный,find,3,0.0228242000 +LinkedList,отсортированный,delete,3,0.0115945000 +LinkedList,отсортированный,insert,4,2.7163021000 +LinkedList,отсортированный,find,4,0.0431456000 +LinkedList,отсортированный,delete,4,0.0136020000 +LinkedList,отсортированный,insert,5,2.6891794000 +LinkedList,отсортированный,find,5,0.0217679000 +LinkedList,отсортированный,delete,5,0.0106384000 +LinkedList,отсортированный,insert,среднее,2.6135498600 +LinkedList,отсортированный,find,среднее,0.0264361200 +LinkedList,отсортированный,delete,среднее,0.0115486200 +HashTable,отсортированный,insert,1,0.1524640000 +HashTable,отсортированный,find,1,0.0014973000 +HashTable,отсортированный,delete,1,0.0006991000 +HashTable,отсортированный,insert,2,0.1537592000 +HashTable,отсортированный,find,2,0.0012225000 +HashTable,отсортированный,delete,2,0.0006561000 +HashTable,отсортированный,insert,3,0.1555816000 +HashTable,отсортированный,find,3,0.0012080000 +HashTable,отсортированный,delete,3,0.0006472000 +HashTable,отсортированный,insert,4,0.1546417000 +HashTable,отсортированный,find,4,0.0015017000 +HashTable,отсортированный,delete,4,0.0007512000 +HashTable,отсортированный,insert,5,0.1531659000 +HashTable,отсортированный,find,5,0.0012219000 +HashTable,отсортированный,delete,5,0.0006493000 +HashTable,отсортированный,insert,среднее,0.1539224800 +HashTable,отсортированный,find,среднее,0.0013302800 +HashTable,отсортированный,delete,среднее,0.0006805800 +BST,отсортированный,insert,1,4.5025059000 +BST,отсортированный,find,1,0.0387267000 +BST,отсортированный,delete,1,0.0162161000 +BST,отсортированный,insert,2,4.6704081000 +BST,отсортированный,find,2,0.0435012000 +BST,отсортированный,delete,2,0.0203211000 +BST,отсортированный,insert,3,6.2192950000 +BST,отсортированный,find,3,0.0578654000 +BST,отсортированный,delete,3,0.0327529000 +BST,отсортированный,insert,4,4.7844525000 +BST,отсортированный,find,4,0.0380228000 +BST,отсортированный,delete,4,0.0159740000 +BST,отсортированный,insert,5,4.4861403000 +BST,отсортированный,find,5,0.0382484000 +BST,отсортированный,delete,5,0.0159402000 +BST,отсортированный,insert,среднее,4.9325603600 +BST,отсортированный,find,среднее,0.0432729000 +BST,отсортированный,delete,среднее,0.0202408600 diff --git a/BolonkinNM/Task 1/utils.py b/BolonkinNM/Task 1/utils.py new file mode 100644 index 0000000..0fcb993 --- /dev/null +++ b/BolonkinNM/Task 1/utils.py @@ -0,0 +1,35 @@ +import random +from typing import List, Tuple + + +Record = Tuple[str, str] + + +def generate_records(n: int, repeat_names: bool = False, seed: int = 42) -> List[Record]: + rng = random.Random(seed) + records: List[Record] = [] + + if repeat_names: + name_pool = [ + "User_Alex", "User_Bob", "User_Cat", "User_Dan", "User_Eva", + "User_Fox", "User_Geo", "User_Hen", "User_Ira", "User_Leo", + ] + for _ in range(n): + name = rng.choice(name_pool) + phone = f"{rng.randint(1000000000, 9999999999)}" + records.append((name, phone)) + else: + for i in range(n): + name = f"User_{i:05d}" + phone = f"{1000000000 + i}" + records.append((name, phone)) + + return records + + +def prepare_records_variants(records: List[Record], seed: int = 42): + rng = random.Random(seed) + records_shuffled = list(records) + rng.shuffle(records_shuffled) + records_sorted = sorted(records, key=lambda x: x[0]) + return records_shuffled, records_sorted diff --git a/BolonkinNM/Task 1/график.png b/BolonkinNM/Task 1/график.png new file mode 100644 index 0000000..448942a Binary files /dev/null and b/BolonkinNM/Task 1/график.png differ diff --git a/BolonkinNM/Task 1/отчет.docx b/BolonkinNM/Task 1/отчет.docx new file mode 100644 index 0000000..3a40fa2 Binary files /dev/null and b/BolonkinNM/Task 1/отчет.docx differ