Merge pull request '[2] final' (#382) from Smirnovvs/2026-rff_mp:4 into develop
Reviewed-on: #382
This commit is contained in:
commit
70dd7d781a
1
SmirnovVS/428.md
Normal file
1
SmirnovVS/428.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
428
|
||||||
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
|
||||||
|
19
SmirnovVS/docs/data/1-st-exercize/results_summary.csv
Normal file
19
SmirnovVS/docs/data/1-st-exercize/results_summary.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
structure,mode,operation,mean_seconds
|
||||||
|
LinkedList,shuffled,insert,0.19510954000033962
|
||||||
|
LinkedList,shuffled,find_110,0.006131179999829328
|
||||||
|
LinkedList,shuffled,delete_50,0.0029453599998305437
|
||||||
|
HashTable,shuffled,insert,0.004042300000037358
|
||||||
|
HashTable,shuffled,find_110,0.00011881999980687397
|
||||||
|
HashTable,shuffled,delete_50,5.7859999833453914e-05
|
||||||
|
BST,shuffled,insert,0.004296579999936512
|
||||||
|
BST,shuffled,find_110,0.00010894000006373971
|
||||||
|
BST,shuffled,delete_50,7.157999989431119e-05
|
||||||
|
LinkedList,sorted,insert,0.19215509999994537
|
||||||
|
LinkedList,sorted,find_110,0.006091039999955683
|
||||||
|
LinkedList,sorted,delete_50,0.0034725400000752416
|
||||||
|
HashTable,sorted,insert,0.0035930200001530466
|
||||||
|
HashTable,sorted,find_110,0.00011517999973875703
|
||||||
|
HashTable,sorted,delete_50,5.590000055235578e-05
|
||||||
|
BST,sorted,insert,0.30382535999997345
|
||||||
|
BST,sorted,find_110,0.007875939999939874
|
||||||
|
BST,sorted,delete_50,0.004559200000039709
|
||||||
|
64
SmirnovVS/docs/data/2-nd-exercize/experiment.py
Normal file
64
SmirnovVS/docs/data/2-nd-exercize/experiment.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
from statistics import mean
|
||||||
|
|
||||||
|
from generate_mazes import generate_all
|
||||||
|
from maze_app import AStarStrategy, BFSStrategy, DFSStrategy, MazeSolver, TextFileMazeBuilder
|
||||||
|
|
||||||
|
|
||||||
|
STRATEGIES = (BFSStrategy, DFSStrategy, AStarStrategy)
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiment(repeats=7, maze_dir="mazes", output_dir="docs/data"):
|
||||||
|
generate_all(maze_dir)
|
||||||
|
builder = TextFileMazeBuilder()
|
||||||
|
rows = []
|
||||||
|
for maze_path in sorted(Path(maze_dir).glob("*.txt")):
|
||||||
|
maze = builder.build_from_file(maze_path)
|
||||||
|
for strategy_type in STRATEGIES:
|
||||||
|
for run in range(1, repeats + 1):
|
||||||
|
stats = MazeSolver(maze, strategy_type()).solve()
|
||||||
|
rows.append({
|
||||||
|
"maze": maze_path.stem,
|
||||||
|
"strategy": stats.strategy,
|
||||||
|
"run": run,
|
||||||
|
"time_ms": stats.time_ms,
|
||||||
|
"visited_cells": stats.visited_cells,
|
||||||
|
"path_length": stats.path_length,
|
||||||
|
"path_found": bool(stats.path),
|
||||||
|
})
|
||||||
|
output = Path(output_dir)
|
||||||
|
output.mkdir(parents=True, exist_ok=True)
|
||||||
|
raw_path = output / "maze_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:
|
||||||
|
groups.setdefault((row["maze"], row["strategy"]), []).append(row)
|
||||||
|
summary = []
|
||||||
|
for (maze_name, strategy), values in groups.items():
|
||||||
|
summary.append({
|
||||||
|
"maze": maze_name,
|
||||||
|
"strategy": strategy,
|
||||||
|
"mean_time_ms": mean(row["time_ms"] for row in values),
|
||||||
|
"mean_visited_cells": mean(row["visited_cells"] for row in values),
|
||||||
|
"path_length": values[0]["path_length"],
|
||||||
|
"path_found": values[0]["path_found"],
|
||||||
|
})
|
||||||
|
summary_path = output / "maze_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("--repeats", type=int, default=7)
|
||||||
|
args = parser.parse_args()
|
||||||
|
print("Результаты:", *run_experiment(args.repeats), sep="\n")
|
||||||
52
SmirnovVS/docs/data/2-nd-exercize/generate_mazes.py
Normal file
52
SmirnovVS/docs/data/2-nd-exercize/generate_mazes.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
"""Генерация воспроизводимых тестовых лабиринтов."""
|
||||||
|
|
||||||
|
import random
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def obstacle_maze(width, height, wall_probability, seed):
|
||||||
|
rng = random.Random(seed)
|
||||||
|
grid = [["#" if x in (0, width - 1) or y in (0, height - 1) else " " for x in range(width)] for y in range(height)]
|
||||||
|
for y in range(1, height - 1):
|
||||||
|
for x in range(1, width - 1):
|
||||||
|
if rng.random() < wall_probability:
|
||||||
|
grid[y][x] = "#"
|
||||||
|
# Оставляем гарантированный путь по верхней и правой внутренним границам.
|
||||||
|
for x in range(1, width - 1):
|
||||||
|
grid[1][x] = " "
|
||||||
|
for y in range(1, height - 1):
|
||||||
|
grid[y][width - 2] = " "
|
||||||
|
grid[1][1], grid[height - 2][width - 2] = "S", "E"
|
||||||
|
return "\n".join("".join(row) for row in grid) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def empty_maze(width=50, height=50):
|
||||||
|
return obstacle_maze(width, height, 0, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def blocked_maze(width=30, height=30):
|
||||||
|
lines = empty_maze(width, height).splitlines()
|
||||||
|
grid = [list(line) for line in lines]
|
||||||
|
exit_y, exit_x = height - 2, width - 2
|
||||||
|
grid[exit_y - 1][exit_x] = "#"
|
||||||
|
grid[exit_y][exit_x - 1] = "#"
|
||||||
|
return "\n".join("".join(row) for row in grid) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_all(output_dir="mazes"):
|
||||||
|
output = Path(output_dir)
|
||||||
|
output.mkdir(parents=True, exist_ok=True)
|
||||||
|
maps = {
|
||||||
|
"small_10x10.txt": obstacle_maze(10, 10, 0.12, 10),
|
||||||
|
"medium_50x50.txt": obstacle_maze(50, 50, 0.28, 50),
|
||||||
|
"large_100x100.txt": obstacle_maze(100, 100, 0.32, 100),
|
||||||
|
"empty_50x50.txt": empty_maze(),
|
||||||
|
"no_path_30x30.txt": blocked_maze(),
|
||||||
|
}
|
||||||
|
for filename, content in maps.items():
|
||||||
|
(output / filename).write_text(content, encoding="utf-8")
|
||||||
|
return list(maps)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Созданы файлы:", ", ".join(generate_all()))
|
||||||
24
SmirnovVS/docs/data/2-nd-exercize/main.py
Normal file
24
SmirnovVS/docs/data/2-nd-exercize/main.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from maze_app import AStarStrategy, BFSStrategy, ConsoleView, DFSStrategy, MazeSolver, TextFileMazeBuilder
|
||||||
|
|
||||||
|
|
||||||
|
STRATEGIES = {"bfs": BFSStrategy, "dfs": DFSStrategy, "astar": AStarStrategy}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Поиск выхода из лабиринта")
|
||||||
|
parser.add_argument("maze", nargs="?", default="mazes/small_10x10.txt")
|
||||||
|
parser.add_argument("--algorithm", choices=STRATEGIES, default="bfs")
|
||||||
|
args = parser.parse_args()
|
||||||
|
maze = TextFileMazeBuilder().build_from_file(args.maze)
|
||||||
|
view = ConsoleView()
|
||||||
|
solver = MazeSolver(maze, STRATEGIES[args.algorithm]())
|
||||||
|
solver.attach(view)
|
||||||
|
stats = solver.solve()
|
||||||
|
print(view.render(maze, stats.path))
|
||||||
|
print(f"Время: {stats.time_ms:.4f} мс; посещено: {stats.visited_cells}; длина пути: {stats.path_length}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
246
SmirnovVS/docs/data/2-nd-exercize/maze_app.py
Normal file
246
SmirnovVS/docs/data/2-nd-exercize/maze_app.py
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
"""Модель лабиринта и алгоритмы поиска с паттернами Builder, Strategy, Observer."""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from heapq import heappop, heappush
|
||||||
|
from itertools import count
|
||||||
|
from pathlib import Path
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Cell:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
is_wall: bool = False
|
||||||
|
is_start: bool = False
|
||||||
|
is_exit: bool = False
|
||||||
|
|
||||||
|
def is_passable(self):
|
||||||
|
return not self.is_wall
|
||||||
|
|
||||||
|
|
||||||
|
class Maze:
|
||||||
|
def __init__(self, cells):
|
||||||
|
if not cells or not cells[0]:
|
||||||
|
raise ValueError("Лабиринт не может быть пустым")
|
||||||
|
self.cells = cells
|
||||||
|
self.height = len(cells)
|
||||||
|
self.width = len(cells[0])
|
||||||
|
self.start = next((cell for row in cells for cell in row if cell.is_start), None)
|
||||||
|
self.exit = next((cell for row in cells for cell in row if cell.is_exit), None)
|
||||||
|
|
||||||
|
def get_cell(self, x, y):
|
||||||
|
if 0 <= x < self.width and 0 <= y < self.height:
|
||||||
|
return self.cells[y][x]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_neighbors(self, cell):
|
||||||
|
neighbors = []
|
||||||
|
for dx, dy in ((0, -1), (1, 0), (0, 1), (-1, 0)):
|
||||||
|
neighbor = self.get_cell(cell.x + dx, cell.y + dy)
|
||||||
|
if neighbor is not None and neighbor.is_passable():
|
||||||
|
neighbors.append(neighbor)
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
|
||||||
|
class MazeBuilder(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def build_from_file(self, filename):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TextFileMazeBuilder(MazeBuilder):
|
||||||
|
SYMBOLS = {"#", " ", "S", "E"}
|
||||||
|
|
||||||
|
def build_from_file(self, filename):
|
||||||
|
lines = Path(filename).read_text(encoding="utf-8").splitlines()
|
||||||
|
if not lines or not lines[0] or any(len(line) != len(lines[0]) for line in lines):
|
||||||
|
raise ValueError("Строки лабиринта должны иметь одинаковую ненулевую длину")
|
||||||
|
unknown = {character for line in lines for character in line} - self.SYMBOLS
|
||||||
|
if unknown:
|
||||||
|
raise ValueError(f"Недопустимые символы: {sorted(unknown)}")
|
||||||
|
if sum(line.count("S") for line in lines) != 1 or sum(line.count("E") for line in lines) != 1:
|
||||||
|
raise ValueError("Лабиринт должен содержать ровно один старт S и один выход E")
|
||||||
|
|
||||||
|
cells = []
|
||||||
|
for y, line in enumerate(lines):
|
||||||
|
cells.append([
|
||||||
|
Cell(x, y, symbol == "#", symbol == "S", symbol == "E")
|
||||||
|
for x, symbol in enumerate(line)
|
||||||
|
])
|
||||||
|
return Maze(cells)
|
||||||
|
|
||||||
|
|
||||||
|
class PathFindingStrategy(ABC):
|
||||||
|
name = "Неизвестно"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.visited_count = 0
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def find_path(self, maze, start, exit_cell):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def restore_path(parent, start, exit_cell):
|
||||||
|
if exit_cell not in parent:
|
||||||
|
return []
|
||||||
|
path, current = [], exit_cell
|
||||||
|
while current is not None:
|
||||||
|
path.append(current)
|
||||||
|
current = parent[current]
|
||||||
|
path.reverse()
|
||||||
|
return path if path and path[0] == start else []
|
||||||
|
|
||||||
|
|
||||||
|
class BFSStrategy(PathFindingStrategy):
|
||||||
|
name = "BFS"
|
||||||
|
|
||||||
|
def find_path(self, maze, start, exit_cell):
|
||||||
|
queue = deque([start])
|
||||||
|
parent = {start: None}
|
||||||
|
visited = 0
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
visited += 1
|
||||||
|
if current == exit_cell:
|
||||||
|
break
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in parent:
|
||||||
|
parent[neighbor] = current
|
||||||
|
queue.append(neighbor)
|
||||||
|
self.visited_count = visited
|
||||||
|
return self.restore_path(parent, start, exit_cell)
|
||||||
|
|
||||||
|
|
||||||
|
class DFSStrategy(PathFindingStrategy):
|
||||||
|
name = "DFS"
|
||||||
|
|
||||||
|
def find_path(self, maze, start, exit_cell):
|
||||||
|
stack = [start]
|
||||||
|
parent = {start: None}
|
||||||
|
visited = 0
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
visited += 1
|
||||||
|
if current == exit_cell:
|
||||||
|
break
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
if neighbor not in parent:
|
||||||
|
parent[neighbor] = current
|
||||||
|
stack.append(neighbor)
|
||||||
|
self.visited_count = visited
|
||||||
|
return self.restore_path(parent, start, exit_cell)
|
||||||
|
|
||||||
|
|
||||||
|
class AStarStrategy(PathFindingStrategy):
|
||||||
|
name = "A*"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def heuristic(first, second):
|
||||||
|
return abs(first.x - second.x) + abs(first.y - second.y)
|
||||||
|
|
||||||
|
def find_path(self, maze, start, exit_cell):
|
||||||
|
order = count()
|
||||||
|
queue = [(self.heuristic(start, exit_cell), next(order), start)]
|
||||||
|
distance = {start: 0}
|
||||||
|
parent = {start: None}
|
||||||
|
closed = set()
|
||||||
|
while queue:
|
||||||
|
_, _, current = heappop(queue)
|
||||||
|
if current in closed:
|
||||||
|
continue
|
||||||
|
closed.add(current)
|
||||||
|
if current == exit_cell:
|
||||||
|
break
|
||||||
|
for neighbor in maze.get_neighbors(current):
|
||||||
|
new_distance = distance[current] + 1
|
||||||
|
if new_distance < distance.get(neighbor, float("inf")):
|
||||||
|
distance[neighbor] = new_distance
|
||||||
|
parent[neighbor] = current
|
||||||
|
priority = new_distance + self.heuristic(neighbor, exit_cell)
|
||||||
|
heappush(queue, (priority, next(order), neighbor))
|
||||||
|
self.visited_count = len(closed)
|
||||||
|
return self.restore_path(parent, start, exit_cell)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SearchStats:
|
||||||
|
strategy: str
|
||||||
|
time_ms: float
|
||||||
|
visited_cells: int
|
||||||
|
path_length: int
|
||||||
|
path: list = field(repr=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Observer(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleView(Observer):
|
||||||
|
def __init__(self, verbose=True):
|
||||||
|
self.verbose = verbose
|
||||||
|
self.events = []
|
||||||
|
|
||||||
|
def update(self, event):
|
||||||
|
self.events.append(event)
|
||||||
|
if self.verbose:
|
||||||
|
print(event["message"])
|
||||||
|
|
||||||
|
def render(self, maze, path=None):
|
||||||
|
path_cells = set(path or [])
|
||||||
|
rows = []
|
||||||
|
for row in maze.cells:
|
||||||
|
symbols = []
|
||||||
|
for cell in row:
|
||||||
|
if cell.is_start:
|
||||||
|
symbols.append("S")
|
||||||
|
elif cell.is_exit:
|
||||||
|
symbols.append("E")
|
||||||
|
elif cell.is_wall:
|
||||||
|
symbols.append("#")
|
||||||
|
elif cell in path_cells:
|
||||||
|
symbols.append(".")
|
||||||
|
else:
|
||||||
|
symbols.append(" ")
|
||||||
|
rows.append("".join(symbols))
|
||||||
|
return "\n".join(rows)
|
||||||
|
|
||||||
|
|
||||||
|
class MazeSolver:
|
||||||
|
def __init__(self, maze, strategy):
|
||||||
|
self.maze = maze
|
||||||
|
self.strategy = strategy
|
||||||
|
self.observers = []
|
||||||
|
|
||||||
|
def set_strategy(self, strategy):
|
||||||
|
self.strategy = strategy
|
||||||
|
|
||||||
|
def attach(self, observer):
|
||||||
|
if observer not in self.observers:
|
||||||
|
self.observers.append(observer)
|
||||||
|
|
||||||
|
def notify(self, event):
|
||||||
|
for observer in self.observers:
|
||||||
|
observer.update(event)
|
||||||
|
|
||||||
|
def solve(self):
|
||||||
|
self.notify({"type": "search_started", "message": f"Запущен алгоритм {self.strategy.name}"})
|
||||||
|
started = perf_counter()
|
||||||
|
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||||
|
elapsed_ms = max((perf_counter() - started) * 1000, 1e-9)
|
||||||
|
stats = SearchStats(
|
||||||
|
strategy=self.strategy.name,
|
||||||
|
time_ms=elapsed_ms,
|
||||||
|
visited_cells=self.strategy.visited_count,
|
||||||
|
path_length=max(len(path) - 1, 0),
|
||||||
|
path=path,
|
||||||
|
)
|
||||||
|
event_type = "path_found" if path else "path_not_found"
|
||||||
|
message = f"{self.strategy.name}: путь длиной {stats.path_length}" if path else f"{self.strategy.name}: путь не найден"
|
||||||
|
self.notify({"type": event_type, "message": message, "stats": stats})
|
||||||
|
return stats
|
||||||
106
SmirnovVS/docs/data/2-nd-exercize/maze_results_raw.csv
Normal file
106
SmirnovVS/docs/data/2-nd-exercize/maze_results_raw.csv
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
maze,strategy,run,time_ms,visited_cells,path_length,path_found
|
||||||
|
empty_50x50,BFS,1,28.560300008393824,2304,94,True
|
||||||
|
empty_50x50,BFS,2,21.739099989645183,2304,94,True
|
||||||
|
empty_50x50,BFS,3,28.969400038477033,2304,94,True
|
||||||
|
empty_50x50,BFS,4,26.839600002858788,2304,94,True
|
||||||
|
empty_50x50,BFS,5,16.788399952929467,2304,94,True
|
||||||
|
empty_50x50,BFS,6,14.332599996123463,2304,94,True
|
||||||
|
empty_50x50,BFS,7,22.064500022679567,2304,94,True
|
||||||
|
empty_50x50,DFS,1,0.5307000246830285,95,94,True
|
||||||
|
empty_50x50,DFS,2,0.6820000126026571,95,94,True
|
||||||
|
empty_50x50,DFS,3,0.5209000082686543,95,94,True
|
||||||
|
empty_50x50,DFS,4,0.5133000086061656,95,94,True
|
||||||
|
empty_50x50,DFS,5,0.8089999901130795,95,94,True
|
||||||
|
empty_50x50,DFS,6,0.5200000014156103,95,94,True
|
||||||
|
empty_50x50,DFS,7,0.8933999924920499,95,94,True
|
||||||
|
empty_50x50,A*,1,68.07579996529967,2304,94,True
|
||||||
|
empty_50x50,A*,2,61.01420003687963,2304,94,True
|
||||||
|
empty_50x50,A*,3,52.171000046655536,2304,94,True
|
||||||
|
empty_50x50,A*,4,53.36979997809976,2304,94,True
|
||||||
|
empty_50x50,A*,5,64.77910000830889,2304,94,True
|
||||||
|
empty_50x50,A*,6,51.02490005083382,2304,94,True
|
||||||
|
empty_50x50,A*,7,70.88650000514463,2304,94,True
|
||||||
|
large_100x100,BFS,1,91.43029997358099,6409,194,True
|
||||||
|
large_100x100,BFS,2,98.55669998796657,6409,194,True
|
||||||
|
large_100x100,BFS,3,98.43990002991632,6409,194,True
|
||||||
|
large_100x100,BFS,4,51.18469998706132,6409,194,True
|
||||||
|
large_100x100,BFS,5,44.218000024557114,6409,194,True
|
||||||
|
large_100x100,BFS,6,28.500600019469857,6409,194,True
|
||||||
|
large_100x100,BFS,7,60.57189998682588,6409,194,True
|
||||||
|
large_100x100,DFS,1,25.9578000404872,1561,762,True
|
||||||
|
large_100x100,DFS,2,18.451499985530972,1561,762,True
|
||||||
|
large_100x100,DFS,3,18.506099993828684,1561,762,True
|
||||||
|
large_100x100,DFS,4,14.582899981178343,1561,762,True
|
||||||
|
large_100x100,DFS,5,10.724799998570234,1561,762,True
|
||||||
|
large_100x100,DFS,6,17.07870000973344,1561,762,True
|
||||||
|
large_100x100,DFS,7,19.463400007225573,1561,762,True
|
||||||
|
large_100x100,A*,1,38.1690000067465,1997,194,True
|
||||||
|
large_100x100,A*,2,30.891000002156943,1997,194,True
|
||||||
|
large_100x100,A*,3,23.280899971723557,1997,194,True
|
||||||
|
large_100x100,A*,4,26.04500000597909,1997,194,True
|
||||||
|
large_100x100,A*,5,27.82869996735826,1997,194,True
|
||||||
|
large_100x100,A*,6,26.94000001065433,1997,194,True
|
||||||
|
large_100x100,A*,7,28.832400043029338,1997,194,True
|
||||||
|
medium_50x50,BFS,1,6.693199975416064,1700,94,True
|
||||||
|
medium_50x50,BFS,2,6.831499980762601,1700,94,True
|
||||||
|
medium_50x50,BFS,3,8.055799989961088,1700,94,True
|
||||||
|
medium_50x50,BFS,4,7.26869999198243,1700,94,True
|
||||||
|
medium_50x50,BFS,5,11.507100018206984,1700,94,True
|
||||||
|
medium_50x50,BFS,6,7.060799980536103,1700,94,True
|
||||||
|
medium_50x50,BFS,7,7.459699991159141,1700,94,True
|
||||||
|
medium_50x50,DFS,1,3.0005000298842788,408,266,True
|
||||||
|
medium_50x50,DFS,2,1.8214000156149268,408,266,True
|
||||||
|
medium_50x50,DFS,3,2.6385000091977417,408,266,True
|
||||||
|
medium_50x50,DFS,4,1.7798999906517565,408,266,True
|
||||||
|
medium_50x50,DFS,5,1.7468000296503305,408,266,True
|
||||||
|
medium_50x50,DFS,6,1.7085999716073275,408,266,True
|
||||||
|
medium_50x50,DFS,7,1.7067999579012394,408,266,True
|
||||||
|
medium_50x50,A*,1,8.779299969319254,942,94,True
|
||||||
|
medium_50x50,A*,2,9.8794000223279,942,94,True
|
||||||
|
medium_50x50,A*,3,9.167099953629076,942,94,True
|
||||||
|
medium_50x50,A*,4,16.693999990820885,942,94,True
|
||||||
|
medium_50x50,A*,5,12.841499992646277,942,94,True
|
||||||
|
medium_50x50,A*,6,23.23459996841848,942,94,True
|
||||||
|
medium_50x50,A*,7,18.193199997767806,942,94,True
|
||||||
|
no_path_30x30,BFS,1,3.5794000141322613,781,0,False
|
||||||
|
no_path_30x30,BFS,2,3.4125999663956463,781,0,False
|
||||||
|
no_path_30x30,BFS,3,3.811199974734336,781,0,False
|
||||||
|
no_path_30x30,BFS,4,3.524600004311651,781,0,False
|
||||||
|
no_path_30x30,BFS,5,3.535500029101968,781,0,False
|
||||||
|
no_path_30x30,BFS,6,3.6441999836824834,781,0,False
|
||||||
|
no_path_30x30,BFS,7,3.4087999956682324,781,0,False
|
||||||
|
no_path_30x30,DFS,1,3.3659999608062208,781,0,False
|
||||||
|
no_path_30x30,DFS,2,3.659199981484562,781,0,False
|
||||||
|
no_path_30x30,DFS,3,3.4770999918691814,781,0,False
|
||||||
|
no_path_30x30,DFS,4,3.4067999804392457,781,0,False
|
||||||
|
no_path_30x30,DFS,5,3.334700013510883,781,0,False
|
||||||
|
no_path_30x30,DFS,6,3.7082999479025602,781,0,False
|
||||||
|
no_path_30x30,DFS,7,5.642200005240738,781,0,False
|
||||||
|
no_path_30x30,A*,1,7.9901000135578215,781,0,False
|
||||||
|
no_path_30x30,A*,2,8.304700022563338,781,0,False
|
||||||
|
no_path_30x30,A*,3,11.212200042791665,781,0,False
|
||||||
|
no_path_30x30,A*,4,14.160500024445355,781,0,False
|
||||||
|
no_path_30x30,A*,5,9.939100011251867,781,0,False
|
||||||
|
no_path_30x30,A*,6,8.455800008960068,781,0,False
|
||||||
|
no_path_30x30,A*,7,7.638900016900152,781,0,False
|
||||||
|
small_10x10,BFS,1,0.18330005696043372,58,14,True
|
||||||
|
small_10x10,BFS,2,0.14630000805482268,58,14,True
|
||||||
|
small_10x10,BFS,3,0.18289999570697546,58,14,True
|
||||||
|
small_10x10,BFS,4,0.14349998673424125,58,14,True
|
||||||
|
small_10x10,BFS,5,0.14270003885030746,58,14,True
|
||||||
|
small_10x10,BFS,6,0.1477000187151134,58,14,True
|
||||||
|
small_10x10,BFS,7,0.14270003885030746,58,14,True
|
||||||
|
small_10x10,DFS,1,0.042700034100562334,15,14,True
|
||||||
|
small_10x10,DFS,2,0.04120002267882228,15,14,True
|
||||||
|
small_10x10,DFS,3,0.041000021155923605,15,14,True
|
||||||
|
small_10x10,DFS,4,0.04099996294826269,15,14,True
|
||||||
|
small_10x10,DFS,5,0.04079996142536402,15,14,True
|
||||||
|
small_10x10,DFS,6,0.04060001811012626,15,14,True
|
||||||
|
small_10x10,DFS,7,0.04060001811012626,15,14,True
|
||||||
|
small_10x10,A*,1,0.30999997397884727,58,14,True
|
||||||
|
small_10x10,A*,2,0.30529999639838934,58,14,True
|
||||||
|
small_10x10,A*,3,0.325299974065274,58,14,True
|
||||||
|
small_10x10,A*,4,0.31670002499595284,58,14,True
|
||||||
|
small_10x10,A*,5,0.32130000181496143,58,14,True
|
||||||
|
small_10x10,A*,6,0.3467000206001103,58,14,True
|
||||||
|
small_10x10,A*,7,0.33109996002167463,58,14,True
|
||||||
|
16
SmirnovVS/docs/data/2-nd-exercize/maze_results_summary.csv
Normal file
16
SmirnovVS/docs/data/2-nd-exercize/maze_results_summary.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
maze,strategy,mean_time_ms,mean_visited_cells,path_length,path_found
|
||||||
|
empty_50x50,BFS,22.75627143015819,2304,94,True
|
||||||
|
empty_50x50,DFS,0.6384714340258922,95,94,True
|
||||||
|
empty_50x50,A*,60.188757155888844,2304,94,True
|
||||||
|
large_100x100,BFS,67.55744285848257,6409,194,True
|
||||||
|
large_100x100,DFS,17.82360000236492,1561,762,True
|
||||||
|
large_100x100,A*,28.85528571537829,1997,194,True
|
||||||
|
medium_50x50,BFS,7.83954284686063,1700,94,True
|
||||||
|
medium_50x50,DFS,2.0575000006439432,408,266,True
|
||||||
|
medium_50x50,A*,14.112728556418526,942,94,True
|
||||||
|
no_path_30x30,BFS,3.559471424003797,781,0,False
|
||||||
|
no_path_30x30,DFS,3.799185697321913,781,0,False
|
||||||
|
no_path_30x30,A*,9.671614305781466,781,0,False
|
||||||
|
small_10x10,BFS,0.1555857348388859,58,14,True
|
||||||
|
small_10x10,DFS,0.041128576932741065,15,14,True
|
||||||
|
small_10x10,A*,0.3223428502678871,58,14,True
|
||||||
|
35
SmirnovVS/docs/data/2-nd-exercize/plot_results.py
Normal file
35
SmirnovVS/docs/data/2-nd-exercize/plot_results.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
def create_plot(csv_path="docs/data/maze_results_summary.csv", output_path="docs/data/maze_performance.png"):
|
||||||
|
with Path(csv_path).open(encoding="utf-8-sig") as file:
|
||||||
|
rows = list(csv.DictReader(file))
|
||||||
|
mazes = sorted({row["maze"] for row in rows})
|
||||||
|
strategies = ["BFS", "DFS", "A*"]
|
||||||
|
colors = {"BFS": "#4472C4", "DFS": "#ED7D31", "A*": "#70AD47"}
|
||||||
|
figure, axes = plt.subplots(2, 1, figsize=(12, 9))
|
||||||
|
positions = range(len(mazes))
|
||||||
|
width = 0.24
|
||||||
|
for index, strategy in enumerate(strategies):
|
||||||
|
selected = [next(row for row in rows if row["maze"] == maze and row["strategy"] == strategy) for maze in mazes]
|
||||||
|
offsets = [position + (index - 1) * width for position in positions]
|
||||||
|
axes[0].bar(offsets, [float(row["mean_time_ms"]) for row in selected], width, label=strategy, color=colors[strategy])
|
||||||
|
axes[1].bar(offsets, [float(row["mean_visited_cells"]) for row in selected], width, label=strategy, color=colors[strategy])
|
||||||
|
for axis, ylabel, title in zip(axes, ["Время, мс", "Посещено клеток"], ["Среднее время поиска", "Объём исследования лабиринта"]):
|
||||||
|
axis.set_xticks(list(positions), mazes, rotation=20, ha="right")
|
||||||
|
axis.set_ylabel(ylabel)
|
||||||
|
axis.set_title(title)
|
||||||
|
axis.grid(axis="y", alpha=0.25)
|
||||||
|
axis.legend()
|
||||||
|
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()}")
|
||||||
BIN
SmirnovVS/docs/maze_performance.png
Normal file
BIN
SmirnovVS/docs/maze_performance.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
BIN
SmirnovVS/docs/performance.png
Normal file
BIN
SmirnovVS/docs/performance.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
SmirnovVS/docs/Поиск пути в лабиринте.docx
Normal file
BIN
SmirnovVS/docs/Поиск пути в лабиринте.docx
Normal file
Binary file not shown.
BIN
SmirnovVS/docs/Структуры данных.docx
Normal file
BIN
SmirnovVS/docs/Структуры данных.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user