491 lines
23 KiB
Plaintext
491 lines
23 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "65b0def8-04cf-4cfd-b4d4-bc862e120497",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Генерация данных...\n",
|
||
"\n",
|
||
"Измерение вставки для Связного списка...\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import time\n",
|
||
"import random\n",
|
||
"import csv\n",
|
||
"import sys\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import numpy as np\n",
|
||
"\n",
|
||
"# Связный список\n",
|
||
"def ll_insert(head, name, phone):\n",
|
||
" current = head\n",
|
||
" prev = None\n",
|
||
" while current is not None:\n",
|
||
" if current['name'] == name:\n",
|
||
" current['phone'] = phone\n",
|
||
" return head\n",
|
||
" prev = current\n",
|
||
" current = current['next']\n",
|
||
" new_node = {'name': name, 'phone': phone, 'next': None}\n",
|
||
" if prev is None:\n",
|
||
" return new_node\n",
|
||
" else:\n",
|
||
" prev['next'] = new_node\n",
|
||
" return head\n",
|
||
"\n",
|
||
"def ll_find(head, name):\n",
|
||
" current = head\n",
|
||
" while current is not None:\n",
|
||
" if current['name'] == name:\n",
|
||
" return current['phone']\n",
|
||
" current = current['next']\n",
|
||
" return None\n",
|
||
"\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",
|
||
"\n",
|
||
"# Хеш-таблица \n",
|
||
"def hash_function(name, size):\n",
|
||
" total = 0\n",
|
||
" for ch in name:\n",
|
||
" total = (total * 31 + ord(ch)) % size\n",
|
||
" return total\n",
|
||
"\n",
|
||
"def ht_create(size=2000):\n",
|
||
" return [None] * size\n",
|
||
"\n",
|
||
"def ht_insert(buckets, name, phone):\n",
|
||
" idx = hash_function(name, len(buckets))\n",
|
||
" buckets[idx] = ll_insert(buckets[idx], name, phone)\n",
|
||
"\n",
|
||
"def ht_find(buckets, name):\n",
|
||
" idx = hash_function(name, len(buckets))\n",
|
||
" return ll_find(buckets[idx], name)\n",
|
||
"\n",
|
||
"def ht_delete(buckets, name):\n",
|
||
" idx = hash_function(name, len(buckets))\n",
|
||
" buckets[idx] = ll_delete(buckets[idx], name)\n",
|
||
"\n",
|
||
"# Двоичное дерево поиска \n",
|
||
"def bst_insert(root, name, phone):\n",
|
||
" new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
|
||
" if root is None:\n",
|
||
" return new_node\n",
|
||
" current = root\n",
|
||
" while True:\n",
|
||
" if name < current['name']:\n",
|
||
" if current['left'] is None:\n",
|
||
" current['left'] = new_node\n",
|
||
" break\n",
|
||
" current = current['left']\n",
|
||
" elif name > current['name']:\n",
|
||
" if current['right'] is None:\n",
|
||
" current['right'] = new_node\n",
|
||
" break\n",
|
||
" current = current['right']\n",
|
||
" else:\n",
|
||
" current['phone'] = phone\n",
|
||
" break\n",
|
||
" return root\n",
|
||
"\n",
|
||
"def bst_find(root, name):\n",
|
||
" current = root\n",
|
||
" while current is not None:\n",
|
||
" if name < current['name']:\n",
|
||
" current = current['left']\n",
|
||
" elif name > current['name']:\n",
|
||
" current = current['right']\n",
|
||
" else:\n",
|
||
" return current['phone']\n",
|
||
" return None\n",
|
||
"\n",
|
||
"def bst_find_min(node):\n",
|
||
" while node['left'] is not None:\n",
|
||
" node = node['left']\n",
|
||
" return node\n",
|
||
"\n",
|
||
"def bst_delete(root, name):\n",
|
||
" parent = None\n",
|
||
" current = root\n",
|
||
" while current is not None and current['name'] != name:\n",
|
||
" parent = current\n",
|
||
" if name < current['name']:\n",
|
||
" current = current['left']\n",
|
||
" else:\n",
|
||
" current = current['right']\n",
|
||
" if current is None:\n",
|
||
" return root\n",
|
||
" \n",
|
||
" if current['left'] is None and current['right'] is None:\n",
|
||
" if parent is None:\n",
|
||
" return None\n",
|
||
" if parent['left'] is current:\n",
|
||
" parent['left'] = None\n",
|
||
" else:\n",
|
||
" parent['right'] = None\n",
|
||
" return root\n",
|
||
" \n",
|
||
" if current['left'] is None:\n",
|
||
" if parent is None:\n",
|
||
" return current['right']\n",
|
||
" if parent['left'] is current:\n",
|
||
" parent['left'] = current['right']\n",
|
||
" else:\n",
|
||
" parent['right'] = current['right']\n",
|
||
" return root\n",
|
||
" if current['right'] is None:\n",
|
||
" if parent is None:\n",
|
||
" return current['left']\n",
|
||
" if parent['left'] is current:\n",
|
||
" parent['left'] = current['left']\n",
|
||
" else:\n",
|
||
" parent['right'] = current['left']\n",
|
||
" return root\n",
|
||
" \n",
|
||
" succ_parent = current\n",
|
||
" succ = current['right']\n",
|
||
" while succ['left'] is not None:\n",
|
||
" succ_parent = succ\n",
|
||
" succ = succ['left']\n",
|
||
" current['name'] = succ['name']\n",
|
||
" current['phone'] = succ['phone']\n",
|
||
" if succ_parent['left'] is succ:\n",
|
||
" succ_parent['left'] = succ['right']\n",
|
||
" else:\n",
|
||
" succ_parent['right'] = succ['right']\n",
|
||
" return root\n",
|
||
"\n",
|
||
"# Генерация данных \n",
|
||
"def generate_records(N):\n",
|
||
" records = []\n",
|
||
" for i in range(N):\n",
|
||
" name = f\"User_{i:05d}\"\n",
|
||
" phone = f\"+7-999-{random.randint(1000000, 9999999)}\"\n",
|
||
" records.append((name, phone))\n",
|
||
" return records\n",
|
||
"\n",
|
||
"# Замеры\n",
|
||
"REPEATS = 5\n",
|
||
"N = 10000\n",
|
||
"\n",
|
||
"def measure_insert(struct, records, repeats=REPEATS):\n",
|
||
" times = []\n",
|
||
" for _ in range(repeats):\n",
|
||
" if struct == 'll':\n",
|
||
" head = None\n",
|
||
" start = time.perf_counter()\n",
|
||
" for name, phone in records:\n",
|
||
" head = ll_insert(head, name, phone)\n",
|
||
" end = time.perf_counter()\n",
|
||
" elif struct == 'ht':\n",
|
||
" buckets = ht_create(2000)\n",
|
||
" start = time.perf_counter()\n",
|
||
" for name, phone in records:\n",
|
||
" ht_insert(buckets, name, phone)\n",
|
||
" end = time.perf_counter()\n",
|
||
" elif struct == 'bst':\n",
|
||
" root = None\n",
|
||
" start = time.perf_counter()\n",
|
||
" for name, phone in records:\n",
|
||
" root = bst_insert(root, name, phone)\n",
|
||
" end = time.perf_counter()\n",
|
||
" times.append(end - start)\n",
|
||
" return times\n",
|
||
"\n",
|
||
"def build_structure(struct, records):\n",
|
||
" if struct == 'll':\n",
|
||
" head = None\n",
|
||
" for name, phone in records:\n",
|
||
" head = ll_insert(head, name, phone)\n",
|
||
" return head\n",
|
||
" elif struct == 'ht':\n",
|
||
" buckets = ht_create(2000)\n",
|
||
" for name, phone in records:\n",
|
||
" ht_insert(buckets, name, phone)\n",
|
||
" return buckets\n",
|
||
" elif struct == 'bst':\n",
|
||
" root = None\n",
|
||
" for name, phone in records:\n",
|
||
" root = bst_insert(root, name, phone)\n",
|
||
" return root\n",
|
||
"\n",
|
||
"def measure_find_on_structure(struct, structure, records, repeats=REPEATS):\n",
|
||
" times = []\n",
|
||
" N = len(records)\n",
|
||
" for _ in range(repeats):\n",
|
||
" indices = random.sample(range(N), 100)\n",
|
||
" exist = [records[i][0] for i in indices]\n",
|
||
" missing = [f\"None_{i}\" for i in range(10)]\n",
|
||
" search = exist + missing\n",
|
||
" start = time.perf_counter()\n",
|
||
" if struct == 'll':\n",
|
||
" for name in search:\n",
|
||
" ll_find(structure, name)\n",
|
||
" elif struct == 'ht':\n",
|
||
" for name in search:\n",
|
||
" ht_find(structure, name)\n",
|
||
" elif struct == 'bst':\n",
|
||
" for name in search:\n",
|
||
" bst_find(structure, name)\n",
|
||
" times.append(time.perf_counter() - start)\n",
|
||
" return times\n",
|
||
"\n",
|
||
"def measure_delete_on_structure(struct, records, repeats=REPEATS):\n",
|
||
" times = []\n",
|
||
" N = len(records)\n",
|
||
" for _ in range(repeats):\n",
|
||
" indices = random.sample(range(N), 50)\n",
|
||
" del_names = [records[i][0] for i in indices]\n",
|
||
" if struct == 'll':\n",
|
||
" head = None\n",
|
||
" for name, phone in records:\n",
|
||
" head = ll_insert(head, name, phone)\n",
|
||
" start = time.perf_counter()\n",
|
||
" for name in del_names:\n",
|
||
" head = ll_delete(head, name)\n",
|
||
" end = time.perf_counter()\n",
|
||
" elif struct == 'ht':\n",
|
||
" buckets = ht_create(2000)\n",
|
||
" for name, phone in records:\n",
|
||
" ht_insert(buckets, name, phone)\n",
|
||
" start = time.perf_counter()\n",
|
||
" for name in del_names:\n",
|
||
" ht_delete(buckets, name)\n",
|
||
" end = time.perf_counter()\n",
|
||
" elif struct == 'bst':\n",
|
||
" root = None\n",
|
||
" for name, phone in records:\n",
|
||
" root = bst_insert(root, name, phone)\n",
|
||
" start = time.perf_counter()\n",
|
||
" for name in del_names:\n",
|
||
" root = bst_delete(root, name)\n",
|
||
" end = time.perf_counter()\n",
|
||
" times.append(end - start)\n",
|
||
" return times\n",
|
||
"\n",
|
||
"# Основная функция \n",
|
||
"def main():\n",
|
||
" print(\"Генерация данных...\")\n",
|
||
" records = generate_records(N)\n",
|
||
" random.shuffle(records) # случайный порядок\n",
|
||
" records_sorted = sorted(records, key=lambda x: x[0]) # отсортированный\n",
|
||
"\n",
|
||
" results = [] # для CSV\n",
|
||
" struct_names = {'ll': 'Связного списка', 'ht': 'Хеш-таблицы', 'bst': 'Двоичного дерева поиска'}\n",
|
||
" mode_names = {'shuffled': 'случайный', 'sorted': 'отсортированный'}\n",
|
||
" op_names = {'insert': 'Вставка всех записей', 'find': 'Поиск записей', 'delete': 'Удаление записей'}\n",
|
||
"\n",
|
||
" # для графиков\n",
|
||
" insert_sh = {} # {struct: [times]}\n",
|
||
" insert_so = {}\n",
|
||
" find_sh = {}\n",
|
||
" find_so = {}\n",
|
||
" delete_sh = {}\n",
|
||
" delete_so = {}\n",
|
||
"\n",
|
||
" # Вставка \n",
|
||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||
" print(f\"\\nИзмерение вставки для {struct_names[struct]}...\")\n",
|
||
" times_sh = measure_insert(struct, records)\n",
|
||
" times_so = measure_insert(struct, records_sorted)\n",
|
||
" insert_sh[struct] = times_sh\n",
|
||
" insert_so[struct] = times_so\n",
|
||
" print(f\" случайный: {[round(t,6) for t in times_sh]}, среднее = {sum(times_sh)/len(times_sh):.6f}\")\n",
|
||
" print(f\" отсортированный: {[round(t,6) for t in times_so]}, среднее = {sum(times_so)/len(times_so):.6f}\")\n",
|
||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['insert'], sum(times_sh)/len(times_sh)] + times_sh)\n",
|
||
" results.append([struct_names[struct], mode_names['sorted'], op_names['insert'], sum(times_so)/len(times_so)] + times_so)\n",
|
||
"\n",
|
||
" # Поиск \n",
|
||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||
" print(f\"\\nПоиск для {struct_names[struct]} на случайных данных...\")\n",
|
||
" structure_sh = build_structure(struct, records)\n",
|
||
" times_find_sh = measure_find_on_structure(struct, structure_sh, records)\n",
|
||
" find_sh[struct] = times_find_sh\n",
|
||
" print(f\" случайный: {[round(t,6) for t in times_find_sh]}, среднее = {sum(times_find_sh)/len(times_find_sh):.6f}\")\n",
|
||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['find'], sum(times_find_sh)/len(times_find_sh)] + times_find_sh)\n",
|
||
"\n",
|
||
" print(f\"Поиск для {struct_names[struct]} на отсортированных данных...\")\n",
|
||
" structure_so = build_structure(struct, records_sorted)\n",
|
||
" times_find_so = measure_find_on_structure(struct, structure_so, records_sorted)\n",
|
||
" find_so[struct] = times_find_so\n",
|
||
" print(f\" отсортированный: {[round(t,6) for t in times_find_so]}, среднее = {sum(times_find_so)/len(times_find_so):.6f}\")\n",
|
||
" results.append([struct_names[struct], mode_names['sorted'], op_names['find'], sum(times_find_so)/len(times_find_so)] + times_find_so)\n",
|
||
"\n",
|
||
" # Удаление \n",
|
||
" for struct in ['ll', 'ht', 'bst']:\n",
|
||
" print(f\"\\nУдаление для {struct_names[struct]} на случайных данных...\")\n",
|
||
" times_del_sh = measure_delete_on_structure(struct, records)\n",
|
||
" delete_sh[struct] = times_del_sh\n",
|
||
" print(f\" случайный: {[round(t,6) for t in times_del_sh]}, среднее = {sum(times_del_sh)/len(times_del_sh):.6f}\")\n",
|
||
" results.append([struct_names[struct], mode_names['shuffled'], op_names['delete'], sum(times_del_sh)/len(times_del_sh)] + times_del_sh)\n",
|
||
"\n",
|
||
" print(f\"Удаление для {struct_names[struct]} на отсортированных данных...\")\n",
|
||
" times_del_so = measure_delete_on_structure(struct, records_sorted)\n",
|
||
" delete_so[struct] = times_del_so\n",
|
||
" print(f\" отсортированный: {[round(t,6) for t in times_del_so]}, среднее = {sum(times_del_so)/len(times_del_so):.6f}\")\n",
|
||
" results.append([struct_names[struct], mode_names['sorted'], op_names['delete'], sum(times_del_so)/len(times_del_so)] + times_del_so)\n",
|
||
"\n",
|
||
" # Сохраняем CSV\n",
|
||
" with open(\"phonebook_results.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
|
||
" writer = csv.writer(f)\n",
|
||
" writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее', 'Замер1', 'Замер2', 'Замер3', 'Замер4', 'Замер5'])\n",
|
||
" writer.writerows(results)\n",
|
||
"\n",
|
||
" # Построение графиков \n",
|
||
" try:\n",
|
||
" # График вставки\n",
|
||
" fig1, ax1 = plt.subplots(figsize=(10,6))\n",
|
||
" x = np.arange(3)\n",
|
||
" width = 0.35\n",
|
||
" means_sh = [sum(insert_sh[s])/len(insert_sh[s]) for s in ['ll','ht','bst']]\n",
|
||
" means_so = [sum(insert_so[s])/len(insert_so[s]) for s in ['ll','ht','bst']]\n",
|
||
" rects1 = ax1.bar(x - width/2, means_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||
" rects2 = ax1.bar(x + width/2, means_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||
" ax1.set_ylabel('Время (сек)')\n",
|
||
" ax1.set_title('Вставка всех записей')\n",
|
||
" ax1.set_xticks(x)\n",
|
||
" ax1.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||
" ax1.legend()\n",
|
||
" ax1.set_yscale('log')\n",
|
||
" for rect in rects1 + rects2:\n",
|
||
" h = rect.get_height()\n",
|
||
" ax1.annotate(f'{h:.3f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||
" plt.tight_layout()\n",
|
||
" plt.savefig('insert_comparison.png')\n",
|
||
" plt.show()\n",
|
||
"\n",
|
||
" # График поиска\n",
|
||
" fig2, ax2 = plt.subplots(figsize=(10,6))\n",
|
||
" means_find_sh = [sum(find_sh[s])/len(find_sh[s]) for s in ['ll','ht','bst']]\n",
|
||
" means_find_so = [sum(find_so[s])/len(find_so[s]) for s in ['ll','ht','bst']]\n",
|
||
" rects1 = ax2.bar(x - width/2, means_find_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||
" rects2 = ax2.bar(x + width/2, means_find_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||
" ax2.set_ylabel('Время (сек)')\n",
|
||
" ax2.set_title('Поиск (100 существующих + 10 отсутствующих)')\n",
|
||
" ax2.set_xticks(x)\n",
|
||
" ax2.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||
" ax2.legend()\n",
|
||
" for rect in rects1 + rects2:\n",
|
||
" h = rect.get_height()\n",
|
||
" ax2.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||
" plt.tight_layout()\n",
|
||
" plt.savefig('find_comparison.png')\n",
|
||
" plt.show()\n",
|
||
"\n",
|
||
" # График удаления \n",
|
||
" fig3, ax3 = plt.subplots(figsize=(10,6))\n",
|
||
" means_del_sh = [sum(delete_sh[s])/len(delete_sh[s]) for s in ['ll','ht','bst']]\n",
|
||
" means_del_so = [sum(delete_so[s])/len(delete_so[s]) for s in ['ll','ht','bst']]\n",
|
||
" rects1 = ax3.bar(x - width/2, means_del_sh, width, label='Случайный порядок', color='skyblue')\n",
|
||
" rects2 = ax3.bar(x + width/2, means_del_so, width, label='Отсортированный порядок', color='salmon')\n",
|
||
" ax3.set_ylabel('Время (сек)')\n",
|
||
" ax3.set_title('Удаление 50 случайных записей')\n",
|
||
" ax3.set_xticks(x)\n",
|
||
" ax3.set_xticklabels(['Связный список', 'Хеш-таблица', 'Двоичное дерево'])\n",
|
||
" ax3.legend()\n",
|
||
" for rect in rects1 + rects2:\n",
|
||
" h = rect.get_height()\n",
|
||
" ax3.annotate(f'{h:.5f}', xy=(rect.get_x()+rect.get_width()/2, h),\n",
|
||
" xytext=(0,3), textcoords=\"offset points\", ha='center', va='bottom', fontsize=8)\n",
|
||
" plt.tight_layout()\n",
|
||
" plt.savefig('delete_comparison.png')\n",
|
||
" plt.show()\n",
|
||
"\n",
|
||
" print(\"Графики сохранены: insert_comparison.png, find_comparison.png, delete_comparison.png\")\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"Не удалось построить графики: {e}\")\n",
|
||
" # Графики замеров\n",
|
||
" try:\n",
|
||
" def plot_attempts(data_sh, data_so, op_name):\n",
|
||
" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||
" # случайный порядок\n",
|
||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||
" times = data_sh[struct]\n",
|
||
" x = range(1, len(times)+1)\n",
|
||
" ax1.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||
" ax1.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||
" ax1.set_xlabel('Номер попытки')\n",
|
||
" ax1.set_ylabel('Время (сек)')\n",
|
||
" ax1.set_title(f'{op_name} – случайный порядок')\n",
|
||
" ax1.legend()\n",
|
||
" ax1.grid(True, linestyle=':', alpha=0.7)\n",
|
||
" # отсортированный порядок\n",
|
||
" for struct, label, color, marker in [('ll','LinkedList','red','o'), ('ht','HashTable','green','s'), ('bst','BST','blue','^')]:\n",
|
||
" times = data_so[struct]\n",
|
||
" x = range(1, len(times)+1)\n",
|
||
" ax2.plot(x, times, marker=marker, color=color, label=label, linestyle='--', linewidth=1)\n",
|
||
" ax2.scatter(x, times, color=color, s=60, zorder=5)\n",
|
||
" ax2.set_xlabel('Номер попытки')\n",
|
||
" ax2.set_ylabel('Время (сек)')\n",
|
||
" ax2.set_title(f'{op_name} – отсортированный порядок')\n",
|
||
" ax2.legend()\n",
|
||
" ax2.grid(True, linestyle=':', alpha=0.7)\n",
|
||
" plt.tight_layout()\n",
|
||
" plt.savefig(f'{op_name}_5attempts.png')\n",
|
||
" plt.show()\n",
|
||
" \n",
|
||
" plot_attempts(insert_sh, insert_so, 'insert')\n",
|
||
" plot_attempts(find_sh, find_so, 'find')\n",
|
||
" plot_attempts(delete_sh, delete_so, 'delete')\n",
|
||
" print(\"Дополнительные графики сохранены: insert_5attempts.png, find_5attempts.png, delete_5attempts.png\")\n",
|
||
" except Exception as e:\n",
|
||
" print(f\"Не удалось построить дополнительные графики: {e}\")\n",
|
||
"\n",
|
||
"if __name__ == \"__main__\":\n",
|
||
" sys.setrecursionlimit(20000)\n",
|
||
" main()\n",
|
||
"\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"id": "cead201d-1150-463f-9ff3-a4bed6f7fc03",
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": []
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3 (ipykernel)",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"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.5"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|