{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3701053f-41f9-464d-a44f-cbda38c1caf7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ\n",
      "Телефонный справочник - 10000 записей\n",
      "\n",
      " Результаты будут сохранены в: C:\\Users\\weron\\experiment_results.csv\n",
      "   Сгенерировано 10000 записей\n",
      "\n",
      "2. Тестирование режима: случайный\n",
      "\n",
      "   LINKED_LIST:\n",
      "      Вставка 10000 записей\n",
      "      Время: 9.2970 сек (среднее)\n",
      "      Поиск 110 записей\n",
      "      Время: 0.0946 сек (среднее)\n",
      "      Удаление 50 записей\n",
      "      Время: 0.0000 сек (среднее)\n",
      "\n",
      "   HASH_TABLE:\n",
      "      Вставка 10000 записей\n",
      "      Время: 0.4810 сек (среднее)\n",
      "      Поиск 110 записей\n",
      "      Время: 0.0039 сек (среднее)\n",
      "      Удаление 50 записей\n",
      "      Время: 0.0030 сек (среднее)\n",
      "\n",
      "   BST:\n",
      "      Вставка 10000 записей\n",
      "      Время: 0.0490 сек (среднее)\n",
      "      Поиск 110 записей\n",
      "      Время: 0.0004 сек (среднее)\n",
      "      Удаление 50 записей\n",
      "      Время: 0.1141 сек (среднее)\n",
      "\n",
      "2. Тестирование режима: отсортированный\n",
      "\n",
      "   LINKED_LIST:\n",
      "      Вставка 10000 записей\n",
      "      Время: 9.2504 сек (среднее)\n",
      "      Поиск 110 записей\n",
      "      Время: 0.1115 сек (среднее)\n",
      "      Удаление 50 записей\n",
      "      Время: 0.0000 сек (среднее)\n",
      "\n",
      "   HASH_TABLE:\n",
      "      Вставка 10000 записей\n",
      "      Время: 0.4928 сек (среднее)\n",
      "      Поиск 110 записей\n",
      "      Время: 0.0061 сек (среднее)\n",
      "      Удаление 50 записей\n",
      "      Время: 0.0029 сек (среднее)\n",
      "\n",
      "   BST:\n",
      "      Вставка 10000 записей\n",
      "      Время: 22.1688 сек (среднее)\n",
      "      Поиск 110 записей\n",
      "      Время: 0.1297 сек (среднее)\n",
      "      Удаление 50 записей\n",
      "      Время: 0.1115 сек (среднее)\n",
      "\n",
      "3. Сохранение результатов\n",
      "   Результаты сохранены в: C:\\Users\\weron\\experiment_results.csv\n",
      "СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ\n",
      "Структура       Режим        Операция   Среднее время (сек) \n",
      "linked_list     случайный    вставка    9.296975            \n",
      "linked_list     случайный    поиск      0.094569            \n",
      "linked_list     случайный    удаление   0.000022            \n",
      "hash_table      случайный    вставка    0.481027            \n",
      "hash_table      случайный    поиск      0.003911            \n",
      "hash_table      случайный    удаление   0.003046            \n",
      "bst             случайный    вставка    0.049011            \n",
      "bst             случайный    поиск      0.000368            \n",
      "bst             случайный    удаление   0.114051            \n",
      "linked_list     отсортированный вставка    9.250436            \n",
      "linked_list     отсортированный поиск      0.111506            \n",
      "linked_list     отсортированный удаление   0.000018            \n",
      "hash_table      отсортированный вставка    0.492765            \n",
      "hash_table      отсортированный поиск      0.006051            \n",
      "hash_table      отсортированный удаление   0.002869            \n",
      "bst             отсортированный вставка    22.168779           \n",
      "bst             отсортированный поиск      0.129713            \n",
      "bst             отсортированный удаление   0.111534            \n",
      "\n",
      "4. Построение графиков\n",
      "   Графики сохранены в: C:\\Users\\weron\\performance_graphs.png\n",
      "ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!\n",
      "\n",
      " СОЗДАННЫЕ ФАЙЛЫ:\n",
      "    Данные: C:\\Users\\weron\\experiment_results.csv\n",
      "    Графики: C:\\Users\\weron\\performance_graphs.png\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import random\n",
    "import csv\n",
    "import os\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import sys\n",
    "sys.setrecursionlimit(20000) \n",
    "#Linked List Phone Book:\n",
    "\n",
    "def ll_insert(head, name, phone):\n",
    "    new_node = {'name': name, 'phone' : phone, 'next': None}\n",
    "    if head is None:\n",
    "        return new_node\n",
    "    if head['name'] == name:\n",
    "        head['phone'] = phone\n",
    "        return head\n",
    "    current = head \n",
    "    while current['next'] is not None:\n",
    "        if current['next']['name'] == name:\n",
    "            current['next']['phone'] = phone\n",
    "            return head\n",
    "        current = current['next']\n",
    "    current['next'] = new_node\n",
    "    return head \n",
    "def ll_find(head, name):\n",
    "    current = head\n",
    "    while current != None:\n",
    "        if current['name']==name:\n",
    "            return current['phone']\n",
    "        current = current['next']\n",
    "    return None\n",
    "def ll_delete(head, name):\n",
    "    if head is None:\n",
    "        return None\n",
    "    if head['name'] == name:\n",
    "        return head['next']\n",
    "    current = head    \n",
    "    while current['next'] is not None:\n",
    "        if current['next']['name'] == name:\n",
    "            current['next']==current['next']['next']\n",
    "            return head\n",
    "        current=current['next']\n",
    "    return head\n",
    "def ll_list_all(head): \n",
    "    records= []\n",
    "    current = head \n",
    "    while current is not None:\n",
    "        records.append({'name': current['name'], 'phone': current['phone']})\n",
    "        current = current['next']\n",
    "    records.sort(key=lambda x: x['name'])\n",
    "    return records \n",
    "def ll_print_all(head):\n",
    "    records = ll_list_all(head)\n",
    "    for record in records:\n",
    "        print(f\"{record['name']}: {record['phone']}\")\n",
    "\n",
    "#Hash Function:\n",
    "\n",
    "def hash_function(name, table_size):\n",
    "    return sum(ord(c) for c in name) % table_size\n",
    "\n",
    "\n",
    "def ht_create(size=1000):\n",
    "    return [None] * size\n",
    "\n",
    "\n",
    "def ht_insert(buckets, name, phone):\n",
    "    size = len(buckets)\n",
    "    index = hash_function(name, size)\n",
    "    buckets[index] = ll_insert(buckets[index], name, phone)\n",
    "\n",
    "\n",
    "def ht_find(buckets, name):\n",
    "    size = len(buckets)\n",
    "    index = hash_function(name, size)\n",
    "    return ll_find(buckets[index], name)\n",
    "\n",
    "\n",
    "def ht_delete(buckets, name):\n",
    "    size = len(buckets)\n",
    "    index = hash_function(name, size)\n",
    "    buckets[index] = ll_delete(buckets[index], name)\n",
    "\n",
    "\n",
    "def ht_list_all(buckets):\n",
    "    records = []\n",
    "    for bucket in buckets:\n",
    "        current = bucket\n",
    "        while current is not None:\n",
    "            records.append((current['name'], current['phone']))\n",
    "            current = current['next']\n",
    "    records.sort(key=lambda x: x[0])\n",
    "    return records\n",
    "\n",
    "#Tree function:\n",
    "\n",
    "def bst_insert(root, name, phone):\n",
    "    \n",
    "    if root is None:\n",
    "        return {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
    "    \n",
    "    if name < root['name']:\n",
    "        root['left'] = bst_insert(root['left'], name, phone)\n",
    "    elif name > root['name']:\n",
    "        root['right'] = bst_insert(root['right'], name, phone)\n",
    "    else:\n",
    "        root['phone'] = phone\n",
    "    \n",
    "    return root\n",
    "\n",
    "\n",
    "def bst_find(root, name):\n",
    "    \n",
    "    current = root\n",
    "    while current is not None:\n",
    "        if name == current['name']:\n",
    "            return current['phone']\n",
    "        elif name < current['name']:\n",
    "            current = current['left']\n",
    "        else:\n",
    "            current = current['right']\n",
    "    return None\n",
    "\n",
    "\n",
    "def bst_find_min(node):\n",
    "    \n",
    "    current = node\n",
    "    while current['left'] is not None:\n",
    "        current = current['left']\n",
    "    return current\n",
    "\n",
    "\n",
    "def bst_delete(root, name):\n",
    "    \n",
    "    if root is None:\n",
    "        return None\n",
    "    \n",
    "    if name < root['name']:\n",
    "        root['left'] = bst_delete(root['left'], name)\n",
    "    elif name > root['name']:\n",
    "        root['right'] = bst_delete(root['right'], name)\n",
    "    else:\n",
    "        if root['left'] is None:\n",
    "            return root['right']\n",
    "        elif root['right'] is None:\n",
    "            return root['left']\n",
    "        \n",
    "        min_node = bst_find_min(root['right'])\n",
    "        root['name'] = min_node['name']\n",
    "        root['phone'] = min_node['phone']\n",
    "        root['right'] = bst_delete(root['right'], min_node['name'])\n",
    "    \n",
    "    return root\n",
    "\n",
    "\n",
    "def bst_list_all(root):\n",
    "    \n",
    "    records = []\n",
    "    \n",
    "    def inorder_traversal(node):\n",
    "        if node is not None:\n",
    "            inorder_traversal(node['left'])\n",
    "            records.append((node['name'], node['phone']))\n",
    "            inorder_traversal(node['right'])\n",
    "    \n",
    "    inorder_traversal(root)\n",
    "    return records\n",
    "\n",
    "#Experemental part \n",
    "#1. Test data generation \n",
    "\n",
    "def generate_records(count=10000):\n",
    "    \n",
    "    records = []\n",
    "    for i in range(count):\n",
    "        name = f\"User_{i:05d}\"\n",
    "        phone = f\"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}\"\n",
    "        records.append((name, phone))\n",
    "    \n",
    "    shuffled = records.copy()\n",
    "    random.shuffle(shuffled)\n",
    "    sorted_records = sorted(records, key=lambda x: x[0])\n",
    "    \n",
    "    return shuffled, sorted_records\n",
    "\n",
    "#2. Timing\n",
    "\n",
    "def measure_insertion(structure_name, records):\n",
    "    \n",
    "    times = []\n",
    "    filled_structure = None\n",
    "    \n",
    "    for run in range(5):\n",
    "        if structure_name == \"linked_list\":\n",
    "            structure = None\n",
    "        elif structure_name == \"hash_table\":\n",
    "            structure = ht_create(1000)\n",
    "        elif structure_name == \"bst\":\n",
    "            structure = None\n",
    "        \n",
    "        start = time.perf_counter()\n",
    "        \n",
    "        for name, phone in records:\n",
    "            if structure_name == \"linked_list\":\n",
    "                structure = ll_insert(structure, name, phone)\n",
    "            elif structure_name == \"hash_table\":\n",
    "                ht_insert(structure, name, phone)\n",
    "            elif structure_name == \"bst\":\n",
    "                structure = bst_insert(structure, name, phone)\n",
    "        \n",
    "        end = time.perf_counter()\n",
    "        times.append(end - start)\n",
    "        \n",
    "        if run == 4:\n",
    "            filled_structure = structure\n",
    "    \n",
    "    return times, filled_structure\n",
    "\n",
    "\n",
    "def measure_search(structure_name, structure, search_names):\n",
    "    \n",
    "    times = []\n",
    "    \n",
    "    for run in range(5):\n",
    "        start = time.perf_counter()\n",
    "        \n",
    "        for name in search_names:\n",
    "            if structure_name == \"linked_list\":\n",
    "                ll_find(structure, name)\n",
    "            elif structure_name == \"hash_table\":\n",
    "                ht_find(structure, name)\n",
    "            elif structure_name == \"bst\":\n",
    "                bst_find(structure, name)\n",
    "        \n",
    "        end = time.perf_counter()\n",
    "        times.append(end - start)\n",
    "    \n",
    "    return times\n",
    "\n",
    "\n",
    "def measure_deletion(structure_name, original_structure, delete_names):\n",
    "    \n",
    "    times = []\n",
    "    \n",
    "    for run in range(5):\n",
    "        if structure_name == \"linked_list\":\n",
    "            all_records = ll_list_all(original_structure)\n",
    "            test_structure = None\n",
    "            for name, phone in all_records:\n",
    "                test_structure = ll_insert(test_structure, name, phone)\n",
    "        \n",
    "        elif structure_name == \"hash_table\":\n",
    "            all_records = ht_list_all(original_structure)\n",
    "            test_structure = ht_create(1000)\n",
    "            for name, phone in all_records:\n",
    "                ht_insert(test_structure, name, phone)\n",
    "        \n",
    "        elif structure_name == \"bst\":\n",
    "            all_records = bst_list_all(original_structure)\n",
    "            test_structure = None\n",
    "            for name, phone in all_records:\n",
    "                test_structure = bst_insert(test_structure, name, phone)\n",
    "        \n",
    "        start = time.perf_counter()\n",
    "        \n",
    "        for name in delete_names:\n",
    "            if structure_name == \"linked_list\":\n",
    "                test_structure = ll_delete(test_structure, name)\n",
    "            elif structure_name == \"hash_table\":\n",
    "                ht_delete(test_structure, name)\n",
    "            elif structure_name == \"bst\":\n",
    "                test_structure = bst_delete(test_structure, name)\n",
    "        \n",
    "        end = time.perf_counter()\n",
    "        times.append(end - start)\n",
    "    \n",
    "    return times\n",
    "#3. Launch and save results\n",
    "\n",
    "def run_experiment():\n",
    "    \n",
    "    current_dir = os.getcwd()\n",
    "    docs_dir = current_dir\n",
    "    csv_file = os.path.join(docs_dir, \"experiment_results.csv\")\n",
    "    \n",
    "    print(\"ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ\")\n",
    "    print(\"Телефонный справочник - 10000 записей\")\n",
    "    print(f\"\\n Результаты будут сохранены в: {csv_file}\")\n",
    "    \n",
    "    shuffled_records, sorted_records = generate_records(10000)\n",
    "    print(f\"   Сгенерировано 10000 записей\")\n",
    "    \n",
    "    existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]\n",
    "    nonexisting_names = [f\"NotExist_{i}\" for i in range(10)]\n",
    "    search_names = existing_names + nonexisting_names\n",
    "    delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]\n",
    "    \n",
    "    results = [[\"Структура\", \"Режим\", \"Операция\", \n",
    "                \"Замер1(с)\", \"Замер2(с)\", \"Замер3(с)\", \"Замер4(с)\", \"Замер5(с)\", \n",
    "                \"Среднее(с)\"]]\n",
    "    \n",
    "    for mode_name, records in [(\"случайный\", shuffled_records), \n",
    "                                (\"отсортированный\", sorted_records)]:\n",
    "        \n",
    "        print(f\"\\n2. Тестирование режима: {mode_name}\")\n",
    "        \n",
    "        for struct_name in [\"linked_list\", \"hash_table\", \"bst\"]:\n",
    "            print(f\"\\n   {struct_name.upper()}:\")\n",
    "            \n",
    "            print(\"      Вставка 10000 записей\")\n",
    "            insert_times, filled_struct = measure_insertion(struct_name, records)\n",
    "            avg_insert = sum(insert_times) / 5\n",
    "            print(f\"      Время: {avg_insert:.4f} сек (среднее)\")\n",
    "            \n",
    "            print(\"      Поиск 110 записей\")\n",
    "            search_times = measure_search(struct_name, filled_struct, search_names)\n",
    "            avg_search = sum(search_times) / 5\n",
    "            print(f\"      Время: {avg_search:.4f} сек (среднее)\")\n",
    "            \n",
    "            print(\"      Удаление 50 записей\")\n",
    "            delete_times = measure_deletion(struct_name, filled_struct, delete_names)\n",
    "            avg_delete = sum(delete_times) / 5\n",
    "            print(f\"      Время: {avg_delete:.4f} сек (среднее)\")\n",
    "            \n",
    "            results.append([struct_name, mode_name, \"вставка\"] + insert_times + [avg_insert])\n",
    "            results.append([struct_name, mode_name, \"поиск\"] + search_times + [avg_search])\n",
    "            results.append([struct_name, mode_name, \"удаление\"] + delete_times + [avg_delete])\n",
    "    \n",
    "    print(\"\\n3. Сохранение результатов\")\n",
    "    with open(csv_file, \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
    "        writer = csv.writer(f)\n",
    "        writer.writerows(results)\n",
    "    print(f\"   Результаты сохранены в: {csv_file}\")\n",
    "    \n",
    "    print(\"СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ\")\n",
    "    print(f\"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}\")\n",
    "    \n",
    "    for row in results[1:]:\n",
    "        struct, mode, op, t1, t2, t3, t4, t5, avg = row\n",
    "        print(f\"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}\")\n",
    "    \n",
    "    return results, docs_dir\n",
    "\n",
    "#4. Graphics\n",
    "\n",
    "def create_graphs(results, docs_dir):\n",
    "    \n",
    "    print(\"\\n4. Построение графиков\")\n",
    "    \n",
    "    data = {}\n",
    "    for row in results[1:]:\n",
    "        struct = row[0]\n",
    "        mode = row[1]\n",
    "        op = row[2]\n",
    "        avg = row[8]\n",
    "        \n",
    "        if struct not in data:\n",
    "            data[struct] = {}\n",
    "        if mode not in data[struct]:\n",
    "            data[struct][mode] = {}\n",
    "        data[struct][mode][op] = avg\n",
    "    \n",
    "    \n",
    "    struct_labels = {\n",
    "        'linked_list': 'LinkedList',\n",
    "        'hash_table': 'HashTable',\n",
    "        'bst': 'BST'\n",
    "    }\n",
    "    \n",
    "    \n",
    "    colors = {\n",
    "        'linked_list': '#8b00ff',  \n",
    "        'hash_table': '#81d8d0',   \n",
    "        'bst': '#000000'            \n",
    "    }\n",
    "    \n",
    "    \n",
    "    fig, axes = plt.subplots(1, 3, figsize=(15, 6))\n",
    "    fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')\n",
    "    \n",
    "    operations = ['вставка', 'поиск', 'удаление']\n",
    "    operation_titles = ['Вставка\\n(10000 записей)', 'Поиск\\n(110 запросов)', 'Удаление\\n(50 записей)']\n",
    "    modes = ['случайный', 'отсортированный']\n",
    "    mode_labels = ['Случайный', 'Отсортированный']\n",
    "    \n",
    "    for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):\n",
    "        ax = axes[idx]\n",
    "        \n",
    "        # Позиции для групп столбцов\n",
    "        x = np.arange(len(modes))  # [0, 1]\n",
    "        width = 0.3  # ширина одного столбца\n",
    "        multiplier = 0\n",
    "        \n",
    "        for struct in ['linked_list', 'hash_table', 'bst']:\n",
    "            values = [data[struct][mode][op] for mode in modes]\n",
    "            offset = width * multiplier\n",
    "            bars = ax.bar(x + offset, values, width, \n",
    "                         label=struct_labels[struct], \n",
    "                         color=colors[struct],\n",
    "                         edgecolor='black', linewidth=0.5)\n",
    "            \n",
    "            \n",
    "            for bar, val in zip(bars, values):\n",
    "                if val < 0.01:\n",
    "                    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05, \n",
    "                           f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)\n",
    "                else:\n",
    "                    ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02, \n",
    "                           f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)\n",
    "            \n",
    "            multiplier += 1\n",
    "        \n",
    "        \n",
    "        ax.set_title(op_title, fontsize=12, fontweight='bold')\n",
    "        ax.set_ylabel('Время (секунды)', fontsize=10)\n",
    "        ax.set_xlabel('Режим данных', fontsize=10)\n",
    "        ax.set_xticks(x + width)\n",
    "        ax.set_xticklabels(mode_labels)\n",
    "        ax.legend(loc='upper left', fontsize=8)\n",
    "        ax.grid(True, alpha=0.3, axis='y')\n",
    "        \n",
    "        \n",
    "        all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]\n",
    "        if max(all_values) / min(all_values) > 100:\n",
    "            ax.set_yscale('log')\n",
    "            ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)\n",
    "    \n",
    "    plt.tight_layout()\n",
    "    graph_path = os.path.join(docs_dir, \"performance_graphs.png\")\n",
    "    plt.savefig(graph_path, dpi=150, bbox_inches='tight')\n",
    "    plt.close()\n",
    "    print(f\"   Графики сохранены в: {graph_path}\")\n",
    "    \n",
    "    return graph_path\n",
    "\n",
    "#5. Main program\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    \n",
    "    results, docs_dir = run_experiment()\n",
    "    \n",
    "    \n",
    "    try:\n",
    "        graph_file = create_graphs(results, docs_dir)\n",
    "        \n",
    "        print(\"ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!\")\n",
    "        print(\"\\n СОЗДАННЫЕ ФАЙЛЫ:\")\n",
    "        print(f\"    Данные: {os.path.join(docs_dir, 'experiment_results.csv')}\")\n",
    "        print(f\"    Графики: {graph_file}\")\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"\\n Ошибка при построении графиков: {e}\")\n",
    "        print(\"   Убедитесь, что установлен matplotlib: pip install matplotlib\")\n",
    "        print(\"ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)\")\n",
    "        print(f\"\\n CSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e02735f2-61dc-484b-b74c-1456f7399863",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:base] *",
   "language": "python",
   "name": "conda-base-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
