forked from UNN/2026-rff_mp
[2]
This commit is contained in:
parent
2fca522d2c
commit
565e8539d0
132
SmirnovVS/docs/data/1-st-exercize/experiment.py
Normal file
132
SmirnovVS/docs/data/1-st-exercize/experiment.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"""Экспериментальное сравнение структур данных."""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import random
|
||||
from pathlib import Path
|
||||
from statistics import mean
|
||||
from time import perf_counter
|
||||
|
||||
from phonebook import (
|
||||
bst_delete, bst_find, bst_insert,
|
||||
ht_delete, ht_find, ht_insert,
|
||||
ll_delete, ll_find, ll_insert,
|
||||
)
|
||||
|
||||
|
||||
def generate_records(size):
|
||||
return [(f"User_{index:05d}", f"+7{index:010d}") for index in range(size)]
|
||||
|
||||
|
||||
def measure_once(structure, records, existing_names, missing_names, deleted_names, bucket_count):
|
||||
started = perf_counter()
|
||||
if structure == "LinkedList":
|
||||
data = None
|
||||
for name, phone in records:
|
||||
data = ll_insert(data, name, phone)
|
||||
insert_time = perf_counter() - started
|
||||
|
||||
started = perf_counter()
|
||||
for name in existing_names + missing_names:
|
||||
ll_find(data, name)
|
||||
find_time = perf_counter() - started
|
||||
|
||||
started = perf_counter()
|
||||
for name in deleted_names:
|
||||
data = ll_delete(data, name)
|
||||
delete_time = perf_counter() - started
|
||||
elif structure == "HashTable":
|
||||
data = [None] * bucket_count
|
||||
for name, phone in records:
|
||||
ht_insert(data, name, phone)
|
||||
insert_time = perf_counter() - started
|
||||
|
||||
started = perf_counter()
|
||||
for name in existing_names + missing_names:
|
||||
ht_find(data, name)
|
||||
find_time = perf_counter() - started
|
||||
|
||||
started = perf_counter()
|
||||
for name in deleted_names:
|
||||
ht_delete(data, name)
|
||||
delete_time = perf_counter() - started
|
||||
else:
|
||||
data = None
|
||||
for name, phone in records:
|
||||
data = bst_insert(data, name, phone)
|
||||
insert_time = perf_counter() - started
|
||||
|
||||
started = perf_counter()
|
||||
for name in existing_names + missing_names:
|
||||
bst_find(data, name)
|
||||
find_time = perf_counter() - started
|
||||
|
||||
started = perf_counter()
|
||||
for name in deleted_names:
|
||||
data = bst_delete(data, name)
|
||||
delete_time = perf_counter() - started
|
||||
|
||||
return {"insert": insert_time, "find_110": find_time, "delete_50": delete_time}
|
||||
|
||||
|
||||
def run_experiment(size=3000, repeats=5, seed=2026, output_dir="docs/data"):
|
||||
rng = random.Random(seed)
|
||||
sorted_records = generate_records(size)
|
||||
shuffled_records = sorted_records.copy()
|
||||
rng.shuffle(shuffled_records)
|
||||
modes = {"shuffled": shuffled_records, "sorted": sorted_records}
|
||||
rows = []
|
||||
|
||||
for mode, records in modes.items():
|
||||
names = [record[0] for record in records]
|
||||
test_cases = []
|
||||
for run in range(1, repeats + 1):
|
||||
test_cases.append((
|
||||
run,
|
||||
rng.sample(names, min(100, size)),
|
||||
[f"None_{index}" for index in range(10)],
|
||||
rng.sample(names, min(50, size)),
|
||||
))
|
||||
for structure in ("LinkedList", "HashTable", "BST"):
|
||||
for run, existing, missing, deleted in test_cases:
|
||||
timings = measure_once(structure, records, existing, missing, deleted, max(17, size * 2 + 1))
|
||||
for operation, elapsed in timings.items():
|
||||
rows.append({
|
||||
"structure": structure,
|
||||
"mode": mode,
|
||||
"operation": operation,
|
||||
"run": run,
|
||||
"time_seconds": elapsed,
|
||||
})
|
||||
|
||||
output = Path(output_dir)
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
raw_path = output / "results_raw.csv"
|
||||
with raw_path.open("w", newline="", encoding="utf-8-sig") as file:
|
||||
writer = csv.DictWriter(file, fieldnames=rows[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
groups = {}
|
||||
for row in rows:
|
||||
key = (row["structure"], row["mode"], row["operation"])
|
||||
groups.setdefault(key, []).append(row["time_seconds"])
|
||||
summary = [
|
||||
{"structure": key[0], "mode": key[1], "operation": key[2], "mean_seconds": mean(values)}
|
||||
for key, values in groups.items()
|
||||
]
|
||||
summary_path = output / "results_summary.csv"
|
||||
with summary_path.open("w", newline="", encoding="utf-8-sig") as file:
|
||||
writer = csv.DictWriter(file, fieldnames=summary[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(summary)
|
||||
return raw_path, summary_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Сравнение структур телефонного справочника")
|
||||
parser.add_argument("--size", type=int, default=3000)
|
||||
parser.add_argument("--repeats", type=int, default=5)
|
||||
args = parser.parse_args()
|
||||
raw, summary = run_experiment(args.size, args.repeats)
|
||||
print(f"Полные замеры: {raw}\nСредние значения: {summary}")
|
||||
25
SmirnovVS/docs/data/1-st-exercize/main.py
Normal file
25
SmirnovVS/docs/data/1-st-exercize/main.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from phonebook import ht_delete, ht_find, ht_insert, ht_list_all
|
||||
|
||||
|
||||
def main():
|
||||
phonebook = [None] * 101
|
||||
print("Телефонный справочник. Команды: add, find, delete, list, exit")
|
||||
while True:
|
||||
command = input("> ").strip().lower()
|
||||
if command == "add":
|
||||
ht_insert(phonebook, input("Имя: ").strip(), input("Телефон: ").strip())
|
||||
elif command == "find":
|
||||
print(ht_find(phonebook, input("Имя: ").strip()) or "Запись не найдена")
|
||||
elif command == "delete":
|
||||
ht_delete(phonebook, input("Имя: ").strip())
|
||||
elif command == "list":
|
||||
for name, phone in ht_list_all(phonebook):
|
||||
print(f"{name}: {phone}")
|
||||
elif command == "exit":
|
||||
break
|
||||
else:
|
||||
print("Неизвестная команда")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
162
SmirnovVS/docs/data/1-st-exercize/phonebook.py
Normal file
162
SmirnovVS/docs/data/1-st-exercize/phonebook.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"""Телефонный справочник на трёх структурах данных."""
|
||||
|
||||
|
||||
def _node(name, phone, next_node=None):
|
||||
return {"name": name, "phone": phone, "next": next_node}
|
||||
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
"""Добавить запись в конец списка или обновить существующую."""
|
||||
if head is None:
|
||||
return _node(name, phone)
|
||||
|
||||
current = head
|
||||
while True:
|
||||
if current["name"] == name:
|
||||
current["phone"] = phone
|
||||
return head
|
||||
if current["next"] is None:
|
||||
current["next"] = _node(name, phone)
|
||||
return head
|
||||
current = current["next"]
|
||||
|
||||
|
||||
def ll_find(head, name):
|
||||
current = head
|
||||
while current is not None:
|
||||
if current["name"] == name:
|
||||
return current["phone"]
|
||||
current = current["next"]
|
||||
return None
|
||||
|
||||
|
||||
def ll_delete(head, name):
|
||||
if head is None:
|
||||
return None
|
||||
if head["name"] == name:
|
||||
return head["next"]
|
||||
|
||||
previous, current = head, head["next"]
|
||||
while current is not None:
|
||||
if current["name"] == name:
|
||||
previous["next"] = current["next"]
|
||||
break
|
||||
previous, current = current, current["next"]
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head):
|
||||
records = []
|
||||
current = head
|
||||
while current is not None:
|
||||
records.append((current["name"], current["phone"]))
|
||||
current = current["next"]
|
||||
return sorted(records, key=lambda record: record[0])
|
||||
|
||||
|
||||
def _require_buckets(buckets):
|
||||
if not buckets:
|
||||
raise ValueError("Хеш-таблица должна содержать хотя бы один бакет")
|
||||
|
||||
|
||||
def _hash_index(name, bucket_count):
|
||||
"""Хеш-функция."""
|
||||
value = 0
|
||||
for character in name:
|
||||
value = (value * 31 + ord(character)) % bucket_count
|
||||
return value
|
||||
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
_require_buckets(buckets)
|
||||
index = _hash_index(name, len(buckets))
|
||||
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||
|
||||
|
||||
def ht_find(buckets, name):
|
||||
_require_buckets(buckets)
|
||||
return ll_find(buckets[_hash_index(name, len(buckets))], name)
|
||||
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
_require_buckets(buckets)
|
||||
index = _hash_index(name, len(buckets))
|
||||
buckets[index] = ll_delete(buckets[index], name)
|
||||
|
||||
|
||||
def ht_list_all(buckets):
|
||||
_require_buckets(buckets)
|
||||
records = []
|
||||
for head in buckets:
|
||||
records.extend(ll_list_all(head))
|
||||
return sorted(records, key=lambda record: record[0])
|
||||
|
||||
|
||||
def _bst_node(name, phone):
|
||||
return {"name": name, "phone": phone, "left": None, "right": None}
|
||||
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
if root is None:
|
||||
return _bst_node(name, phone)
|
||||
|
||||
current = root
|
||||
while True:
|
||||
if name == current["name"]:
|
||||
current["phone"] = phone
|
||||
return root
|
||||
side = "left" if name < current["name"] else "right"
|
||||
if current[side] is None:
|
||||
current[side] = _bst_node(name, phone)
|
||||
return root
|
||||
current = current[side]
|
||||
|
||||
|
||||
def bst_find(root, name):
|
||||
current = root
|
||||
while current is not None:
|
||||
if name == current["name"]:
|
||||
return current["phone"]
|
||||
current = current["left"] if name < current["name"] else current["right"]
|
||||
return None
|
||||
|
||||
|
||||
def bst_delete(root, name):
|
||||
parent = None
|
||||
current = root
|
||||
while current is not None and current["name"] != name:
|
||||
parent = current
|
||||
current = current["left"] if name < current["name"] else current["right"]
|
||||
if current is None:
|
||||
return root
|
||||
|
||||
if current["left"] is not None and current["right"] is not None:
|
||||
successor_parent = current
|
||||
successor = current["right"]
|
||||
while successor["left"] is not None:
|
||||
successor_parent = successor
|
||||
successor = successor["left"]
|
||||
current["name"], current["phone"] = successor["name"], successor["phone"]
|
||||
parent, current = successor_parent, successor
|
||||
|
||||
child = current["left"] if current["left"] is not None else current["right"]
|
||||
if parent is None:
|
||||
return child
|
||||
if parent["left"] is current:
|
||||
parent["left"] = child
|
||||
else:
|
||||
parent["right"] = child
|
||||
return root
|
||||
|
||||
|
||||
def bst_list_all(root):
|
||||
records, stack = [], []
|
||||
current = root
|
||||
while stack or current is not None:
|
||||
while current is not None:
|
||||
stack.append(current)
|
||||
current = current["left"]
|
||||
current = stack.pop()
|
||||
records.append((current["name"], current["phone"]))
|
||||
current = current["right"]
|
||||
return records
|
||||
32
SmirnovVS/docs/data/1-st-exercize/plot_results.py
Normal file
32
SmirnovVS/docs/data/1-st-exercize/plot_results.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import csv
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def create_plot(csv_path="docs/data/results_summary.csv", output_path="docs/data/performance.png"):
|
||||
with Path(csv_path).open(encoding="utf-8-sig") as file:
|
||||
rows = list(csv.DictReader(file))
|
||||
operations = ["insert", "find_110", "delete_50"]
|
||||
titles = ["Вставка всех записей", "Поиск 110 имён", "Удаление 50 записей"]
|
||||
labels = [(structure, mode) for mode in ("shuffled", "sorted") for structure in ("LinkedList", "HashTable", "BST")]
|
||||
figure, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||
for axis, operation, title in zip(axes, operations, titles):
|
||||
values = []
|
||||
for structure, mode in labels:
|
||||
row = next(item for item in rows if item["structure"] == structure and item["mode"] == mode and item["operation"] == operation)
|
||||
values.append(float(row["mean_seconds"]) * 1000)
|
||||
axis.bar(range(len(labels)), values, color=["#4472C4", "#70AD47", "#ED7D31"] * 2)
|
||||
axis.set_title(title)
|
||||
axis.set_ylabel("Время, мс")
|
||||
axis.set_xticks(range(len(labels)), [f"{s}\n{m}" for s, m in labels], rotation=35, ha="right")
|
||||
axis.grid(axis="y", alpha=0.25)
|
||||
figure.suptitle("Производительность структур данных (средние значения)")
|
||||
figure.tight_layout()
|
||||
figure.savefig(output_path, dpi=180)
|
||||
plt.close(figure)
|
||||
return Path(output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"График сохранён: {create_plot()}")
|
||||
91
SmirnovVS/docs/data/1-st-exercize/results_raw.csv
Normal file
91
SmirnovVS/docs/data/1-st-exercize/results_raw.csv
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
structure,mode,operation,run,time_seconds
|
||||
LinkedList,shuffled,insert,1,0.1947654000005059
|
||||
LinkedList,shuffled,find_110,1,0.0063459000002694665
|
||||
LinkedList,shuffled,delete_50,1,0.002933899999334244
|
||||
LinkedList,shuffled,insert,2,0.19509340000058728
|
||||
LinkedList,shuffled,find_110,2,0.006126699999185803
|
||||
LinkedList,shuffled,delete_50,2,0.002876999999898544
|
||||
LinkedList,shuffled,insert,3,0.19163060000028054
|
||||
LinkedList,shuffled,find_110,3,0.00638989999970363
|
||||
LinkedList,shuffled,delete_50,3,0.0027322000005369773
|
||||
LinkedList,shuffled,insert,4,0.19537710000076913
|
||||
LinkedList,shuffled,find_110,4,0.00553089999993972
|
||||
LinkedList,shuffled,delete_50,4,0.00274009999975533
|
||||
LinkedList,shuffled,insert,5,0.19868119999955525
|
||||
LinkedList,shuffled,find_110,5,0.006262500000048021
|
||||
LinkedList,shuffled,delete_50,5,0.0034435999996276223
|
||||
HashTable,shuffled,insert,1,0.0045235999996293685
|
||||
HashTable,shuffled,find_110,1,0.00012590000005729962
|
||||
HashTable,shuffled,delete_50,1,6.089999988034833e-05
|
||||
HashTable,shuffled,insert,2,0.004393600000184961
|
||||
HashTable,shuffled,find_110,2,0.00011859999995067483
|
||||
HashTable,shuffled,delete_50,2,5.79999996261904e-05
|
||||
HashTable,shuffled,insert,3,0.0038748000006307848
|
||||
HashTable,shuffled,find_110,3,0.00011610000001383014
|
||||
HashTable,shuffled,delete_50,3,5.719999990105862e-05
|
||||
HashTable,shuffled,insert,4,0.003696299999319308
|
||||
HashTable,shuffled,find_110,4,0.00011779999931604834
|
||||
HashTable,shuffled,delete_50,4,5.729999975301325e-05
|
||||
HashTable,shuffled,insert,5,0.0037232000004223664
|
||||
HashTable,shuffled,find_110,5,0.0001156999996965169
|
||||
HashTable,shuffled,delete_50,5,5.5900000006658956e-05
|
||||
BST,shuffled,insert,1,0.004337399999712943
|
||||
BST,shuffled,find_110,1,0.0001140000003942987
|
||||
BST,shuffled,delete_50,1,7.019999975454994e-05
|
||||
BST,shuffled,insert,2,0.004390000000057626
|
||||
BST,shuffled,find_110,2,0.00011150000045745401
|
||||
BST,shuffled,delete_50,2,6.52999997328152e-05
|
||||
BST,shuffled,insert,3,0.004179900000053749
|
||||
BST,shuffled,find_110,3,0.00010879999990720535
|
||||
BST,shuffled,delete_50,3,6.070000017643906e-05
|
||||
BST,shuffled,insert,4,0.004183999999440857
|
||||
BST,shuffled,find_110,4,0.00010620000011840602
|
||||
BST,shuffled,delete_50,4,9.539999973640079e-05
|
||||
BST,shuffled,insert,5,0.004391600000417384
|
||||
BST,shuffled,find_110,5,0.00010419999944133451
|
||||
BST,shuffled,delete_50,5,6.630000007135095e-05
|
||||
LinkedList,sorted,insert,1,0.19158669999978883
|
||||
LinkedList,sorted,find_110,1,0.006112799999755225
|
||||
LinkedList,sorted,delete_50,1,0.005895900000723486
|
||||
LinkedList,sorted,insert,2,0.19256610000047658
|
||||
LinkedList,sorted,find_110,2,0.006044900000233611
|
||||
LinkedList,sorted,delete_50,2,0.002797499999360298
|
||||
LinkedList,sorted,insert,3,0.19292270000005374
|
||||
LinkedList,sorted,find_110,3,0.006467600000178209
|
||||
LinkedList,sorted,delete_50,3,0.0028355000004012254
|
||||
LinkedList,sorted,insert,4,0.19164809999983845
|
||||
LinkedList,sorted,find_110,4,0.005796100000225124
|
||||
LinkedList,sorted,delete_50,4,0.0030335999999806518
|
||||
LinkedList,sorted,insert,5,0.1920518999995693
|
||||
LinkedList,sorted,find_110,5,0.006033799999386247
|
||||
LinkedList,sorted,delete_50,5,0.0028001999999105465
|
||||
HashTable,sorted,insert,1,0.0036538000003929483
|
||||
HashTable,sorted,find_110,1,0.0001173999999082298
|
||||
HashTable,sorted,delete_50,1,5.810000038763974e-05
|
||||
HashTable,sorted,insert,2,0.003586599999835016
|
||||
HashTable,sorted,find_110,2,0.00011629999971773941
|
||||
HashTable,sorted,delete_50,2,5.540000074688578e-05
|
||||
HashTable,sorted,insert,3,0.00354940000033821
|
||||
HashTable,sorted,find_110,3,0.00011549999999260763
|
||||
HashTable,sorted,delete_50,3,5.520000013348181e-05
|
||||
HashTable,sorted,insert,4,0.0036109000002397806
|
||||
HashTable,sorted,find_110,4,0.00011329999961162684
|
||||
HashTable,sorted,delete_50,4,5.6000000768108293e-05
|
||||
HashTable,sorted,insert,5,0.003564399999959278
|
||||
HashTable,sorted,find_110,5,0.00011339999946358148
|
||||
HashTable,sorted,delete_50,5,5.480000072566327e-05
|
||||
BST,sorted,insert,1,0.3111501000003045
|
||||
BST,sorted,find_110,1,0.007159199999478005
|
||||
BST,sorted,delete_50,1,0.004548900000372669
|
||||
BST,sorted,insert,2,0.30066009999973176
|
||||
BST,sorted,find_110,2,0.008023899999898276
|
||||
BST,sorted,delete_50,2,0.004239900000357011
|
||||
BST,sorted,insert,3,0.3020464000001084
|
||||
BST,sorted,find_110,3,0.00876090000019758
|
||||
BST,sorted,delete_50,3,0.004445499999746971
|
||||
BST,sorted,insert,4,0.30183889999989333
|
||||
BST,sorted,find_110,4,0.007328300000153831
|
||||
BST,sorted,delete_50,4,0.005208799999309122
|
||||
BST,sorted,insert,5,0.3034312999998292
|
||||
BST,sorted,find_110,5,0.008107399999971676
|
||||
BST,sorted,delete_50,5,0.004352900000412774
|
||||
|
Loading…
Reference in New Issue
Block a user