2026-rff_mp/soninrv/docs/data/lab1/phonebook.py
2026-05-25 00:51:26 +03:00

305 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 1. СВЯЗНЫЙ СПИСОК
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
"""Добавить или обновить запись. Возвращает голову списка."""
node = head
while node is not None:
if node['name'] == name:
node['phone'] = phone # обновить
return head
node = node['next']
# Вставка в начало — O(1)
new_node = ll_create_node(name, phone)
new_node['next'] = head
return new_node
def ll_find(head, name):
"""Вернуть телефон или None."""
node = head
while node is not None:
if node['name'] == name:
return node['phone']
node = node['next']
return None
def ll_delete(head, name):
"""Удалить узел, вернуть новую голову."""
if head is None:
return None
if head['name'] == name:
return head['next']
prev, node = head, head['next']
while node is not None:
if node['name'] == name:
prev['next'] = node['next']
return head
prev, node = node, node['next']
return head
def ll_list_all(head):
"""Собрать все записи и вернуть отсортированный список (name, phone)."""
result = []
node = head
while node is not None:
result.append((node['name'], node['phone']))
node = node['next']
result.sort(key=lambda x: x[0])
return result
# 2. ХЕШ-ТАБЛИЦА (цепочки через связный список)
HT_SIZE = 1024 # число корзин (степень двойки)
def ht_create(size=HT_SIZE):
return [None] * size
def _ht_hash(name, size):
h = 5381
for ch in name:
h = ((h << 5) + h) ^ ord(ch)
return h % size
def ht_insert(buckets, name, phone):
idx = _ht_hash(name, len(buckets))
buckets[idx] = ll_insert(buckets[idx], name, phone)
def ht_find(buckets, name):
idx = _ht_hash(name, len(buckets))
return ll_find(buckets[idx], name)
def ht_delete(buckets, name):
idx = _ht_hash(name, len(buckets))
buckets[idx] = ll_delete(buckets[idx], name)
def ht_list_all(buckets):
result = []
for head in buckets:
result.extend(ll_list_all(head))
result.sort(key=lambda x: x[0])
return result
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST)
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
"""Вставить / обновить. Возвращает корень."""
if root is None:
return bst_create_node(name, phone)
if name == root['name']:
root['phone'] = phone
elif name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
else:
root['right'] = bst_insert(root['right'], name, phone)
return root
def bst_find(root, name):
"""Вернуть телефон или None."""
while root is not None:
if name == root['name']:
return root['phone']
elif name < root['name']:
root = root['left']
else:
root = root['right']
return None
def _bst_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']
# Два потомка: заменить минимальным из правого поддерева
successor = _bst_min(root['right'])
root['name'] = successor['name']
root['phone'] = successor['phone']
root['right'] = bst_delete(root['right'], successor['name'])
return root
def bst_list_all(root):
"""Центрированный (in-order) обход → отсортированный список."""
result = []
stack = []
node = root
while stack or node is not None:
while node is not None:
stack.append(node)
node = node['left']
node = stack.pop()
result.append((node['name'], node['phone']))
node = node['right']
return result
"""
Экспериментальная часть: замер производительности трёх структур данных.
"""
import time
import csv
import random
import os
import sys
sys.setrecursionlimit(30000) # BST с отсортированными данными — глубокая рекурсия
# ── Параметры ──────────────────────────────────────────────────────────────
N = 10_000 # размер набора
REPEATS = 5 # повторений каждого замера
SEARCH_N = 100 # запросов на поиск (существующих)
SEARCH_MISS = 10 # запросов на поиск (отсутствующих)
DELETE_N = 50 # удалений
random.seed(42)
# ── Генерация данных ───────────────────────────────────────────────────────
records_sorted = [(f"User_{i:05d}", f"+7-000-{i:07d}") for i in range(N)]
records_shuffled = records_sorted[:]
random.shuffle(records_shuffled)
search_names_hit = [records_sorted[i][0] for i in random.sample(range(N), SEARCH_N)]
search_names_miss = [f"None_{i:04d}" for i in range(SEARCH_MISS)]
search_names = search_names_hit + search_names_miss
delete_names = [records_sorted[i][0] for i in random.sample(range(N), DELETE_N)]
# ── Вспомогательные функции ────────────────────────────────────────────────
def build_ll(records):
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
return head
def build_ht(records):
buckets = ht_create()
for name, phone in records:
ht_insert(buckets, name, phone)
return buckets
def build_bst(records):
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
return root
STRUCTURES = {
'LinkedList': {
'build': build_ll,
'find': ll_find,
'delete': lambda ds, name: ll_delete(ds, name), # возвращает новый head
'list_all': ll_list_all,
'mutable': False, # ll_delete возвращает новую голову
},
'HashTable': {
'build': build_ht,
'find': ht_find,
'delete': lambda ds, name: ht_delete(ds, name), # in-place, returns None
'list_all': ht_list_all,
'mutable': True,
},
'BST': {
'build': build_bst,
'find': bst_find,
'delete': lambda ds, name: bst_delete(ds, name), # возвращает новый корень
'list_all': bst_list_all,
'mutable': False,
},
}
MODES = {
'shuffled': records_shuffled,
'sorted': records_sorted,
}
# ── Замер ──────────────────────────────────────────────────────────────────
def measure(fn, *args, repeats=REPEATS):
times = []
for _ in range(repeats):
t0 = time.perf_counter()
fn(*args)
times.append(time.perf_counter() - t0)
return times
rows = [["structure", "mode", "operation", "run", "time_sec"]]
for struct_name, ops in STRUCTURES.items():
for mode_name, records in MODES.items():
print(f" {struct_name} / {mode_name} ...", flush=True)
# ── А. Вставка ──────────────────────────────────────────────────
insert_times = []
for run in range(REPEATS):
t0 = time.perf_counter()
ds = ops['build'](records)
insert_times.append(time.perf_counter() - t0)
rows.append([struct_name, mode_name, "insert", run + 1, insert_times[-1]])
# Строим структуру один раз для поиска и удаления
ds = ops['build'](records)
# ── Б. Поиск ────────────────────────────────────────────────────
def do_search(ds=ds):
for name in search_names:
ops['find'](ds, name)
search_times = measure(do_search)
for run, t in enumerate(search_times, 1):
rows.append([struct_name, mode_name, "find", run, t])
# ── В. Удаление ─────────────────────────────────────────────────
# Удаление изменяет структуру, поэтому каждый раз пересобираем
delete_times = []
for run in range(REPEATS):
ds2 = ops['build'](records)
t0 = time.perf_counter()
for name in delete_names:
result = ops['delete'](ds2, name)
if result is not None: # ll / bst возвращают новую голову/корень
ds2 = result
delete_times.append(time.perf_counter() - t0)
rows.append([struct_name, mode_name, "delete", run + 1, delete_times[-1]])
print(f" insert avg={sum(insert_times)/REPEATS:.4f}s "
f"find avg={sum(search_times)/REPEATS:.4f}s "
f"delete avg={sum(delete_times)/REPEATS:.4f}s")
with open("results.csv", "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
print("\nРезультаты сохранены в docs/data/results.csv")