2026-rff_mp/Ezhovnd/phonebook.py
Ezhovnd 380c8f643f Upload files to "Ezhovnd"
Лабораторная работа №1
2026-05-25 07:21:19 +00:00

368 lines
12 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 — структуры данных: телефонный справочник
# Процедурная парадигма (без классов)
# ============================================================
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}")