368 lines
12 KiB
Python
368 lines
12 KiB
Python
# ============================================================
|
||
# Задание 1 — структуры данных: телефонный справочник
|
||
# Процедурная парадигма (без классов)
|
||
# ============================================================
|
||
|
||
import random
|
||
import time
|
||
import csv
|
||
|
||
# ============================================================
|
||
# 1. СВЯЗНЫЙ СПИСОК
|
||
# ============================================================
|
||
|
||
def ll_new_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']
|
||
# Не нашли — вставляем в начало
|
||
new_node = ll_new_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 = head
|
||
node = 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. ХЕШ-ТАБЛИЦА (на основе связных списков)
|
||
# ============================================================
|
||
|
||
def ht_new(size=1024):
|
||
"""Создать пустую хеш-таблицу (список бакетов)."""
|
||
return [None] * size
|
||
|
||
def ht_hash(buckets, name):
|
||
"""Полиномиальный хеш строки по модулю размера таблицы."""
|
||
h = 0
|
||
for ch in name:
|
||
h = (h * 31 + ord(ch)) % len(buckets)
|
||
return h
|
||
|
||
def ht_insert(buckets, name, phone):
|
||
idx = ht_hash(buckets, name)
|
||
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||
|
||
def ht_find(buckets, name):
|
||
idx = ht_hash(buckets, name)
|
||
return ll_find(buckets[idx], name)
|
||
|
||
def ht_delete(buckets, name):
|
||
idx = ht_hash(buckets, name)
|
||
buckets[idx] = ll_delete(buckets[idx], name)
|
||
|
||
def ht_list_all(buckets):
|
||
result = []
|
||
for bucket in buckets:
|
||
node = bucket
|
||
while node is not None:
|
||
result.append((node['name'], node['phone']))
|
||
node = node['next']
|
||
result.sort(key=lambda x: x[0])
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST)
|
||
# ============================================================
|
||
|
||
def bst_new_node(name, phone):
|
||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||
|
||
def bst_insert(root, name, phone):
|
||
"""Вставить или обновить запись (итеративно). Возвращает новый корень."""
|
||
new_node = bst_new_node(name, phone)
|
||
if root is None:
|
||
return new_node
|
||
node = root
|
||
while True:
|
||
if name < node['name']:
|
||
if node['left'] is None:
|
||
node['left'] = new_node
|
||
break
|
||
node = node['left']
|
||
elif name > node['name']:
|
||
if node['right'] is None:
|
||
node['right'] = new_node
|
||
break
|
||
node = node['right']
|
||
else:
|
||
node['phone'] = phone # обновить
|
||
break
|
||
return root
|
||
|
||
def bst_find(root, name):
|
||
"""Найти телефон. Возвращает телефон или None."""
|
||
while root is not None:
|
||
if name < root['name']:
|
||
root = root['left']
|
||
elif name > root['name']:
|
||
root = root['right']
|
||
else:
|
||
return root['phone']
|
||
return None
|
||
|
||
def _bst_min_node(node):
|
||
"""Найти узел с минимальным ключом в поддереве."""
|
||
while node['left'] is not None:
|
||
node = node['left']
|
||
return node
|
||
|
||
def bst_delete(root, name):
|
||
"""Удалить узел (итеративно). Возвращает новый корень."""
|
||
parent = None
|
||
node = root
|
||
is_left = False
|
||
# Найти узел и его родителя
|
||
while node is not None and node['name'] != name:
|
||
parent = node
|
||
if name < node['name']:
|
||
node = node['left']
|
||
is_left = True
|
||
else:
|
||
node = node['right']
|
||
is_left = False
|
||
if node is None:
|
||
return root # не нашли
|
||
|
||
# Удалить найденный узел
|
||
if node['left'] is None:
|
||
replacement = node['right']
|
||
elif node['right'] is None:
|
||
replacement = node['left']
|
||
else:
|
||
# Два потомка: найти in-order successor
|
||
succ_parent = node
|
||
succ = node['right']
|
||
while succ['left'] is not None:
|
||
succ_parent = succ
|
||
succ = succ['left']
|
||
node['name'] = succ['name']
|
||
node['phone'] = succ['phone']
|
||
# Удалить successor
|
||
if succ_parent is node:
|
||
succ_parent['right'] = succ['right']
|
||
else:
|
||
succ_parent['left'] = succ['right']
|
||
return root
|
||
|
||
if parent is None:
|
||
return replacement
|
||
if is_left:
|
||
parent['left'] = replacement
|
||
else:
|
||
parent['right'] = replacement
|
||
return root
|
||
|
||
def bst_list_all(root):
|
||
"""Центрированный обход (итеративно) — возвращает записи в порядке ключей."""
|
||
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
|
||
|
||
|
||
# ============================================================
|
||
# ГЕНЕРАЦИЯ ТЕСТОВЫХ ДАННЫХ
|
||
# ============================================================
|
||
|
||
def generate_records(n=10000, seed=42):
|
||
rng = random.Random(seed)
|
||
records = []
|
||
for i in range(n):
|
||
name = f"User_{i:05d}"
|
||
phone = f"+7{rng.randint(9000000000, 9999999999)}"
|
||
records.append((name, phone))
|
||
return records
|
||
|
||
|
||
# ============================================================
|
||
# БЕНЧМАРК
|
||
# ============================================================
|
||
|
||
REPEATS = 5
|
||
|
||
def bench_insert(structure_type, records):
|
||
times = []
|
||
for _ in range(REPEATS):
|
||
if structure_type == 'LinkedList':
|
||
head = None
|
||
start = time.perf_counter()
|
||
for name, phone in records:
|
||
head = ll_insert(head, name, phone)
|
||
times.append(time.perf_counter() - start)
|
||
|
||
elif structure_type == 'HashTable':
|
||
buckets = ht_new()
|
||
start = time.perf_counter()
|
||
for name, phone in records:
|
||
ht_insert(buckets, name, phone)
|
||
times.append(time.perf_counter() - start)
|
||
|
||
elif structure_type == 'BST':
|
||
root = None
|
||
start = time.perf_counter()
|
||
for name, phone in records:
|
||
root = bst_insert(root, name, phone)
|
||
times.append(time.perf_counter() - start)
|
||
|
||
return times
|
||
|
||
def bench_find(structure, structure_type, search_names):
|
||
times = []
|
||
for _ in range(REPEATS):
|
||
start = time.perf_counter()
|
||
if structure_type == 'LinkedList':
|
||
for name in search_names:
|
||
ll_find(structure, name)
|
||
elif structure_type == 'HashTable':
|
||
for name in search_names:
|
||
ht_find(structure, name)
|
||
elif structure_type == 'BST':
|
||
for name in search_names:
|
||
bst_find(structure, name)
|
||
times.append(time.perf_counter() - start)
|
||
return times
|
||
|
||
def bench_delete(structure, structure_type, delete_names):
|
||
times = []
|
||
for _ in range(REPEATS):
|
||
if structure_type == 'LinkedList':
|
||
s = structure # head
|
||
start = time.perf_counter()
|
||
for name in delete_names:
|
||
s = ll_delete(s, name)
|
||
times.append(time.perf_counter() - start)
|
||
|
||
elif structure_type == 'HashTable':
|
||
start = time.perf_counter()
|
||
for name in delete_names:
|
||
ht_delete(structure, name)
|
||
times.append(time.perf_counter() - start)
|
||
|
||
elif structure_type == 'BST':
|
||
s = structure # root
|
||
start = time.perf_counter()
|
||
for name in delete_names:
|
||
s = bst_delete(s, name)
|
||
times.append(time.perf_counter() - start)
|
||
|
||
return times
|
||
|
||
|
||
def build_structure(structure_type, records):
|
||
if structure_type == 'LinkedList':
|
||
head = None
|
||
for name, phone in records:
|
||
head = ll_insert(head, name, phone)
|
||
return head
|
||
elif structure_type == 'HashTable':
|
||
buckets = ht_new()
|
||
for name, phone in records:
|
||
ht_insert(buckets, name, phone)
|
||
return buckets
|
||
elif structure_type == 'BST':
|
||
root = None
|
||
for name, phone in records:
|
||
root = bst_insert(root, name, phone)
|
||
return root
|
||
|
||
|
||
def run_all_benchmarks(n=10000):
|
||
rng = random.Random(99)
|
||
records_shuffled = generate_records(n)
|
||
random.Random(7).shuffle(records_shuffled)
|
||
records_sorted = sorted(generate_records(n), key=lambda x: x[0])
|
||
|
||
all_names = [r[0] for r in generate_records(n)]
|
||
search_names = rng.choices(all_names, k=100) + [f"None_{i}" for i in range(10)]
|
||
delete_names = rng.choices(all_names, k=50)
|
||
|
||
structures = ['LinkedList', 'HashTable', 'BST']
|
||
modes = [('shuffled', records_shuffled), ('sorted', records_sorted)]
|
||
|
||
rows = [["Structure", "Mode", "Operation", "Run", "Time_sec"]]
|
||
|
||
for struct in structures:
|
||
for mode_name, records in modes:
|
||
print(f" [{struct}] [{mode_name}] insert...", flush=True)
|
||
ins_times = bench_insert(struct, records)
|
||
for i, t in enumerate(ins_times):
|
||
rows.append([struct, mode_name, 'insert', i + 1, round(t, 6)])
|
||
|
||
# Построить структуру один раз для find/delete
|
||
print(f" [{struct}] [{mode_name}] building structure for find/delete...", flush=True)
|
||
s = build_structure(struct, records)
|
||
|
||
print(f" [{struct}] [{mode_name}] find...", flush=True)
|
||
find_times = bench_find(s, struct, search_names)
|
||
for i, t in enumerate(find_times):
|
||
rows.append([struct, mode_name, 'find', i + 1, round(t, 6)])
|
||
|
||
print(f" [{struct}] [{mode_name}] delete...", flush=True)
|
||
del_times = bench_delete(s, struct, delete_names)
|
||
for i, t in enumerate(del_times):
|
||
rows.append([struct, mode_name, 'delete', i + 1, round(t, 6)])
|
||
|
||
return rows
|
||
|
||
|
||
if __name__ == '__main__':
|
||
import sys
|
||
N = int(sys.argv[1]) if len(sys.argv) > 1 else 10000
|
||
print(f"Running benchmarks with N={N}, {REPEATS} repeats each...")
|
||
rows = run_all_benchmarks(N)
|
||
|
||
out_path = '/home/claude/docs/data/results.csv'
|
||
import os; os.makedirs('/home/claude/docs/data', exist_ok=True)
|
||
with open(out_path, 'w', newline='', encoding='utf-8') as f:
|
||
csv.writer(f).writerows(rows)
|
||
print(f"Results saved to {out_path}")
|
||
print(f"Total rows: {len(rows) - 1}")
|