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