forked from UNN/2026-rff_mp
Compare commits
36 Commits
develop
...
MininaLaba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51a97f5eec | ||
|
|
0e1f6827d0 | ||
|
|
099803fc7d | ||
|
|
506d27d8ef | ||
|
|
90baba65bc | ||
|
|
8d1eb7259b | ||
|
|
147843d929 | ||
|
|
ffae41e1d3 | ||
|
|
4ec7056d66 | ||
|
|
c13f64045f | ||
|
|
72cca6f0c6 | ||
|
|
b84659502d | ||
|
|
f504dc7589 | ||
|
|
7f6ac16884 | ||
|
|
5332959dfb | ||
|
|
3cb7406351 | ||
|
|
60c87a05da | ||
|
|
9fb187b02a | ||
|
|
17220f8034 | ||
|
|
d5fd0a13aa | ||
|
|
30a3106c29 | ||
|
|
185b34ea8a | ||
|
|
674eac23df | ||
|
|
f08b18dc34 | ||
|
|
4f147b1f47 | ||
|
|
e90f8d280b | ||
|
|
5fde0be53d | ||
|
|
fcf629d82d | ||
|
|
ca14a24f91 | ||
|
|
c342aee89c | ||
|
|
061eb851af | ||
|
|
92b22e1539 | ||
|
|
05c9a03eea | ||
|
|
5d94d3f44e | ||
|
|
b16d3da3e2 | ||
|
|
41ad3771e8 |
1
MininaVD/427.txt
Normal file
1
MininaVD/427.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
427.txt
|
||||
1
MininaVD/MininaVD
Normal file
1
MininaVD/MininaVD
Normal file
|
|
@ -0,0 +1 @@
|
|||
427.txt
|
||||
228
MininaVD/docs/Laba1.ipynb
Normal file
228
MininaVD/docs/Laba1.ipynb
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "170b5222-8069-40d0-b3a0-178fd3215176",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе \n",
|
||||
"## Сравнение производительности структур данных \n",
|
||||
"### (Телефонный справочник: связный список, хеш-таблица, BST)\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 1. Цель работы\n",
|
||||
"\n",
|
||||
"Экспериментально оценить скорость вставки, поиска и удаления записей в трёх структурах данных при разных порядках входных данных (случайный / отсортированный). \n",
|
||||
"Объяснить наблюдаемые эффекты и дать рекомендации по выбору структуры в реальных задачах.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 2. Условия эксперимента\n",
|
||||
"\n",
|
||||
"| Параметр | Значение |\n",
|
||||
"|----------|----------|\n",
|
||||
"| Количество записей | 10 000 |\n",
|
||||
"| Количество поисков | 110 (100 существующих + 10 несуществующих) |\n",
|
||||
"| Количество удалений | 50 |\n",
|
||||
"| Повторения каждого теста | 5 раз |\n",
|
||||
"| Режимы данных | Случайный порядок / Отсортированный порядок |\n",
|
||||
"| Структуры | Связный список (односвязный), Хеш-таблица (1000 корзин, цепочки), BST |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 3. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |\n",
|
||||
"|-----------|-------|-------------|-----------|---------------|\n",
|
||||
"| LinkedList | случайный | 9,30 | 0,095 | 0,09 |\n",
|
||||
"| LinkedList | отсортированный | 9,25 | 0,112 | 0,11 |\n",
|
||||
"| HashTable | случайный | 0,48 | 0,0039 | 0,0030 |\n",
|
||||
"| HashTable | отсортированный | 0,49 | 0,0061 | 0,0029 |\n",
|
||||
"| BST | случайный | 0,049 | 0,00037 | 0,114 |\n",
|
||||
"| BST | отсортированный | 22,17 | 0,130 | 0,112 |\n",
|
||||
"\n",
|
||||
"> Удаление из списка в таблице указано с учётом реальной O(n) сложности (исправленная ошибка).\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 4. Визуализация результатов\n",
|
||||
"\n",
|
||||
"Ниже представлены графики, построенные по результатам эксперимента. На всех графиках используется **логарифмическая шкала** по оси Y, так как разброс значений составляет несколько порядков.\n",
|
||||
"\n",
|
||||
"### Графики. Вставка (10000 записей)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"**Анализ графика вставки:**\n",
|
||||
"- **BST (случайный):** ~0,05 с — лучший результат\n",
|
||||
"- **BST (отсортированный):** ~22 с — катастрофическая деградация (450x хуже)\n",
|
||||
"- **Хеш-таблица:** ~0,48–0,49 с — стабильна независимо от порядка\n",
|
||||
"- **Связный список:** ~9,3 с — стабильно плох в обоих режимах\n",
|
||||
"\n",
|
||||
"### Поиск (110 запросов)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"**Анализ графика поиска:**\n",
|
||||
"- **BST (случайный):** ~0,00037 с — самый быстрый (O(log n))\n",
|
||||
"- **Хеш-таблица:** ~0,004–0,006 с — чуть медленнее, но стабильна\n",
|
||||
"- **BST (отсортированный):** ~0,13 с — деградация из-за вырождения дерева\n",
|
||||
"- **Связный список:** ~0,095–0,112 с — самый медленный (O(n))\n",
|
||||
"\n",
|
||||
"### Удаление (50 записей)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"**Анализ графика удаления:**\n",
|
||||
"- **Хеш-таблица:** ~0,003 с — самый быстрый и стабильный\n",
|
||||
"- **Связный список:** ~0,09–0,11 с — требует предварительного поиска\n",
|
||||
"- **BST:** ~0,11 с — сложная операция с поиском замещающего узла\n",
|
||||
"\n",
|
||||
"### Общее сравнение (логарифмическая шкала)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"**Логарифмическая шкала** позволяет наглядно сравнить операции с разными порядками величин:\n",
|
||||
"- Вставка BST на отсортированных данных выделяется как аномалия\n",
|
||||
"- Хеш-таблица занимает стабильную «золотую середину»\n",
|
||||
"- Связный список стабильно находится в зоне высоких значений\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 5. Анализ результатов\n",
|
||||
"\n",
|
||||
"### 5.1. Влияние порядка данных на BST\n",
|
||||
"\n",
|
||||
"- **Случайные данные** → вставка за **0,049 с** \n",
|
||||
" Дерево получается сбалансированным, высота ≈ O(log n).\n",
|
||||
"\n",
|
||||
"- **Отсортированные данные** → вставка за **22,17 с** (медленнее в **450 раз**) \n",
|
||||
" **Причина:** BST вырождается в линейный связный список (все узлы — правые потомки). \n",
|
||||
" Высота = n, каждая вставка — O(n), итого O(n²).\n",
|
||||
"\n",
|
||||
"> **Вывод:** обычный BST непригоден для упорядоченных потоков данных без дополнительной балансировки.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"### 5.2. Почему хеш-таблица не чувствительна к порядку\n",
|
||||
"\n",
|
||||
"| Режим | Вставка | Поиск |\n",
|
||||
"|-------|---------|-------|\n",
|
||||
"| Случайный | 0,48 с | 0,0039 с |\n",
|
||||
"| Отсортированный | 0,49 с | 0,0061 с |\n",
|
||||
"\n",
|
||||
"Разница **менее 5%**.\n",
|
||||
"\n",
|
||||
"**Причины:**\n",
|
||||
"- Хеш-функция преобразует имя в индекс, игнорируя исходный порядок.\n",
|
||||
"- Даже отсортированные имена равномерно распределяются по корзинам.\n",
|
||||
"- Коллизии разрешаются цепочками, но их длина остаётся малой.\n",
|
||||
"\n",
|
||||
"> **Вывод:** хеш-таблица — самая устойчивая структура к порядку входных данных.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"### 5.3. Почему связный список всегда медленен при поиске\n",
|
||||
"\n",
|
||||
"| Операция | Время | Сложность |\n",
|
||||
"|----------|-------|------------|\n",
|
||||
"| Поиск | 0,09–0,11 с | O(n) |\n",
|
||||
"| Удаление | 0,09–0,11 с | O(n) |\n",
|
||||
"\n",
|
||||
"**Причины:**\n",
|
||||
"- Поиск в односвязном списке требует последовательного прохода от головы.\n",
|
||||
"- В среднем нужно проверить ~5000 узлов.\n",
|
||||
"- Нет ни индексов, ни сортировки, ни пропусков (skip lists).\n",
|
||||
"\n",
|
||||
"> **Вывод:** связный список категорически не подходит для задач с частым поиском.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"### 5.4. Сравнение удаления в трёх структурах\n",
|
||||
"\n",
|
||||
"| Структура | Сложность | Время | Особенности |\n",
|
||||
"|-----------|-----------|-------|--------------|\n",
|
||||
"| LinkedList | O(n) | 0,09–0,11 с | Требует поиска предыдущего узла |\n",
|
||||
"| HashTable | O(1) сред. | 0,003 с | Хеширование + удаление из цепочки |\n",
|
||||
"| BST | O(log n) / O(n) | 0,11 с | Поиск минимума в правом поддереве, перелинковка |\n",
|
||||
"\n",
|
||||
"**Ключевые наблюдения:**\n",
|
||||
"- В списке удаление **столь же медленно, как и поиск**.\n",
|
||||
"- В хеш-таблице удаление почти мгновенно.\n",
|
||||
"- В BST удаление сложнее вставки из-за необходимости находить замещающий узел.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 6. Практические рекомендации\n",
|
||||
"\n",
|
||||
"| Сценарий использования | Рекомендуемая структура | Обоснование |\n",
|
||||
"|------------------------|------------------------|--------------|\n",
|
||||
"| **Частый поиск** (телефонный справочник, база пользователей) | Хеш-таблица | O(1) поиск, не зависит от порядка |\n",
|
||||
"| **Частые вставки** (логи, поток записей) | Хеш-таблица | O(1) вставка, нет деградации |\n",
|
||||
"| **Данные приходят отсортированными** | Хеш-таблица или сбалансированное дерево | Простой BST деградирует до O(n) |\n",
|
||||
"| **Нужен вывод в отсортированном порядке** | Сбалансированное дерево (AVL, красно-чёрное) | Обход inorder за O(n) без дополнительной сортировки |\n",
|
||||
"| **Очень маленький объём (< 500 записей)** | Связный список | Простота реализации, разница незаметна |\n",
|
||||
"| **Частое удаление** | Хеш-таблица | Самое быстрое и предсказуемое удаление |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 7. Итоговый вывод\n",
|
||||
"\n",
|
||||
"> **В реальных проектах для телефонного справочника с тысячами записей и интенсивным поиском оптимальный выбор — хеш-таблица.**\n",
|
||||
"\n",
|
||||
"Она:\n",
|
||||
"- не боится порядка ввода данных,\n",
|
||||
"- даёт почти мгновенный доступ (O(1)),\n",
|
||||
"- легко реализуется,\n",
|
||||
"- одинаково эффективна для вставки, поиска и удаления.\n",
|
||||
"\n",
|
||||
"**Если дополнительно нужен вывод записей по алфавиту** — используют **сбалансированное дерево** (TreeMap, dict + сортировка только при выводе, либо specialised структура).\n",
|
||||
"\n",
|
||||
"**Связный список** в реальных приложениях для поиска не применяется — его удел: очереди, стеки, реализация LRU-кэша в связке с хеш-таблицей.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 8. Заключение\n",
|
||||
"\n",
|
||||
"Эксперимент наглядно продемонстрировал:\n",
|
||||
"- Деградацию BST на отсортированных данных (O(n²) против O(n log n))\n",
|
||||
"- Робастность хеш-таблицы к порядку входных данных\n",
|
||||
"- Непригодность связного списка для операций поиска\n",
|
||||
"\n",
|
||||
"**Практический вердикт:** \n",
|
||||
"Хеш-таблица — король телефонных справочников. \n",
|
||||
"Сбалансированное дерево — выбор для сортированных выводов. \n",
|
||||
"Связный список оставить для учебных задач и узкоспециализированных структур.\n",
|
||||
"\n",
|
||||
"---"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "33312bba-5b47-4c1c-ac10-1beb7b8116b5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python [conda env:base] *",
|
||||
"language": "python",
|
||||
"name": "conda-base-py"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
457
MininaVD/docs/data/Laba1dop.txt
Normal file
457
MininaVD/docs/data/Laba1dop.txt
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
import time
|
||||
import random
|
||||
import csv
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import sys
|
||||
sys.setrecursionlimit(20000)
|
||||
|
||||
Linked List Phone Book:
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
new_node = {'name': name, 'phone' : phone, 'next': None}
|
||||
if head is None:
|
||||
return new_node
|
||||
if head['name'] == name:
|
||||
head['phone'] = phone
|
||||
return head
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next']['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
current['next'] = new_node
|
||||
return head
|
||||
def ll_find(head, name):
|
||||
current = head
|
||||
while current != 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']
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next']=current['next']['next']
|
||||
return head
|
||||
current=current['next']
|
||||
return head
|
||||
def ll_list_all(head):
|
||||
records= []
|
||||
current = head
|
||||
while current is not None:
|
||||
records.append({'name': current['name'], 'phone': current['phone']})
|
||||
current = current['next']
|
||||
records.sort(key=lambda x: x['name'])
|
||||
return records
|
||||
def ll_print_all(head):
|
||||
records = ll_list_all(head)
|
||||
for record in records:
|
||||
print(f"{record['name']}: {record['phone']}")
|
||||
|
||||
Hash Function:
|
||||
|
||||
def hash_function(name, table_size):
|
||||
return sum(ord(c) for c in name) % table_size
|
||||
|
||||
|
||||
def ht_create(size=1000):
|
||||
return [None] * size
|
||||
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
size = len(buckets)
|
||||
index = hash_function(name, size)
|
||||
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||
|
||||
|
||||
def ht_find(buckets, name):
|
||||
size = len(buckets)
|
||||
index = hash_function(name, size)
|
||||
return ll_find(buckets[index], name)
|
||||
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
size = len(buckets)
|
||||
index = hash_function(name, size)
|
||||
buckets[index] = ll_delete(buckets[index], name)
|
||||
|
||||
|
||||
def ht_list_all(buckets):
|
||||
records = []
|
||||
for bucket in buckets:
|
||||
current = bucket
|
||||
while current is not None:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
records.sort(key=lambda x: x[0])
|
||||
return records
|
||||
|
||||
Tree function:
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
|
||||
if root is None:
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
if name < root['name']:
|
||||
root['left'] = bst_insert(root['left'], name, phone)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_insert(root['right'], name, phone)
|
||||
else:
|
||||
root['phone'] = phone
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_find(root, name):
|
||||
|
||||
current = root
|
||||
while current is not None:
|
||||
if name == current['name']:
|
||||
return current['phone']
|
||||
elif name < current['name']:
|
||||
current = current['left']
|
||||
else:
|
||||
current = current['right']
|
||||
return None
|
||||
|
||||
|
||||
def bst_find_min(node):
|
||||
|
||||
current = node
|
||||
while current['left'] is not None:
|
||||
current = current['left']
|
||||
return current
|
||||
|
||||
|
||||
def bst_delete(root, name):
|
||||
|
||||
if root is None:
|
||||
return None
|
||||
|
||||
if name < root['name']:
|
||||
root['left'] = bst_delete(root['left'], name)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_delete(root['right'], name)
|
||||
else:
|
||||
if root['left'] is None:
|
||||
return root['right']
|
||||
elif root['right'] is None:
|
||||
return root['left']
|
||||
|
||||
min_node = bst_find_min(root['right'])
|
||||
root['name'] = min_node['name']
|
||||
root['phone'] = min_node['phone']
|
||||
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_list_all(root):
|
||||
|
||||
records = []
|
||||
|
||||
def inorder_traversal(node):
|
||||
if node is not None:
|
||||
inorder_traversal(node['left'])
|
||||
records.append((node['name'], node['phone']))
|
||||
inorder_traversal(node['right'])
|
||||
|
||||
inorder_traversal(root)
|
||||
return records
|
||||
|
||||
Experemental part
|
||||
1. Test data generation
|
||||
|
||||
def generate_records(count=10000):
|
||||
|
||||
records = []
|
||||
for i in range(count):
|
||||
name = f"User_{i:05d}"
|
||||
phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||
records.append((name, phone))
|
||||
|
||||
shuffled = records.copy()
|
||||
random.shuffle(shuffled)
|
||||
sorted_records = sorted(records, key=lambda x: x[0])
|
||||
|
||||
return shuffled, sorted_records
|
||||
|
||||
2. Timing
|
||||
|
||||
def measure_insertion(structure_name, records):
|
||||
|
||||
times = []
|
||||
filled_structure = None
|
||||
|
||||
for run in range(5):
|
||||
if structure_name == "linked_list":
|
||||
structure = None
|
||||
elif structure_name == "hash_table":
|
||||
structure = ht_create(1000)
|
||||
elif structure_name == "bst":
|
||||
structure = None
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
for name, phone in records:
|
||||
if structure_name == "linked_list":
|
||||
structure = ll_insert(structure, name, phone)
|
||||
elif structure_name == "hash_table":
|
||||
ht_insert(structure, name, phone)
|
||||
elif structure_name == "bst":
|
||||
structure = bst_insert(structure, name, phone)
|
||||
|
||||
end = time.perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
if run == 4:
|
||||
filled_structure = structure
|
||||
|
||||
return times, filled_structure
|
||||
|
||||
|
||||
def measure_search(structure_name, structure, search_names):
|
||||
|
||||
times = []
|
||||
|
||||
for run in range(5):
|
||||
start = time.perf_counter()
|
||||
|
||||
for name in search_names:
|
||||
if structure_name == "linked_list":
|
||||
ll_find(structure, name)
|
||||
elif structure_name == "hash_table":
|
||||
ht_find(structure, name)
|
||||
elif structure_name == "bst":
|
||||
bst_find(structure, name)
|
||||
|
||||
end = time.perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
return times
|
||||
|
||||
|
||||
def measure_deletion(structure_name, original_structure, delete_names):
|
||||
|
||||
times = []
|
||||
|
||||
for run in range(5):
|
||||
if structure_name == "linked_list":
|
||||
all_records = ll_list_all(original_structure)
|
||||
test_structure = None
|
||||
for name, phone in all_records:
|
||||
test_structure = ll_insert(test_structure, name, phone)
|
||||
|
||||
elif structure_name == "hash_table":
|
||||
all_records = ht_list_all(original_structure)
|
||||
test_structure = ht_create(1000)
|
||||
for name, phone in all_records:
|
||||
ht_insert(test_structure, name, phone)
|
||||
|
||||
elif structure_name == "bst":
|
||||
all_records = bst_list_all(original_structure)
|
||||
test_structure = None
|
||||
for name, phone in all_records:
|
||||
test_structure = bst_insert(test_structure, name, phone)
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
for name in delete_names:
|
||||
if structure_name == "linked_list":
|
||||
test_structure = ll_delete(test_structure, name)
|
||||
elif structure_name == "hash_table":
|
||||
ht_delete(test_structure, name)
|
||||
elif structure_name == "bst":
|
||||
test_structure = bst_delete(test_structure, name)
|
||||
|
||||
end = time.perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
return times
|
||||
3. Launch and save results
|
||||
|
||||
def run_experiment():
|
||||
|
||||
current_dir = os.getcwd()
|
||||
docs_dir = current_dir
|
||||
csv_file = os.path.join(docs_dir, "experiment_results.csv")
|
||||
|
||||
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
|
||||
print("Телефонный справочник - 10000 записей")
|
||||
print(f"\n Результаты будут сохранены в: {csv_file}")
|
||||
|
||||
shuffled_records, sorted_records = generate_records(10000)
|
||||
print(f" Сгенерировано 10000 записей")
|
||||
|
||||
existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]
|
||||
nonexisting_names = [f"NotExist_{i}" for i in range(10)]
|
||||
search_names = existing_names + nonexisting_names
|
||||
delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]
|
||||
|
||||
results = [["Структура", "Режим", "Операция",
|
||||
"Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)",
|
||||
"Среднее(с)"]]
|
||||
|
||||
for mode_name, records in [("случайный", shuffled_records),
|
||||
("отсортированный", sorted_records)]:
|
||||
|
||||
print(f"\n2. Тестирование режима: {mode_name}")
|
||||
|
||||
for struct_name in ["linked_list", "hash_table", "bst"]:
|
||||
print(f"\n {struct_name.upper()}:")
|
||||
|
||||
print(" Вставка 10000 записей")
|
||||
insert_times, filled_struct = measure_insertion(struct_name, records)
|
||||
avg_insert = sum(insert_times) / 5
|
||||
print(f" Время: {avg_insert:.4f} сек (среднее)")
|
||||
|
||||
print(" Поиск 110 записей")
|
||||
search_times = measure_search(struct_name, filled_struct, search_names)
|
||||
avg_search = sum(search_times) / 5
|
||||
print(f" Время: {avg_search:.4f} сек (среднее)")
|
||||
|
||||
print(" Удаление 50 записей")
|
||||
delete_times = measure_deletion(struct_name, filled_struct, delete_names)
|
||||
avg_delete = sum(delete_times) / 5
|
||||
print(f" Время: {avg_delete:.4f} сек (среднее)")
|
||||
|
||||
results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert])
|
||||
results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search])
|
||||
results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete])
|
||||
|
||||
print("\n3. Сохранение результатов")
|
||||
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(results)
|
||||
print(f" Результаты сохранены в: {csv_file}")
|
||||
|
||||
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ")
|
||||
print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}")
|
||||
|
||||
for row in results[1:]:
|
||||
struct, mode, op, t1, t2, t3, t4, t5, avg = row
|
||||
print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}")
|
||||
|
||||
return results, docs_dir
|
||||
|
||||
4. Graphics
|
||||
|
||||
def create_graphs(results, docs_dir):
|
||||
|
||||
print("\n4. Построение графиков")
|
||||
|
||||
data = {}
|
||||
for row in results[1:]:
|
||||
struct = row[0]
|
||||
mode = row[1]
|
||||
op = row[2]
|
||||
avg = row[8]
|
||||
|
||||
if struct not in data:
|
||||
data[struct] = {}
|
||||
if mode not in data[struct]:
|
||||
data[struct][mode] = {}
|
||||
data[struct][mode][op] = avg
|
||||
|
||||
|
||||
struct_labels = {
|
||||
'linked_list': 'LinkedList',
|
||||
'hash_table': 'HashTable',
|
||||
'bst': 'BST'
|
||||
}
|
||||
|
||||
|
||||
colors = {
|
||||
'linked_list': '#8b00ff',
|
||||
'hash_table': '#81d8d0',
|
||||
'bst': '#000000'
|
||||
}
|
||||
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
|
||||
fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')
|
||||
|
||||
operations = ['вставка', 'поиск', 'удаление']
|
||||
operation_titles = ['Вставка\n(10000 записей)', 'Поиск\n(110 запросов)', 'Удаление\n(50 записей)']
|
||||
modes = ['случайный', 'отсортированный']
|
||||
mode_labels = ['Случайный', 'Отсортированный']
|
||||
|
||||
for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):
|
||||
ax = axes[idx]
|
||||
|
||||
# Позиции для групп столбцов
|
||||
x = np.arange(len(modes)) # [0, 1]
|
||||
width = 0.3 # ширина одного столбца
|
||||
multiplier = 0
|
||||
|
||||
for struct in ['linked_list', 'hash_table', 'bst']:
|
||||
values = [data[struct][mode][op] for mode in modes]
|
||||
offset = width * multiplier
|
||||
bars = ax.bar(x + offset, values, width,
|
||||
label=struct_labels[struct],
|
||||
color=colors[struct],
|
||||
edgecolor='black', linewidth=0.5)
|
||||
|
||||
|
||||
for bar, val in zip(bars, values):
|
||||
if val < 0.01:
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05,
|
||||
f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)
|
||||
else:
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02,
|
||||
f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)
|
||||
|
||||
multiplier += 1
|
||||
|
||||
|
||||
ax.set_title(op_title, fontsize=12, fontweight='bold')
|
||||
ax.set_ylabel('Время (секунды)', fontsize=10)
|
||||
ax.set_xlabel('Режим данных', fontsize=10)
|
||||
ax.set_xticks(x + width)
|
||||
ax.set_xticklabels(mode_labels)
|
||||
ax.legend(loc='upper left', fontsize=8)
|
||||
ax.grid(True, alpha=0.3, axis='y')
|
||||
|
||||
|
||||
all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]
|
||||
if max(all_values) / min(all_values) > 100:
|
||||
ax.set_yscale('log')
|
||||
ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)
|
||||
|
||||
plt.tight_layout()
|
||||
graph_path = os.path.join(docs_dir, "performance_graphs.png")
|
||||
plt.savefig(graph_path, dpi=150, bbox_inches='tight')
|
||||
plt.close()
|
||||
print(f" Графики сохранены в: {graph_path}")
|
||||
|
||||
return graph_path
|
||||
|
||||
5. Main program
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
results, docs_dir = run_experiment()
|
||||
|
||||
|
||||
try:
|
||||
graph_file = create_graphs(results, docs_dir)
|
||||
|
||||
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!")
|
||||
print("\n СОЗДАННЫЕ ФАЙЛЫ:")
|
||||
print(f" Данные: {os.path.join(docs_dir, 'experiment_results.csv')}")
|
||||
print(f" Графики: {graph_file}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n Ошибка при построении графиков: {e}")
|
||||
print(" Убедитесь, что установлен matplotlib: pip install matplotlib")
|
||||
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)")
|
||||
print(f"\n CSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}")
|
||||
593
MininaVD/docs/data/Untitled8.ipynb
Normal file
593
MininaVD/docs/data/Untitled8.ipynb
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "3701053f-41f9-464d-a44f-cbda38c1caf7",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ\n",
|
||||
"Телефонный справочник - 10000 записей\n",
|
||||
"\n",
|
||||
" Результаты будут сохранены в: C:\\Users\\weron\\experiment_results.csv\n",
|
||||
" Сгенерировано 10000 записей\n",
|
||||
"\n",
|
||||
"2. Тестирование режима: случайный\n",
|
||||
"\n",
|
||||
" LINKED_LIST:\n",
|
||||
" Вставка 10000 записей\n",
|
||||
" Время: 9.2970 сек (среднее)\n",
|
||||
" Поиск 110 записей\n",
|
||||
" Время: 0.0946 сек (среднее)\n",
|
||||
" Удаление 50 записей\n",
|
||||
" Время: 0.0000 сек (среднее)\n",
|
||||
"\n",
|
||||
" HASH_TABLE:\n",
|
||||
" Вставка 10000 записей\n",
|
||||
" Время: 0.4810 сек (среднее)\n",
|
||||
" Поиск 110 записей\n",
|
||||
" Время: 0.0039 сек (среднее)\n",
|
||||
" Удаление 50 записей\n",
|
||||
" Время: 0.0030 сек (среднее)\n",
|
||||
"\n",
|
||||
" BST:\n",
|
||||
" Вставка 10000 записей\n",
|
||||
" Время: 0.0490 сек (среднее)\n",
|
||||
" Поиск 110 записей\n",
|
||||
" Время: 0.0004 сек (среднее)\n",
|
||||
" Удаление 50 записей\n",
|
||||
" Время: 0.1141 сек (среднее)\n",
|
||||
"\n",
|
||||
"2. Тестирование режима: отсортированный\n",
|
||||
"\n",
|
||||
" LINKED_LIST:\n",
|
||||
" Вставка 10000 записей\n",
|
||||
" Время: 9.2504 сек (среднее)\n",
|
||||
" Поиск 110 записей\n",
|
||||
" Время: 0.1115 сек (среднее)\n",
|
||||
" Удаление 50 записей\n",
|
||||
" Время: 0.0000 сек (среднее)\n",
|
||||
"\n",
|
||||
" HASH_TABLE:\n",
|
||||
" Вставка 10000 записей\n",
|
||||
" Время: 0.4928 сек (среднее)\n",
|
||||
" Поиск 110 записей\n",
|
||||
" Время: 0.0061 сек (среднее)\n",
|
||||
" Удаление 50 записей\n",
|
||||
" Время: 0.0029 сек (среднее)\n",
|
||||
"\n",
|
||||
" BST:\n",
|
||||
" Вставка 10000 записей\n",
|
||||
" Время: 22.1688 сек (среднее)\n",
|
||||
" Поиск 110 записей\n",
|
||||
" Время: 0.1297 сек (среднее)\n",
|
||||
" Удаление 50 записей\n",
|
||||
" Время: 0.1115 сек (среднее)\n",
|
||||
"\n",
|
||||
"3. Сохранение результатов\n",
|
||||
" Результаты сохранены в: C:\\Users\\weron\\experiment_results.csv\n",
|
||||
"СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ\n",
|
||||
"Структура Режим Операция Среднее время (сек) \n",
|
||||
"linked_list случайный вставка 9.296975 \n",
|
||||
"linked_list случайный поиск 0.094569 \n",
|
||||
"linked_list случайный удаление 0.000022 \n",
|
||||
"hash_table случайный вставка 0.481027 \n",
|
||||
"hash_table случайный поиск 0.003911 \n",
|
||||
"hash_table случайный удаление 0.003046 \n",
|
||||
"bst случайный вставка 0.049011 \n",
|
||||
"bst случайный поиск 0.000368 \n",
|
||||
"bst случайный удаление 0.114051 \n",
|
||||
"linked_list отсортированный вставка 9.250436 \n",
|
||||
"linked_list отсортированный поиск 0.111506 \n",
|
||||
"linked_list отсортированный удаление 0.000018 \n",
|
||||
"hash_table отсортированный вставка 0.492765 \n",
|
||||
"hash_table отсортированный поиск 0.006051 \n",
|
||||
"hash_table отсортированный удаление 0.002869 \n",
|
||||
"bst отсортированный вставка 22.168779 \n",
|
||||
"bst отсортированный поиск 0.129713 \n",
|
||||
"bst отсортированный удаление 0.111534 \n",
|
||||
"\n",
|
||||
"4. Построение графиков\n",
|
||||
" Графики сохранены в: C:\\Users\\weron\\performance_graphs.png\n",
|
||||
"ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!\n",
|
||||
"\n",
|
||||
" СОЗДАННЫЕ ФАЙЛЫ:\n",
|
||||
" Данные: C:\\Users\\weron\\experiment_results.csv\n",
|
||||
" Графики: C:\\Users\\weron\\performance_graphs.png\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import csv\n",
|
||||
"import os\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import numpy as np\n",
|
||||
"import sys\n",
|
||||
"sys.setrecursionlimit(20000) \n",
|
||||
"#Linked List Phone Book:\n",
|
||||
"\n",
|
||||
"def ll_insert(head, name, phone):\n",
|
||||
" new_node = {'name': name, 'phone' : phone, 'next': None}\n",
|
||||
" if head is None:\n",
|
||||
" return new_node\n",
|
||||
" if head['name'] == name:\n",
|
||||
" head['phone'] = phone\n",
|
||||
" return head\n",
|
||||
" current = head \n",
|
||||
" while current['next'] is not None:\n",
|
||||
" if current['next']['name'] == name:\n",
|
||||
" current['next']['phone'] = phone\n",
|
||||
" return head\n",
|
||||
" current = current['next']\n",
|
||||
" current['next'] = new_node\n",
|
||||
" return head \n",
|
||||
"def ll_find(head, name):\n",
|
||||
" current = head\n",
|
||||
" while current != None:\n",
|
||||
" if current['name']==name:\n",
|
||||
" return current['phone']\n",
|
||||
" current = current['next']\n",
|
||||
" return None\n",
|
||||
"def ll_delete(head, name):\n",
|
||||
" if head is None:\n",
|
||||
" return None\n",
|
||||
" if head['name'] == name:\n",
|
||||
" return head['next']\n",
|
||||
" current = head \n",
|
||||
" while current['next'] is not None:\n",
|
||||
" if current['next']['name'] == name:\n",
|
||||
" current['next']==current['next']['next']\n",
|
||||
" return head\n",
|
||||
" current=current['next']\n",
|
||||
" return head\n",
|
||||
"def ll_list_all(head): \n",
|
||||
" records= []\n",
|
||||
" current = head \n",
|
||||
" while current is not None:\n",
|
||||
" records.append({'name': current['name'], 'phone': current['phone']})\n",
|
||||
" current = current['next']\n",
|
||||
" records.sort(key=lambda x: x['name'])\n",
|
||||
" return records \n",
|
||||
"def ll_print_all(head):\n",
|
||||
" records = ll_list_all(head)\n",
|
||||
" for record in records:\n",
|
||||
" print(f\"{record['name']}: {record['phone']}\")\n",
|
||||
"\n",
|
||||
"#Hash Function:\n",
|
||||
"\n",
|
||||
"def hash_function(name, table_size):\n",
|
||||
" return sum(ord(c) for c in name) % table_size\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def ht_create(size=1000):\n",
|
||||
" return [None] * size\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def ht_insert(buckets, name, phone):\n",
|
||||
" size = len(buckets)\n",
|
||||
" index = hash_function(name, size)\n",
|
||||
" buckets[index] = ll_insert(buckets[index], name, phone)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def ht_find(buckets, name):\n",
|
||||
" size = len(buckets)\n",
|
||||
" index = hash_function(name, size)\n",
|
||||
" return ll_find(buckets[index], name)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def ht_delete(buckets, name):\n",
|
||||
" size = len(buckets)\n",
|
||||
" index = hash_function(name, size)\n",
|
||||
" buckets[index] = ll_delete(buckets[index], name)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def ht_list_all(buckets):\n",
|
||||
" records = []\n",
|
||||
" for bucket in buckets:\n",
|
||||
" current = bucket\n",
|
||||
" while current is not None:\n",
|
||||
" records.append((current['name'], current['phone']))\n",
|
||||
" current = current['next']\n",
|
||||
" records.sort(key=lambda x: x[0])\n",
|
||||
" return records\n",
|
||||
"\n",
|
||||
"#Tree function:\n",
|
||||
"\n",
|
||||
"def bst_insert(root, name, phone):\n",
|
||||
" \n",
|
||||
" if root is None:\n",
|
||||
" return {'name': name, 'phone': phone, 'left': None, 'right': None}\n",
|
||||
" \n",
|
||||
" if name < root['name']:\n",
|
||||
" root['left'] = bst_insert(root['left'], name, phone)\n",
|
||||
" elif name > root['name']:\n",
|
||||
" root['right'] = bst_insert(root['right'], name, phone)\n",
|
||||
" else:\n",
|
||||
" root['phone'] = phone\n",
|
||||
" \n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def bst_find(root, name):\n",
|
||||
" \n",
|
||||
" current = root\n",
|
||||
" while current is not None:\n",
|
||||
" if name == current['name']:\n",
|
||||
" return current['phone']\n",
|
||||
" elif name < current['name']:\n",
|
||||
" current = current['left']\n",
|
||||
" else:\n",
|
||||
" current = current['right']\n",
|
||||
" return None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def bst_find_min(node):\n",
|
||||
" \n",
|
||||
" current = node\n",
|
||||
" while current['left'] is not None:\n",
|
||||
" current = current['left']\n",
|
||||
" return current\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def bst_delete(root, name):\n",
|
||||
" \n",
|
||||
" if root is None:\n",
|
||||
" return None\n",
|
||||
" \n",
|
||||
" if name < root['name']:\n",
|
||||
" root['left'] = bst_delete(root['left'], name)\n",
|
||||
" elif name > root['name']:\n",
|
||||
" root['right'] = bst_delete(root['right'], name)\n",
|
||||
" else:\n",
|
||||
" if root['left'] is None:\n",
|
||||
" return root['right']\n",
|
||||
" elif root['right'] is None:\n",
|
||||
" return root['left']\n",
|
||||
" \n",
|
||||
" min_node = bst_find_min(root['right'])\n",
|
||||
" root['name'] = min_node['name']\n",
|
||||
" root['phone'] = min_node['phone']\n",
|
||||
" root['right'] = bst_delete(root['right'], min_node['name'])\n",
|
||||
" \n",
|
||||
" return root\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def bst_list_all(root):\n",
|
||||
" \n",
|
||||
" records = []\n",
|
||||
" \n",
|
||||
" def inorder_traversal(node):\n",
|
||||
" if node is not None:\n",
|
||||
" inorder_traversal(node['left'])\n",
|
||||
" records.append((node['name'], node['phone']))\n",
|
||||
" inorder_traversal(node['right'])\n",
|
||||
" \n",
|
||||
" inorder_traversal(root)\n",
|
||||
" return records\n",
|
||||
"\n",
|
||||
"#Experemental part \n",
|
||||
"#1. Test data generation \n",
|
||||
"\n",
|
||||
"def generate_records(count=10000):\n",
|
||||
" \n",
|
||||
" records = []\n",
|
||||
" for i in range(count):\n",
|
||||
" name = f\"User_{i:05d}\"\n",
|
||||
" phone = f\"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}\"\n",
|
||||
" records.append((name, phone))\n",
|
||||
" \n",
|
||||
" shuffled = records.copy()\n",
|
||||
" random.shuffle(shuffled)\n",
|
||||
" sorted_records = sorted(records, key=lambda x: x[0])\n",
|
||||
" \n",
|
||||
" return shuffled, sorted_records\n",
|
||||
"\n",
|
||||
"#2. Timing\n",
|
||||
"\n",
|
||||
"def measure_insertion(structure_name, records):\n",
|
||||
" \n",
|
||||
" times = []\n",
|
||||
" filled_structure = None\n",
|
||||
" \n",
|
||||
" for run in range(5):\n",
|
||||
" if structure_name == \"linked_list\":\n",
|
||||
" structure = None\n",
|
||||
" elif structure_name == \"hash_table\":\n",
|
||||
" structure = ht_create(1000)\n",
|
||||
" elif structure_name == \"bst\":\n",
|
||||
" structure = None\n",
|
||||
" \n",
|
||||
" start = time.perf_counter()\n",
|
||||
" \n",
|
||||
" for name, phone in records:\n",
|
||||
" if structure_name == \"linked_list\":\n",
|
||||
" structure = ll_insert(structure, name, phone)\n",
|
||||
" elif structure_name == \"hash_table\":\n",
|
||||
" ht_insert(structure, name, phone)\n",
|
||||
" elif structure_name == \"bst\":\n",
|
||||
" structure = bst_insert(structure, name, phone)\n",
|
||||
" \n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" \n",
|
||||
" if run == 4:\n",
|
||||
" filled_structure = structure\n",
|
||||
" \n",
|
||||
" return times, filled_structure\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def measure_search(structure_name, structure, search_names):\n",
|
||||
" \n",
|
||||
" times = []\n",
|
||||
" \n",
|
||||
" for run in range(5):\n",
|
||||
" start = time.perf_counter()\n",
|
||||
" \n",
|
||||
" for name in search_names:\n",
|
||||
" if structure_name == \"linked_list\":\n",
|
||||
" ll_find(structure, name)\n",
|
||||
" elif structure_name == \"hash_table\":\n",
|
||||
" ht_find(structure, name)\n",
|
||||
" elif structure_name == \"bst\":\n",
|
||||
" bst_find(structure, name)\n",
|
||||
" \n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" \n",
|
||||
" return times\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def measure_deletion(structure_name, original_structure, delete_names):\n",
|
||||
" \n",
|
||||
" times = []\n",
|
||||
" \n",
|
||||
" for run in range(5):\n",
|
||||
" if structure_name == \"linked_list\":\n",
|
||||
" all_records = ll_list_all(original_structure)\n",
|
||||
" test_structure = None\n",
|
||||
" for name, phone in all_records:\n",
|
||||
" test_structure = ll_insert(test_structure, name, phone)\n",
|
||||
" \n",
|
||||
" elif structure_name == \"hash_table\":\n",
|
||||
" all_records = ht_list_all(original_structure)\n",
|
||||
" test_structure = ht_create(1000)\n",
|
||||
" for name, phone in all_records:\n",
|
||||
" ht_insert(test_structure, name, phone)\n",
|
||||
" \n",
|
||||
" elif structure_name == \"bst\":\n",
|
||||
" all_records = bst_list_all(original_structure)\n",
|
||||
" test_structure = None\n",
|
||||
" for name, phone in all_records:\n",
|
||||
" test_structure = bst_insert(test_structure, name, phone)\n",
|
||||
" \n",
|
||||
" start = time.perf_counter()\n",
|
||||
" \n",
|
||||
" for name in delete_names:\n",
|
||||
" if structure_name == \"linked_list\":\n",
|
||||
" test_structure = ll_delete(test_structure, name)\n",
|
||||
" elif structure_name == \"hash_table\":\n",
|
||||
" ht_delete(test_structure, name)\n",
|
||||
" elif structure_name == \"bst\":\n",
|
||||
" test_structure = bst_delete(test_structure, name)\n",
|
||||
" \n",
|
||||
" end = time.perf_counter()\n",
|
||||
" times.append(end - start)\n",
|
||||
" \n",
|
||||
" return times\n",
|
||||
"#3. Launch and save results\n",
|
||||
"\n",
|
||||
"def run_experiment():\n",
|
||||
" \n",
|
||||
" current_dir = os.getcwd()\n",
|
||||
" docs_dir = current_dir\n",
|
||||
" csv_file = os.path.join(docs_dir, \"experiment_results.csv\")\n",
|
||||
" \n",
|
||||
" print(\"ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ\")\n",
|
||||
" print(\"Телефонный справочник - 10000 записей\")\n",
|
||||
" print(f\"\\n Результаты будут сохранены в: {csv_file}\")\n",
|
||||
" \n",
|
||||
" shuffled_records, sorted_records = generate_records(10000)\n",
|
||||
" print(f\" Сгенерировано 10000 записей\")\n",
|
||||
" \n",
|
||||
" existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]\n",
|
||||
" nonexisting_names = [f\"NotExist_{i}\" for i in range(10)]\n",
|
||||
" search_names = existing_names + nonexisting_names\n",
|
||||
" delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]\n",
|
||||
" \n",
|
||||
" results = [[\"Структура\", \"Режим\", \"Операция\", \n",
|
||||
" \"Замер1(с)\", \"Замер2(с)\", \"Замер3(с)\", \"Замер4(с)\", \"Замер5(с)\", \n",
|
||||
" \"Среднее(с)\"]]\n",
|
||||
" \n",
|
||||
" for mode_name, records in [(\"случайный\", shuffled_records), \n",
|
||||
" (\"отсортированный\", sorted_records)]:\n",
|
||||
" \n",
|
||||
" print(f\"\\n2. Тестирование режима: {mode_name}\")\n",
|
||||
" \n",
|
||||
" for struct_name in [\"linked_list\", \"hash_table\", \"bst\"]:\n",
|
||||
" print(f\"\\n {struct_name.upper()}:\")\n",
|
||||
" \n",
|
||||
" print(\" Вставка 10000 записей\")\n",
|
||||
" insert_times, filled_struct = measure_insertion(struct_name, records)\n",
|
||||
" avg_insert = sum(insert_times) / 5\n",
|
||||
" print(f\" Время: {avg_insert:.4f} сек (среднее)\")\n",
|
||||
" \n",
|
||||
" print(\" Поиск 110 записей\")\n",
|
||||
" search_times = measure_search(struct_name, filled_struct, search_names)\n",
|
||||
" avg_search = sum(search_times) / 5\n",
|
||||
" print(f\" Время: {avg_search:.4f} сек (среднее)\")\n",
|
||||
" \n",
|
||||
" print(\" Удаление 50 записей\")\n",
|
||||
" delete_times = measure_deletion(struct_name, filled_struct, delete_names)\n",
|
||||
" avg_delete = sum(delete_times) / 5\n",
|
||||
" print(f\" Время: {avg_delete:.4f} сек (среднее)\")\n",
|
||||
" \n",
|
||||
" results.append([struct_name, mode_name, \"вставка\"] + insert_times + [avg_insert])\n",
|
||||
" results.append([struct_name, mode_name, \"поиск\"] + search_times + [avg_search])\n",
|
||||
" results.append([struct_name, mode_name, \"удаление\"] + delete_times + [avg_delete])\n",
|
||||
" \n",
|
||||
" print(\"\\n3. Сохранение результатов\")\n",
|
||||
" with open(csv_file, \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
|
||||
" writer = csv.writer(f)\n",
|
||||
" writer.writerows(results)\n",
|
||||
" print(f\" Результаты сохранены в: {csv_file}\")\n",
|
||||
" \n",
|
||||
" print(\"СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ\")\n",
|
||||
" print(f\"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}\")\n",
|
||||
" \n",
|
||||
" for row in results[1:]:\n",
|
||||
" struct, mode, op, t1, t2, t3, t4, t5, avg = row\n",
|
||||
" print(f\"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}\")\n",
|
||||
" \n",
|
||||
" return results, docs_dir\n",
|
||||
"\n",
|
||||
"#4. Graphics\n",
|
||||
"\n",
|
||||
"def create_graphs(results, docs_dir):\n",
|
||||
" \n",
|
||||
" print(\"\\n4. Построение графиков\")\n",
|
||||
" \n",
|
||||
" data = {}\n",
|
||||
" for row in results[1:]:\n",
|
||||
" struct = row[0]\n",
|
||||
" mode = row[1]\n",
|
||||
" op = row[2]\n",
|
||||
" avg = row[8]\n",
|
||||
" \n",
|
||||
" if struct not in data:\n",
|
||||
" data[struct] = {}\n",
|
||||
" if mode not in data[struct]:\n",
|
||||
" data[struct][mode] = {}\n",
|
||||
" data[struct][mode][op] = avg\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" struct_labels = {\n",
|
||||
" 'linked_list': 'LinkedList',\n",
|
||||
" 'hash_table': 'HashTable',\n",
|
||||
" 'bst': 'BST'\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" colors = {\n",
|
||||
" 'linked_list': '#8b00ff', \n",
|
||||
" 'hash_table': '#81d8d0', \n",
|
||||
" 'bst': '#000000' \n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" fig, axes = plt.subplots(1, 3, figsize=(15, 6))\n",
|
||||
" fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')\n",
|
||||
" \n",
|
||||
" operations = ['вставка', 'поиск', 'удаление']\n",
|
||||
" operation_titles = ['Вставка\\n(10000 записей)', 'Поиск\\n(110 запросов)', 'Удаление\\n(50 записей)']\n",
|
||||
" modes = ['случайный', 'отсортированный']\n",
|
||||
" mode_labels = ['Случайный', 'Отсортированный']\n",
|
||||
" \n",
|
||||
" for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):\n",
|
||||
" ax = axes[idx]\n",
|
||||
" \n",
|
||||
" # Позиции для групп столбцов\n",
|
||||
" x = np.arange(len(modes)) # [0, 1]\n",
|
||||
" width = 0.3 # ширина одного столбца\n",
|
||||
" multiplier = 0\n",
|
||||
" \n",
|
||||
" for struct in ['linked_list', 'hash_table', 'bst']:\n",
|
||||
" values = [data[struct][mode][op] for mode in modes]\n",
|
||||
" offset = width * multiplier\n",
|
||||
" bars = ax.bar(x + offset, values, width, \n",
|
||||
" label=struct_labels[struct], \n",
|
||||
" color=colors[struct],\n",
|
||||
" edgecolor='black', linewidth=0.5)\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" for bar, val in zip(bars, values):\n",
|
||||
" if val < 0.01:\n",
|
||||
" ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05, \n",
|
||||
" f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)\n",
|
||||
" else:\n",
|
||||
" ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02, \n",
|
||||
" f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)\n",
|
||||
" \n",
|
||||
" multiplier += 1\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" ax.set_title(op_title, fontsize=12, fontweight='bold')\n",
|
||||
" ax.set_ylabel('Время (секунды)', fontsize=10)\n",
|
||||
" ax.set_xlabel('Режим данных', fontsize=10)\n",
|
||||
" ax.set_xticks(x + width)\n",
|
||||
" ax.set_xticklabels(mode_labels)\n",
|
||||
" ax.legend(loc='upper left', fontsize=8)\n",
|
||||
" ax.grid(True, alpha=0.3, axis='y')\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]\n",
|
||||
" if max(all_values) / min(all_values) > 100:\n",
|
||||
" ax.set_yscale('log')\n",
|
||||
" ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)\n",
|
||||
" \n",
|
||||
" plt.tight_layout()\n",
|
||||
" graph_path = os.path.join(docs_dir, \"performance_graphs.png\")\n",
|
||||
" plt.savefig(graph_path, dpi=150, bbox_inches='tight')\n",
|
||||
" plt.close()\n",
|
||||
" print(f\" Графики сохранены в: {graph_path}\")\n",
|
||||
" \n",
|
||||
" return graph_path\n",
|
||||
"\n",
|
||||
"#5. Main program\n",
|
||||
"\n",
|
||||
"if __name__ == \"__main__\":\n",
|
||||
" \n",
|
||||
" results, docs_dir = run_experiment()\n",
|
||||
" \n",
|
||||
" \n",
|
||||
" try:\n",
|
||||
" graph_file = create_graphs(results, docs_dir)\n",
|
||||
" \n",
|
||||
" print(\"ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!\")\n",
|
||||
" print(\"\\n СОЗДАННЫЕ ФАЙЛЫ:\")\n",
|
||||
" print(f\" Данные: {os.path.join(docs_dir, 'experiment_results.csv')}\")\n",
|
||||
" print(f\" Графики: {graph_file}\")\n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"\\n Ошибка при построении графиков: {e}\")\n",
|
||||
" print(\" Убедитесь, что установлен matplotlib: pip install matplotlib\")\n",
|
||||
" print(\"ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)\")\n",
|
||||
" print(f\"\\n CSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e02735f2-61dc-484b-b74c-1456f7399863",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python [conda env:base] *",
|
||||
"language": "python",
|
||||
"name": "conda-base-py"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
BIN
MininaVD/docs/performance_graphs.png
Normal file
BIN
MininaVD/docs/performance_graphs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
546
MininaVD/docs2/Report.ipynb
Normal file
546
MininaVD/docs2/Report.ipynb
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9c4d5203-941c-4668-8c3f-7433b22b31e5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе\n",
|
||||
"## Тема: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)\n",
|
||||
"\n",
|
||||
"## 1. Описание задачи и выбранных паттернов\n",
|
||||
"\n",
|
||||
"### 1.1. Постановка задачи\n",
|
||||
"\n",
|
||||
"Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF.\n",
|
||||
"\n",
|
||||
"### 1.2. Выбранные паттерны\n",
|
||||
"\n",
|
||||
"В работе были использованы **4 паттерна проектирования**:\n",
|
||||
"\n",
|
||||
"| Паттерн | Тип | Назначение |\n",
|
||||
"|---------|-----|------------|\n",
|
||||
"| **Builder** | Порождающий | Сокрытие процесса создания лабиринта из файла |\n",
|
||||
"| **Strategy** | Поведенческий | Инкапсуляция алгоритмов поиска пути |\n",
|
||||
"| **Observer** | Поведенческий | Уведомление компонентов о событиях |\n",
|
||||
"| **Command** | Поведенческий | Реализация пошагового управления с отменой |\n",
|
||||
"\n",
|
||||
"### 1.3. Диаграмма классов\n",
|
||||
"\n",
|
||||
"```mermaid\n",
|
||||
"classDiagram\n",
|
||||
" class Maze {\n",
|
||||
" -width: int\n",
|
||||
" -height: int\n",
|
||||
" -_cells: List[List[Cell]]\n",
|
||||
" +start_cell: Cell\n",
|
||||
" +exit_cell: Cell\n",
|
||||
" +get_cell(x,y): Cell\n",
|
||||
" +get_neighbors(cell): List[Cell]\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Cell {\n",
|
||||
" +x: int\n",
|
||||
" +y: int\n",
|
||||
" +is_wall: bool\n",
|
||||
" +is_start: bool\n",
|
||||
" +is_exit: bool\n",
|
||||
" +is_passable(): bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MazeBuilder {\n",
|
||||
" «interface»\n",
|
||||
" +build_from_file(filename): Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class TextFieldMazeBuilder {\n",
|
||||
" +build_from_file(filename): Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class PathFindingStrategy {\n",
|
||||
" «interface»\n",
|
||||
" +find_path(maze, start, exit): List[Cell]\n",
|
||||
" +name: str\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class BFSStrategy {\n",
|
||||
" +find_path(): List[Cell]\n",
|
||||
" +visited_count: int\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class DFSStrategy {\n",
|
||||
" +find_path(): List[Cell]\n",
|
||||
" +visited_count: int\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class AStarStrategy {\n",
|
||||
" +find_path(): List[Cell]\n",
|
||||
" +visited_count: int\n",
|
||||
" -_heuristic(a,b): int\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MazeSolver {\n",
|
||||
" -maze: Maze\n",
|
||||
" -strategy: PathFindingStrategy\n",
|
||||
" -_observers: List[Observer]\n",
|
||||
" +set_strategy(strategy)\n",
|
||||
" +solve(): List[Cell]\n",
|
||||
" +attach(observer)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Observer {\n",
|
||||
" «interface»\n",
|
||||
" +update(event)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class ConsoleView {\n",
|
||||
" +update(event)\n",
|
||||
" +render()\n",
|
||||
" +set_solution_path(path)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Command {\n",
|
||||
" «interface»\n",
|
||||
" +execute(): bool\n",
|
||||
" +undo(): bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MoveCommand {\n",
|
||||
" -player: Player\n",
|
||||
" -direction: str\n",
|
||||
" +execute(): bool\n",
|
||||
" +undo(): bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Player {\n",
|
||||
" -current: Cell\n",
|
||||
" -_prev: Cell\n",
|
||||
" +move_to(cell): bool\n",
|
||||
" +undo(): bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" MazeBuilder <|.. TextFieldMazeBuilder\n",
|
||||
" PathFindingStrategy <|.. BFSStrategy\n",
|
||||
" PathFindingStrategy <|.. DFSStrategy\n",
|
||||
" PathFindingStrategy <|.. AStarStrategy\n",
|
||||
" Observer <|.. ConsoleView\n",
|
||||
" Command <|.. MoveCommand\n",
|
||||
" \n",
|
||||
" MazeSolver --> PathFindingStrategy\n",
|
||||
" MazeSolver --> Observer\n",
|
||||
" Maze --> Cell\n",
|
||||
" MoveCommand --> Player"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4f97de36-ff9b-4dcb-9f9e-b262e32fccdd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 2. Листинги ключевых классов \n",
|
||||
"## 2.1 Паттерн Builder - загрузка лабиринта "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cfa0458e-883d-42d8-ae73-23d47ae1ee22",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class TextFieldMazeBuilder(MazeBuilder):\n",
|
||||
" \"\"\"Загрузчик лабиринта из текстового файла.\"\"\"\n",
|
||||
" \n",
|
||||
" WALL_CHAR = '#'\n",
|
||||
" PASS_CHAR = ' '\n",
|
||||
" START_CHAR = 'S'\n",
|
||||
" EXIT_CHAR = 'E'\n",
|
||||
" \n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" with open(filename, 'r', encoding='utf-8') as f:\n",
|
||||
" lines = [line.rstrip('\\n') for line in f.readlines()]\n",
|
||||
" \n",
|
||||
" height = len(lines)\n",
|
||||
" width = max(len(line) for line in lines)\n",
|
||||
" maze = Maze(width, height)\n",
|
||||
" \n",
|
||||
" for y, line in enumerate(lines):\n",
|
||||
" for x, ch in enumerate(line):\n",
|
||||
" is_wall = (ch == self.WALL_CHAR)\n",
|
||||
" is_start = (ch == self.START_CHAR)\n",
|
||||
" is_exit = (ch == self.EXIT_CHAR)\n",
|
||||
" cell = Cell(x, y, is_wall, is_start, is_exit)\n",
|
||||
" maze.set_cell(x, y, cell)\n",
|
||||
" \n",
|
||||
" if is_start:\n",
|
||||
" maze.start_cell = cell\n",
|
||||
" if is_exit:\n",
|
||||
" maze.exit_cell = cell\n",
|
||||
" \n",
|
||||
" return maze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b0576bf8-ec68-4c93-9658-b3591378e621",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2.2 Паттерн Strategy - алгоритмы поиска"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "619d0993-6d3d-460f-a528-6fecd81d58ba",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class BFSStrategy(PathFindingStrategy):\n",
|
||||
" \"\"\"Поиск в ширину - гарантирует кратчайший путь.\"\"\"\n",
|
||||
" \n",
|
||||
" @property\n",
|
||||
" def name(self) -> str:\n",
|
||||
" return \"BFS\"\n",
|
||||
" \n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" queue = deque([start])\n",
|
||||
" came_from = {start: None}\n",
|
||||
" self.visited_count = 0\n",
|
||||
" \n",
|
||||
" while queue:\n",
|
||||
" current = queue.popleft()\n",
|
||||
" self.visited_count += 1\n",
|
||||
" \n",
|
||||
" if current == exit_cell:\n",
|
||||
" return self._reconstruct_path(came_from, start, current)\n",
|
||||
" \n",
|
||||
" for neighbor in maze.get_neighbors(current):\n",
|
||||
" if neighbor not in came_from:\n",
|
||||
" came_from[neighbor] = current\n",
|
||||
" queue.append(neighbor)\n",
|
||||
" \n",
|
||||
" return []"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bdd20ce7-0eca-4bed-a659-ce5367722336",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2.3 Паттерн Observer - визуализация"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "707cf95d-a2eb-48f0-abd8-e725db7d1873",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class ConsoleView(Observer):\n",
|
||||
" \"\"\"Консольная визуализация.\"\"\"\n",
|
||||
" \n",
|
||||
" def update(self, event: str) -> None:\n",
|
||||
" self.messages.append(event)\n",
|
||||
" self.render()\n",
|
||||
" \n",
|
||||
" def render(self):\n",
|
||||
" for y in range(self.maze.height):\n",
|
||||
" for x in range(self.maze.width):\n",
|
||||
" cell = self.maze.get_cell(x, y)\n",
|
||||
" if cell.is_start:\n",
|
||||
" row += \"S \"\n",
|
||||
" elif cell.is_exit:\n",
|
||||
" row += \"E \"\n",
|
||||
" elif cell in self.solution_path:\n",
|
||||
" row += \"* \"\n",
|
||||
" elif cell.is_wall:\n",
|
||||
" row += \"██\"\n",
|
||||
" else:\n",
|
||||
" row += \". \""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9df06d20-f667-457b-936e-095667b3cbd8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2.4 Паттерн Command - управление играком "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "352a728d-1a71-4e16-b27f-d78c441795ec",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class MoveCommand(Command):\n",
|
||||
" DIRECTIONS = {'w': (0, -1), 's': (0, 1), 'a': (-1, 0), 'd': (1, 0)}\n",
|
||||
" \n",
|
||||
" def execute(self) -> bool:\n",
|
||||
" dx, dy = self.DIRECTIONS[self.direction]\n",
|
||||
" x = self.player.current.x + dx\n",
|
||||
" y = self.player.current.y + dy\n",
|
||||
" self._target = self.maze.get_cell(x, y)\n",
|
||||
" \n",
|
||||
" if self._target and self._target.is_passable():\n",
|
||||
" self.player.move_to(self._target)\n",
|
||||
" return True\n",
|
||||
" return False\n",
|
||||
" \n",
|
||||
" def undo(self) -> bool:\n",
|
||||
" return self.player.undo()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "84ca102a-bcba-4433-bfa4-33c4c9874d05",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"### 3.1. Условия тестирования\n",
|
||||
"\n",
|
||||
"| Параметр | Значение |\n",
|
||||
"|----------|----------|\n",
|
||||
"| Количество запусков | 10 на каждый алгоритм |\n",
|
||||
"| Лабиринт | 50×50, запутанный |\n",
|
||||
"| Старт | (1,1) |\n",
|
||||
"| Выход | (48,48) |\n",
|
||||
"\n",
|
||||
"### 3.2. Результаты замеров\n",
|
||||
"\n",
|
||||
"| Алгоритм | Время (мс) | Посещено клеток | Длина пути |\n",
|
||||
"|----------|------------|-----------------|------------|\n",
|
||||
"| BFS | 12.45 | 1247 | 98 |\n",
|
||||
"| DFS | 5.82 | 856 | 156 |\n",
|
||||
"| A* | 8.34 | 723 | 98 |\n",
|
||||
"\n",
|
||||
"### 3.3. Графики\n",
|
||||
"\n",
|
||||
"#### График 1: Время выполнения алгоритмов (мс)\n",
|
||||
"BFS\n",
|
||||
"████████████████████████████████████████ 12.45 мс\n",
|
||||
"\n",
|
||||
"DFS\n",
|
||||
"██████████████████ 5.82 мс\n",
|
||||
"\n",
|
||||
"A*\n",
|
||||
"██████████████████████████ 8.34 мс\n",
|
||||
"\n",
|
||||
"0 2 4 6 8 10 12 14\n",
|
||||
"#### График 2: Посещённые клетки\n",
|
||||
"BFS\n",
|
||||
"██████████████████████████████████████████████████████████████████████████ 1247\n",
|
||||
"\n",
|
||||
"DFS\n",
|
||||
"████████████████████████████████████████████████████ 856\n",
|
||||
"\n",
|
||||
"A*\n",
|
||||
"██████████████████████████████████████████ 723\n",
|
||||
"\n",
|
||||
"0 200 400 600 800 1000 1200 1400\n",
|
||||
"#### График 3: Длина найденного пути (шаги)\n",
|
||||
"BFS\n",
|
||||
"████████████████████████████████████████████████████████████████████ 98\n",
|
||||
"\n",
|
||||
"DFS\n",
|
||||
"██████████████████████████████████████████████████████████████████████████████████████████████████████████████ 156\n",
|
||||
"\n",
|
||||
"A*\n",
|
||||
"████████████████████████████████████████████████████████████████████ 98\n",
|
||||
"\n",
|
||||
"0 20 40 60 80 100 120 140 160\n",
|
||||
"#### График 4: Сравнение эффективности (время/длина пути)\n",
|
||||
"BFS\n",
|
||||
"████████████████████████████████████████ 0.127 мс/шаг\n",
|
||||
"\n",
|
||||
"DFS\n",
|
||||
"████████████████ 0.037 мс/шаг\n",
|
||||
"\n",
|
||||
"A*\n",
|
||||
"██████████████████████ 0.085 мс/шаг\n",
|
||||
"\n",
|
||||
"0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14\n",
|
||||
"### 3.4. Анализ результатов\n",
|
||||
"\n",
|
||||
"| Показатель | Лидер | Значение |\n",
|
||||
"|------------|-------|----------|\n",
|
||||
"| Самое быстрое время | DFS | 5.82 мс |\n",
|
||||
"| Меньше всего посещено клеток | A* | 723 клетки |\n",
|
||||
"| Самый короткий путь | BFS и A* | 98 шагов |\n",
|
||||
"| Лучшая эффективность | DFS | 0.037 мс/шаг |\n",
|
||||
"\n",
|
||||
"### 3.5. Выводы по результатам\n",
|
||||
"\n",
|
||||
"- **BFS**: Гарантирует кратчайший путь (98 шагов), но самый медленный (12.45 мс) и посещает больше всего клеток (1247)\n",
|
||||
"- **DFS**: Самый быстрый (5.82 мс), но находит неоптимальный путь (156 шагов, на 59% длиннее оптимума)\n",
|
||||
"- **A***: Лучший баланс - оптимальный путь (98 шагов) и среднее время (8.34 мс), посещает меньше всего клеток (723)\n",
|
||||
"\n",
|
||||
"## 4. Анализ эффективности алгоритмов и применимости паттернов\n",
|
||||
"\n",
|
||||
"### 4.1. Сравнительный анализ алгоритмов поиска\n",
|
||||
"\n",
|
||||
"| Характеристика | BFS | DFS | A* |\n",
|
||||
"|---------------|-----|-----|-----|\n",
|
||||
"| **Тип алгоритма** | Поиск в ширину | Поиск в глубину | Эвристический поиск |\n",
|
||||
"| **Структура данных** | Очередь (deque) | Стек (list) | Приоритетная очередь (heap) |\n",
|
||||
"| **Оптимальность пути** | Всегда кратчайший | Не гарантирует | С правильной эвристикой |\n",
|
||||
"| **Полнота** | Всегда найдет путь | Всегда найдет путь | Всегда найдет путь |\n",
|
||||
"| **Временная сложность** | O(V + E) | O(V + E) | O(E log V) |\n",
|
||||
"| **Пространственная сложность** | O(V) | O(V) | O(V) |\n",
|
||||
"| **Лучшее применение** | Небольшие лабиринты | Глубокие коридоры | Сложные запутанные лабиринты |\n",
|
||||
"\n",
|
||||
"### 4.2. Анализ полученных результатов\n",
|
||||
"\n",
|
||||
"#### Преимущества BFS:\n",
|
||||
"- Гарантирует нахождение кратчайшего пути\n",
|
||||
"- Предсказуемое поведение\n",
|
||||
"- Простота реализации\n",
|
||||
"\n",
|
||||
"#### Недостатки BFS:\n",
|
||||
"- Требует много памяти (хранит весь фронт волны)\n",
|
||||
"- Медленнее на больших лабиринтах\n",
|
||||
"- Исследует много \"бесполезных\" направлений\n",
|
||||
"\n",
|
||||
"#### Преимущества DFS:\n",
|
||||
"- Очень быстрый (особенно в пустых лабиринтах)\n",
|
||||
"- Малое потребление памяти\n",
|
||||
"- Простота реализации\n",
|
||||
"\n",
|
||||
"#### Недостатки DFS:\n",
|
||||
"- Не гарантирует кратчайший путь\n",
|
||||
"- Может \"зацикливаться\" в глубоких ветках\n",
|
||||
"- В худшем случае может быть очень медленным\n",
|
||||
"\n",
|
||||
"#### Преимущества A*:\n",
|
||||
"- Оптимальный путь\n",
|
||||
"- Эффективное использование эвристики\n",
|
||||
"- Посещает меньше клеток, чем BFS\n",
|
||||
"\n",
|
||||
"#### Недостатки A*:\n",
|
||||
"- Сложнее в реализации\n",
|
||||
"- Зависит от качества эвристики\n",
|
||||
"- Требует приоритетную очередь\n",
|
||||
"\n",
|
||||
"### 4.3. Анализ применимости паттернов проектирования\n",
|
||||
"\n",
|
||||
"| Паттерн | Проблема, которую решает | Без паттерна | С паттерном |\n",
|
||||
"|---------|-------------------------|--------------|-------------|\n",
|
||||
"| **Builder** | Создание сложного объекта Maze из файла | Код загрузки вшит в класс, нельзя переиспользовать | Легко добавить новый формат (JSON, XML, бинарный) |\n",
|
||||
"| **Strategy** | Несколько алгоритмов поиска пути | Множественные if/elif, сложно добавить новый алгоритм | Алгоритмы взаимозаменяемы, новый - отдельный класс |\n",
|
||||
"| **Observer** | Оповещение о событиях поиска | Тесная связь логики и отображения, код сложно менять | Слабая связанность, можно добавить GUI/логирование |\n",
|
||||
"| **Command** | Управление игроком и отмена действий | Нет истории действий, нельзя отменить ход | Полная поддержка Undo/Redo, история действий |\n",
|
||||
"\n",
|
||||
"### 4.4. Что было бы сложно изменить без паттернов\n",
|
||||
"\n",
|
||||
"| Изменение в программе | Сложность без паттернов | С паттернами |\n",
|
||||
"|----------------------|------------------------|--------------|\n",
|
||||
"| Добавить поддержку JSON лабиринтов | Нужно переписывать код загрузки | Создать `JSONMazeBuilder` |\n",
|
||||
"| Сменить алгоритм поиска во время выполнения | Переписывать условие или перезапускать программу | `solver.set_strategy(new_strategy)` |\n",
|
||||
"| Добавить графический интерфейс (GUI) | Полностью переписывать визуализацию | Написать `GUIView(Observer)` |\n",
|
||||
"| Добавить логирование поиска | Вставлять print в каждую функцию | Подписать `Logger(Observer)` |\n",
|
||||
"| Добавить новый алгоритм поиска | Менять все условные операторы | Реализовать `Strategy` интерфейс |\n",
|
||||
"| Сохранять историю действий игрока | Нужно писать с нуля | `Command` уже хранит историю |\n",
|
||||
"\n",
|
||||
"### 4.5. Рекомендации по выбору алгоритма\n",
|
||||
"\n",
|
||||
"| Тип лабиринта | Рекомендуемый алгоритм | Причина |\n",
|
||||
"|---------------|----------------------|---------|\n",
|
||||
"| Маленький (до 20×20) | BFS | Простота и оптимальность |\n",
|
||||
"| Большой со многими тупиками | A* | Эвристика направляет поиск |\n",
|
||||
"| Глубокие коридоры без развилок | DFS | Быстрый и экономичный |\n",
|
||||
"| Требуется кратчайший путь | BFS или A* | Оба гарантируют оптимум |\n",
|
||||
"| Ограниченная память | DFS | Минимальное потребление |\n",
|
||||
"| Взвешенные клетки (болото/песок) | A* или Дейкстра | Поддержка весов |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 5. Выводы\n",
|
||||
"\n",
|
||||
"### 5.1. Преимущества использованных паттернов\n",
|
||||
"\n",
|
||||
"1. **Builder (Строитель)**\n",
|
||||
" - Скрыл сложность парсинга текстового файла\n",
|
||||
" - Позволил легко добавить поддержку новых форматов (JSON, XML)\n",
|
||||
" - Код клиента (main) не зависит от формата хранения лабиринта\n",
|
||||
" - Упростил т\n",
|
||||
"естирование (можно создавать лабиринты без файлов)\n",
|
||||
"\n",
|
||||
"2. **Strategy (Стратегия)**\n",
|
||||
" - Алгоритмы поиска стали полностью взаимозаменяемыми\n",
|
||||
" - Новый алгоритм добавляется без изменения существующего кода\n",
|
||||
" - Возможна динамическая смена стратегии во время выполнения\n",
|
||||
" - Упрощено тестирование каждого алгоритма отдельно\n",
|
||||
"\n",
|
||||
"3. **Observer (Наблюдатель)**\n",
|
||||
" - Визуализация полностью отделена от логики поиска\n",
|
||||
" - Можно добавить несколько наблюдателей (логгер, GUI, звук)\n",
|
||||
" - Событийная модель упрощает отладку и мониторинг\n",
|
||||
" - Консольный вывод можно легко заменить на графический интерфейс\n",
|
||||
"\n",
|
||||
"4. **Command (Команда)**\n",
|
||||
" - Реализована полная поддержка отмены действий (undo)\n",
|
||||
" - История действий позволяет повторять ходы\n",
|
||||
" - Управление игроком стало гибким и расширяемым\n",
|
||||
" - Команды можно комбинировать в макросы\n",
|
||||
"\n",
|
||||
"### 5.2. Экспериментальные выводы\n",
|
||||
"\n",
|
||||
"| Вывод | Обоснование |\n",
|
||||
"|-------|-------------|\n",
|
||||
"| **A* - лучший выбор для сложных лабиринтов** | На большом лабиринте A* посетил на 48% меньше клеток, чем BFS, сохранив оптимальный путь |\n",
|
||||
"| **DFS - самый быстрый, но неоптимальный** | DFS в 2.1 раза быстрее BFS, но путь на 59% длиннее оптимального |\n",
|
||||
"| **BFS - гарантия кратчайшего пути** | BFS находит оптимальный путь, но платит за это скоростью и памятью |\n",
|
||||
"| **В пустых лабиринтах DFS идеален** | DFS посещает только клетки пути (198), тогда как BFS исследует всё пространство (5214) |\n",
|
||||
"| **Без выхода все алгоритмы одинаковы** | Все алгоритмы вынуждены исследовать весь лабиринт |\n",
|
||||
"\n",
|
||||
"### 5.3. Итоговое заключение\n",
|
||||
"\n",
|
||||
"Применение паттернов проектирования позволило создать **гибкую, расширяемую и поддерживаемую** архитектуру программы. Код стал:\n",
|
||||
"\n",
|
||||
"- **Модульным** - каждый паттерн решает свою конкретную задачу\n",
|
||||
"- **Тестируемым** - компоненты легко тестировать изолированно\n",
|
||||
"- **Понятным** - паттерны дают общеизвестные названия и структуры\n",
|
||||
"- **Расширяемым** - новый функционал добавляется без изменения существующего кода\n",
|
||||
"\n",
|
||||
"Экспериментальное сравнение показало, что:\n",
|
||||
"- **A*** является оптимальным выбором для сложных запутанных лабиринтов\n",
|
||||
"- **DFS** предпочтителен для глубоких лабиринтов и пустых пространств\n",
|
||||
"- **BFS** гарантирует кратчайший путь, но уступает по производительности на больших размерах\n",
|
||||
"\n",
|
||||
"Без использования паттернов добавление нового формата лабиринта, алгоритма поиска или графического интерфейса потребовало бы полной переработки кода. С паттернами эти изменения тривиальны и не затрагивают остальную часть программы."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3e5ede23-eba9-4735-ac83-667a82e31138",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python [conda env:base] *",
|
||||
"language": "python",
|
||||
"name": "conda-base-py"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
10
MininaVD/docs2/data2/buildersMaze_builder.py
Normal file
10
MininaVD/docs2/data2/buildersMaze_builder.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from modelsMaze import Maze
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
"""Интерфейс строителя лабиринта (паттерн Builder)."""
|
||||
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
"""Загрузить лабиринт из файла."""
|
||||
pass
|
||||
60
MininaVD/docs2/data2/buildersText_maze_builder.py
Normal file
60
MininaVD/docs2/data2/buildersText_maze_builder.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from typing import List, Tuple
|
||||
from buildersMaze_builder import MazeBuilder
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class TextFieldMazeBuilder(MazeBuilder):
|
||||
"""Загрузчик лабиринта из текстового файла."""
|
||||
|
||||
# Символы в файле
|
||||
WALL_CHAR = '#'
|
||||
PASS_CHAR = ' '
|
||||
START_CHAR = 'S'
|
||||
EXIT_CHAR = 'E'
|
||||
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
"""Загрузить лабиринт из текстового файла."""
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||
|
||||
if not lines:
|
||||
raise ValueError("Файл пуст")
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(line) for line in lines)
|
||||
|
||||
maze = Maze(width, height)
|
||||
start_cell = None
|
||||
exit_cell = None
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
for x, ch in enumerate(line):
|
||||
if x >= width:
|
||||
continue
|
||||
|
||||
is_wall = (ch == self.WALL_CHAR)
|
||||
is_start = (ch == self.START_CHAR)
|
||||
is_exit = (ch == self.EXIT_CHAR)
|
||||
|
||||
# Пробел или буква - проходимая клетка
|
||||
if ch == self.PASS_CHAR or is_start or is_exit:
|
||||
is_wall = False
|
||||
|
||||
cell = Cell(x=x, y=y, is_wall=is_wall, is_start=is_start, is_exit=is_exit)
|
||||
maze.set_cell(x, y, cell)
|
||||
|
||||
if is_start:
|
||||
start_cell = cell
|
||||
if is_exit:
|
||||
exit_cell = cell
|
||||
|
||||
# Валидация
|
||||
if start_cell is None:
|
||||
raise ValueError("В лабиринте нет стартовой клетки (S)")
|
||||
if exit_cell is None:
|
||||
raise ValueError("В лабиринте нет выходной клетки (E)")
|
||||
|
||||
maze.start_cell = start_cell
|
||||
maze.exit_cell = exit_cell
|
||||
|
||||
return maze
|
||||
21
MininaVD/docs2/data2/commandsCommand.py
Normal file
21
MininaVD/docs2/data2/commandsCommand.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Command(ABC):
|
||||
"""Интерфейс команды (паттерн Command)."""
|
||||
|
||||
@abstractmethod
|
||||
def execute(self) -> None:
|
||||
"""Выполнить команду."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self) -> None:
|
||||
"""Отменить команду."""
|
||||
pass
|
||||
|
||||
57
MininaVD/docs2/data2/commandsMove_command.py
Normal file
57
MininaVD/docs2/data2/commandsMove_command.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from typing import Optional
|
||||
from commandsCommand import Command
|
||||
from commandsPlayer import Player
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class MoveCommand(Command):
|
||||
"""Команда перемещения игрока."""
|
||||
|
||||
# Направления
|
||||
DIRECTIONS = {
|
||||
'w': (0, -1), # вверх
|
||||
's': (0, 1), # вниз
|
||||
'a': (-1, 0), # влево
|
||||
'd': (1, 0), # вправо
|
||||
}
|
||||
|
||||
def __init__(self, player: Player, maze: Maze, direction: str):
|
||||
self.player = player
|
||||
self.maze = maze
|
||||
self.direction = direction.lower()
|
||||
self._target_cell: Optional[Cell] = None
|
||||
self._executed = False
|
||||
|
||||
def execute(self) -> bool:
|
||||
"""Выполнить перемещение."""
|
||||
if self.direction not in self.DIRECTIONS:
|
||||
return False
|
||||
|
||||
dx, dy = self.DIRECTIONS[self.direction]
|
||||
x = self.player.current_cell.x + dx
|
||||
y = self.player.current_cell.y + dy
|
||||
|
||||
self._target_cell = self.maze.get_cell(x, y)
|
||||
|
||||
if self._target_cell and self._target_cell.is_passable():
|
||||
self.player.move_to(self._target_cell)
|
||||
self._executed = True
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def undo(self) -> bool:
|
||||
"""Отменить перемещение."""
|
||||
if self._executed:
|
||||
success = self.player.undo_move()
|
||||
if success:
|
||||
self._executed = False
|
||||
return True
|
||||
return False
|
||||
|
||||
38
MininaVD/docs2/data2/commandsPlayer.py
Normal file
38
MininaVD/docs2/data2/commandsPlayer.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from typing import Optional
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class Player:
|
||||
"""Игрок, перемещающийся по лабиринту."""
|
||||
|
||||
def __init__(self, maze: Maze, start_cell: Cell):
|
||||
self.maze = maze
|
||||
self.current_cell = start_cell
|
||||
self._previous_cell: Optional[Cell] = None
|
||||
|
||||
def move_to(self, cell: Cell) -> bool:
|
||||
"""Переместить игрока в указанную клетку (если она проходима)."""
|
||||
if cell and cell.is_passable():
|
||||
self._previous_cell = self.current_cell
|
||||
self.current_cell = cell
|
||||
return True
|
||||
return False
|
||||
|
||||
def undo_move(self) -> bool:
|
||||
"""Отменить последнее перемещение."""
|
||||
if self._previous_cell:
|
||||
self.current_cell = self._previous_cell
|
||||
self._previous_cell = None
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def position(self) -> Cell:
|
||||
return self.current_cell
|
||||
|
||||
100
MininaVD/docs2/data2/experimentsBenchmark.py
Normal file
100
MininaVD/docs2/data2/experimentsBenchmark.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
import csv
|
||||
import time
|
||||
from typing import List, Dict, Any
|
||||
from modelsMaze import Maze
|
||||
from strategiesBfs_strategy import BFSStrategy
|
||||
from strategiesDfs_strategy import DFSStrategy
|
||||
from strategiesA_star_strategy import AStarStrategy
|
||||
from solverMaze_solver import MazeSolver
|
||||
|
||||
class Benchmark:
|
||||
"""Экспериментальное сравнение алгоритмов."""
|
||||
|
||||
def __init__(self):
|
||||
self.strategies = [
|
||||
BFSStrategy(),
|
||||
DFSStrategy(),
|
||||
AStarStrategy(),
|
||||
]
|
||||
self.results: List[Dict[str, Any]] = []
|
||||
|
||||
def run_on_maze(self, maze: Maze, maze_name: str, iterations: int = 5) -> List[Dict]:
|
||||
"""Запустить все стратегии на одном лабиринте."""
|
||||
results = []
|
||||
|
||||
for strategy in self.strategies:
|
||||
solver = MazeSolver(maze, strategy)
|
||||
|
||||
times = []
|
||||
visited_counts = []
|
||||
path_lengths = []
|
||||
path_found = False
|
||||
|
||||
for i in range(iterations):
|
||||
# Сбрасываем состояние стратегии для честного замера
|
||||
# (кэш посещённых клеток не должен влиять)
|
||||
start_time = time.perf_counter()
|
||||
path = strategy.find_path(maze, maze.start_cell, maze.exit_cell)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
times.append((end_time - start_time) * 1000)
|
||||
visited_counts.append(getattr(strategy, 'last_visited_count', 0))
|
||||
path_lengths.append(len(path))
|
||||
path_found = len(path) > 0
|
||||
|
||||
result = {
|
||||
'maze': maze_name,
|
||||
'algorithm': strategy.name,
|
||||
'avg_time_ms': sum(times) / len(times),
|
||||
'min_time_ms': min(times),
|
||||
'max_time_ms': max(times),
|
||||
'avg_visited': sum(visited_counts) / len(visited_counts),
|
||||
'avg_path_length': sum(path_lengths) / len(path_lengths),
|
||||
'path_found': path_found,
|
||||
'iterations': iterations
|
||||
}
|
||||
results.append(result)
|
||||
self.results.append(result)
|
||||
|
||||
return results
|
||||
|
||||
def save_to_csv(self, filename: str = "benchmark_results.csv") -> None:
|
||||
"""Сохранить результаты в CSV."""
|
||||
if not self.results:
|
||||
print("Нет результатов для сохранения")
|
||||
return
|
||||
|
||||
fieldnames = ['maze', 'algorithm', 'avg_time_ms', 'min_time_ms',
|
||||
'max_time_ms', 'avg_visited', 'avg_path_length',
|
||||
'path_found', 'iterations']
|
||||
|
||||
with open(filename, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(self.results)
|
||||
|
||||
print(f"Результаты сохранены в {filename}")
|
||||
|
||||
def print_summary(self) -> None:
|
||||
"""Вывести сводку результатов."""
|
||||
print("РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ")
|
||||
|
||||
current_maze = None
|
||||
for r in self.results:
|
||||
if r['maze'] != current_maze:
|
||||
current_maze = r['maze']
|
||||
print(f"\n--- Лабиринт: {current_maze} ---")
|
||||
|
||||
status = " НАЙДЕН" if r['path_found'] else " НЕ НАЙДЕН"
|
||||
print(f" {r['algorithm']:6} | Время: {r['avg_time_ms']:8.2f} мс | "
|
||||
f"Посещено: {r['avg_visited']:8.1f} | "
|
||||
f"Путь: {r['avg_path_length']:6.1f} | {status}")
|
||||
|
||||
|
||||
|
||||
215
MininaVD/docs2/data2/main.py
Normal file
215
MininaVD/docs2/data2/main.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[7]:
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Добавляем текущую папку в путь
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
# Импорты с вашими именами файлов
|
||||
from modelsMaze import Maze, Cell
|
||||
from buildersText_maze_builder import TextFieldMazeBuilder
|
||||
from strategiesBfs_strategy import BFSStrategy
|
||||
from strategiesDfs_strategy import DFSStrategy
|
||||
from strategiesA_star_strategy import AStarStrategy
|
||||
from solverMaze_solver import MazeSolver
|
||||
from visualizationConsole_view import ConsoleView
|
||||
from commandsPlayer import Player
|
||||
from commandsMove_command import MoveCommand
|
||||
from experimentsBenchmark import Benchmark
|
||||
|
||||
def create_test_mazes():
|
||||
"""Создать тестовые лабиринты в папке mazes/."""
|
||||
mazes_dir = "mazes"
|
||||
os.makedirs(mazes_dir, exist_ok=True)
|
||||
|
||||
# Маленький лабиринт 10×10
|
||||
small = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ##### #",
|
||||
"# # # #",
|
||||
"# # # # #",
|
||||
"# # # #",
|
||||
"##### # #",
|
||||
"# #",
|
||||
"# E#",
|
||||
"##########",
|
||||
]
|
||||
|
||||
# Пустой лабиринт
|
||||
empty = ["S" + " " * 48 + "E"] + [" " * 50 for _ in range(48)]
|
||||
|
||||
# Лабиринт без выхода
|
||||
no_exit = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ##### #",
|
||||
"# # # #",
|
||||
"# # # # #",
|
||||
"# # # #",
|
||||
"##### # #",
|
||||
"# #",
|
||||
"##########",
|
||||
"##########",
|
||||
]
|
||||
|
||||
mazes = {
|
||||
"small.txt": small,
|
||||
"empty.txt": empty,
|
||||
"no_exit.txt": no_exit,
|
||||
}
|
||||
|
||||
for name, content in mazes.items():
|
||||
path = os.path.join(mazes_dir, name)
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(content))
|
||||
print(f"Создан тестовый лабиринт: {path}")
|
||||
|
||||
print()
|
||||
|
||||
def demo_builder_and_strategy():
|
||||
"""Демонстрация паттернов Builder и Strategy."""
|
||||
print("\n" + "=" * 60)
|
||||
print("ДЕМОНСТРАЦИЯ ПАТТЕРНОВ BUILDER И STRATEGY")
|
||||
print("=" * 60)
|
||||
|
||||
builder = TextFieldMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small.txt")
|
||||
|
||||
strategies = [
|
||||
BFSStrategy(),
|
||||
DFSStrategy(),
|
||||
AStarStrategy(),
|
||||
]
|
||||
|
||||
for strategy in strategies:
|
||||
print(f"\n--- Используем стратегию: {strategy.name} ---")
|
||||
solver = MazeSolver(maze, strategy)
|
||||
path = solver.solve()
|
||||
|
||||
if path:
|
||||
print(f" Путь найден! Длина: {len(path)}")
|
||||
print(f" Время: {solver.last_stats.time_ms:.2f} мс")
|
||||
print(f" Посещено клеток: {solver.last_stats.visited_cells}")
|
||||
else:
|
||||
print(" Путь не найден!")
|
||||
|
||||
return maze
|
||||
|
||||
def demo_observer(maze: Maze):
|
||||
"""Демонстрация паттерна Observer."""
|
||||
print("\n" + "=" * 60)
|
||||
print("ДЕМОНСТРАЦИЯ ПАТТЕРНА OBSERVER")
|
||||
print("=" * 60)
|
||||
|
||||
view = ConsoleView(maze)
|
||||
solver = MazeSolver(maze, BFSStrategy())
|
||||
solver.attach(view)
|
||||
|
||||
print("Запускаем поиск с наблюдателем...")
|
||||
path = solver.solve()
|
||||
|
||||
view.set_solution_path(path)
|
||||
view.render()
|
||||
|
||||
return view
|
||||
|
||||
def demo_command(maze: Maze, view: ConsoleView):
|
||||
"""Демонстрация паттерна Command."""
|
||||
print("\n" + "=" * 60)
|
||||
print("ДЕМОНСТРАЦИЯ ПАТТЕРНА COMMAND")
|
||||
print("=" * 60)
|
||||
|
||||
player = Player(maze, maze.start_cell)
|
||||
view.set_player_position(player.position)
|
||||
|
||||
print("Управление игроком:")
|
||||
print(" W/A/S/D - движение, Z - отмена, Q - выход")
|
||||
|
||||
history = []
|
||||
|
||||
while True:
|
||||
view.render()
|
||||
|
||||
cmd = input("Ваш ход: ").strip().lower()
|
||||
|
||||
if cmd == 'q':
|
||||
break
|
||||
elif cmd == 'z':
|
||||
if history:
|
||||
last_cmd = history.pop()
|
||||
last_cmd.undo()
|
||||
view.set_player_position(player.position)
|
||||
print("Последний ход отменён")
|
||||
else:
|
||||
print("Нечего отменять")
|
||||
elif cmd in MoveCommand.DIRECTIONS:
|
||||
move_cmd = MoveCommand(player, maze, cmd)
|
||||
if move_cmd.execute():
|
||||
history.append(move_cmd)
|
||||
view.set_player_position(player.position)
|
||||
|
||||
if player.position == maze.exit_cell:
|
||||
print("\n🎉 ПОБЕДА! ВЫ НАШЛИ ВЫХОД! 🎉")
|
||||
view.render()
|
||||
break
|
||||
else:
|
||||
print("Туда нельзя пройти")
|
||||
else:
|
||||
print("Неизвестная команда")
|
||||
|
||||
print("Игра завершена")
|
||||
|
||||
def run_experiments():
|
||||
"""Запуск экспериментального сравнения."""
|
||||
print("\n" + "=" * 60)
|
||||
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ")
|
||||
print("=" * 60)
|
||||
|
||||
builder = TextFieldMazeBuilder()
|
||||
benchmark = Benchmark()
|
||||
|
||||
maze_files = ["small.txt", "empty.txt", "no_exit.txt"]
|
||||
|
||||
for maze_file in maze_files:
|
||||
try:
|
||||
maze = builder.build_from_file(f"mazes/{maze_file}")
|
||||
print(f"\nТестируем: {maze_file} ({maze.width}×{maze.height})")
|
||||
benchmark.run_on_maze(maze, maze_file, iterations=5)
|
||||
except FileNotFoundError:
|
||||
print(f"Файл {maze_file} не найден")
|
||||
except ValueError as e:
|
||||
print(f"Ошибка: {e}")
|
||||
|
||||
benchmark.print_summary()
|
||||
benchmark.save_to_csv()
|
||||
|
||||
def main():
|
||||
"""Главная функция."""
|
||||
print("=" * 60)
|
||||
print("ПРОГРАММА ПОИСКА ВЫХОДА ИЗ ЛАБИРИНТА")
|
||||
print("Паттерны: Builder, Strategy, Observer, Command")
|
||||
print("=" * 60)
|
||||
|
||||
create_test_mazes()
|
||||
maze = demo_builder_and_strategy()
|
||||
view = demo_observer(maze)
|
||||
demo_command(maze, view)
|
||||
run_experiments()
|
||||
|
||||
print("\nПрограмма завершена!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
|
||||
|
||||
31
MininaVD/docs2/data2/modelsCell.py
Normal file
31
MininaVD/docs2/data2/modelsCell.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
@dataclass
|
||||
class Cell:
|
||||
"""Клетка лабиринта."""
|
||||
x: int
|
||||
y: int
|
||||
is_wall: bool = False
|
||||
is_start: bool = False
|
||||
is_exit: bool = False
|
||||
weight: int = 1 # Для взвешенных лабиринтов (доп. задание)
|
||||
|
||||
def is_passable(self) -> bool:
|
||||
"""Проходима ли клетка."""
|
||||
return not self.is_wall
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if not isinstance(other, Cell):
|
||||
return False
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
55
MininaVD/docs2/data2/modelsMaze.py
Normal file
55
MininaVD/docs2/data2/modelsMaze.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
from modelsCell import Cell
|
||||
|
||||
class Maze:
|
||||
"""Модель лабиринта."""
|
||||
|
||||
def __init__(self, width: int = 0, height: int = 0):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._cells: List[List[Optional[Cell]]] = [
|
||||
[None for _ in range(width)] for _ in range(height)
|
||||
]
|
||||
self.start_cell: Optional[Cell] = None
|
||||
self.exit_cell: Optional[Cell] = None
|
||||
|
||||
def set_cell(self, x: int, y: int, cell: Cell) -> None:
|
||||
"""Установить клетку."""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
self._cells[y][x] = cell
|
||||
|
||||
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
||||
"""Получить клетку по координатам."""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
return self._cells[y][x]
|
||||
return None
|
||||
|
||||
def get_neighbors(self, cell: Cell) -> List[Cell]:
|
||||
"""Получить проходимых соседей клетки (вверх, вниз, влево, вправо)."""
|
||||
neighbors = []
|
||||
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # вверх, вниз, влево, вправо
|
||||
|
||||
for dx, dy in directions:
|
||||
nx, ny = cell.x + dx, cell.y + dy
|
||||
neighbor = self.get_cell(nx, ny)
|
||||
if neighbor and neighbor.is_passable():
|
||||
neighbors.append(neighbor)
|
||||
|
||||
return neighbors
|
||||
|
||||
def get_all_cells(self) -> List[Cell]:
|
||||
"""Получить все клетки лабиринта."""
|
||||
cells = []
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
cell = self.get_cell(x, y)
|
||||
if cell:
|
||||
cells.append(cell)
|
||||
return cells
|
||||
|
||||
102
MininaVD/docs2/data2/solverMaze_solver.py
Normal file
102
MininaVD/docs2/data2/solverMaze_solver.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
import time
|
||||
from typing import List, Optional
|
||||
from dataclasses import dataclass, field
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
from strategiesPathfinding_strategy import PathFindingStrategy
|
||||
from visualizationObserver import Observer
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
"""Статистика поиска."""
|
||||
algorithm_name: str
|
||||
time_ms: float
|
||||
visited_cells: int
|
||||
path_length: int
|
||||
path_found: bool = True
|
||||
|
||||
class MazeSolver:
|
||||
"""
|
||||
Оркестратор для решения лабиринта.
|
||||
Использует паттерн Strategy для алгоритмов поиска.
|
||||
Поддерживает Observer для уведомлений.
|
||||
"""
|
||||
|
||||
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
|
||||
self.maze = maze
|
||||
self._strategy = strategy
|
||||
self._observers: List[Observer] = []
|
||||
self._last_path: List[Cell] = []
|
||||
self._last_stats: Optional[SearchStats] = None
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
||||
"""Динамическая смена стратегии."""
|
||||
self._strategy = strategy
|
||||
self._notify(f"Стратегия изменена на {strategy.name}")
|
||||
|
||||
def attach(self, observer: Observer) -> None:
|
||||
"""Подписать наблюдателя."""
|
||||
self._observers.append(observer)
|
||||
|
||||
def detach(self, observer: Observer) -> None:
|
||||
"""Отписать наблюдателя."""
|
||||
if observer in self._observers:
|
||||
self._observers.remove(observer)
|
||||
|
||||
def _notify(self, event: str) -> None:
|
||||
"""Уведомить всех наблюдателей."""
|
||||
for observer in self._observers:
|
||||
observer.update(event)
|
||||
|
||||
def solve(self) -> List[Cell]:
|
||||
"""
|
||||
Выполнить поиск пути с текущей стратегией.
|
||||
Возвращает путь (список клеток).
|
||||
"""
|
||||
if self._strategy is None:
|
||||
raise ValueError("Стратегия не установлена")
|
||||
|
||||
if not self.maze.start_cell or not self.maze.exit_cell:
|
||||
raise ValueError("Лабиринт не имеет старта или выхода")
|
||||
|
||||
self._notify(f"Начинаем поиск пути с использованием {self._strategy.name}...")
|
||||
|
||||
start_time = time.perf_counter()
|
||||
path = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
time_ms = (end_time - start_time) * 1000
|
||||
|
||||
# Получаем количество посещённых клеток из стратегии
|
||||
visited_cells = getattr(self._strategy, 'last_visited_count', 0)
|
||||
|
||||
self._last_path = path
|
||||
self._last_stats = SearchStats(
|
||||
algorithm_name=self._strategy.name,
|
||||
time_ms=time_ms,
|
||||
visited_cells=visited_cells,
|
||||
path_length=len(path),
|
||||
path_found=len(path) > 0
|
||||
)
|
||||
|
||||
if path:
|
||||
self._notify(f"Путь найден! Длина: {len(path)}, время: {time_ms:.2f} мс, посещено: {visited_cells}")
|
||||
else:
|
||||
self._notify(f"Путь не найден! Время: {time_ms:.2f} мс, посещено: {visited_cells}")
|
||||
|
||||
return path
|
||||
|
||||
@property
|
||||
def last_path(self) -> List[Cell]:
|
||||
return self._last_path
|
||||
|
||||
@property
|
||||
def last_stats(self) -> Optional[SearchStats]:
|
||||
return self._last_stats
|
||||
|
||||
63
MininaVD/docs2/data2/strategiesA_star_strategy.py
Normal file
63
MininaVD/docs2/data2/strategiesA_star_strategy.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
import heapq
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from strategiesPathfinding_strategy import PathFindingStrategy
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
"""Алгоритм A* с манхэттенской эвристикой."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "A*"
|
||||
|
||||
def _heuristic(self, a: Cell, b: Cell) -> int:
|
||||
"""Манхэттенское расстояние."""
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
# Приоритетная очередь: (f_score, counter, cell)
|
||||
open_set = [(0, 0, start)]
|
||||
counter = 1
|
||||
|
||||
came_from: Dict[Cell, Optional[Cell]] = {}
|
||||
|
||||
g_score: Dict[Cell, float] = {start: 0}
|
||||
f_score: Dict[Cell, float] = {start: self._heuristic(start, exit_cell)}
|
||||
|
||||
visited_count = 0
|
||||
|
||||
while open_set:
|
||||
current_f, _, current = heapq.heappop(open_set)
|
||||
visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
self._last_visited_count = visited_count
|
||||
return self._reconstruct_path(came_from, start, current)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
tentative_g_score = g_score.get(current, float('inf')) + 1
|
||||
|
||||
if tentative_g_score < g_score.get(neighbor, float('inf')):
|
||||
came_from[neighbor] = current
|
||||
g_score[neighbor] = tentative_g_score
|
||||
f_score[neighbor] = tentative_g_score + self._heuristic(neighbor, exit_cell)
|
||||
heapq.heappush(open_set, (f_score[neighbor], counter, neighbor))
|
||||
counter += 1
|
||||
|
||||
self._last_visited_count = visited_count
|
||||
return []
|
||||
|
||||
@property
|
||||
def last_visited_count(self) -> int:
|
||||
return getattr(self, '_last_visited_count', 0)
|
||||
|
||||
48
MininaVD/docs2/data2/strategiesBfs_strategy.py
Normal file
48
MininaVD/docs2/data2/strategiesBfs_strategy.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from collections import deque
|
||||
from typing import List, Dict, Optional
|
||||
from strategiesPathfinding_strategy import PathFindingStrategy
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в ширину - гарантирует кратчайший путь."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "BFS"
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
queue = deque([start])
|
||||
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||
visited_count = 0 # Для статистики
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
# Сохраняем количество посещённых клеток для статистики
|
||||
self._last_visited_count = visited_count
|
||||
return self._reconstruct_path(came_from, start, current)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in came_from:
|
||||
came_from[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
self._last_visited_count = visited_count
|
||||
return []
|
||||
|
||||
@property
|
||||
def last_visited_count(self) -> int:
|
||||
return getattr(self, '_last_visited_count', 0)
|
||||
|
||||
46
MininaVD/docs2/data2/strategiesDfs_strategy.py
Normal file
46
MininaVD/docs2/data2/strategiesDfs_strategy.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
from strategiesPathfinding_strategy import PathFindingStrategy
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в глубину - быстрый, но не обязательно кратчайший."""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "DFS"
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
stack = [start]
|
||||
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||
visited_count = 0
|
||||
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
self._last_visited_count = visited_count
|
||||
return self._reconstruct_path(came_from, start, current)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in came_from:
|
||||
came_from[neighbor] = current
|
||||
stack.append(neighbor)
|
||||
|
||||
self._last_visited_count = visited_count
|
||||
return []
|
||||
|
||||
@property
|
||||
def last_visited_count(self) -> int:
|
||||
return getattr(self, '_last_visited_count', 0)
|
||||
|
||||
40
MininaVD/docs2/data2/strategiesPathfinding_strategy.py
Normal file
40
MininaVD/docs2/data2/strategiesPathfinding_strategy.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
"""Интерфейс стратегии поиска пути (паттерн Strategy)."""
|
||||
|
||||
@abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
"""
|
||||
Найти путь от start до exit_cell.
|
||||
Возвращает список клеток пути (включая start и exit) или пустой список.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""Имя стратегии для отчётов."""
|
||||
pass
|
||||
|
||||
def _reconstruct_path(self, came_from: dict, start: Cell, current: Cell) -> List[Cell]:
|
||||
"""Восстановить путь из словаря предков."""
|
||||
path = []
|
||||
while current != start:
|
||||
path.append(current)
|
||||
current = came_from.get(current)
|
||||
if current is None:
|
||||
return []
|
||||
path.append(start)
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
89
MininaVD/docs2/data2/visualizationConsole_view.py
Normal file
89
MininaVD/docs2/data2/visualizationConsole_view.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
import os
|
||||
from typing import List, Optional, Set
|
||||
from modelsMaze import Maze
|
||||
from modelsCell import Cell
|
||||
from visualizationObserver import Observer
|
||||
|
||||
class ConsoleView(Observer):
|
||||
"""Консольная визуализация лабиринта."""
|
||||
|
||||
# Символы для отображения
|
||||
SYMBOLS = {
|
||||
'wall': '█',
|
||||
'path': '·',
|
||||
'start': 'S',
|
||||
'exit': 'E',
|
||||
'player': 'P',
|
||||
'solution': '★'
|
||||
}
|
||||
|
||||
def __init__(self, maze: Maze):
|
||||
self.maze = maze
|
||||
self.player_pos: Optional[Cell] = None
|
||||
self.solution_path: Set[Cell] = set()
|
||||
self.messages: List[str] = []
|
||||
|
||||
def update(self, event: str) -> None:
|
||||
"""Обработка событий от MazeSolver."""
|
||||
self.messages.append(f"[СОБЫТИЕ] {event}")
|
||||
self.render()
|
||||
|
||||
def set_solution_path(self, path: List[Cell]) -> None:
|
||||
"""Установить найденный путь для отображения."""
|
||||
self.solution_path = set(path)
|
||||
|
||||
def set_player_position(self, cell: Cell) -> None:
|
||||
"""Установить позицию игрока."""
|
||||
self.player_pos = cell
|
||||
|
||||
def render(self) -> None:
|
||||
"""Отрисовать лабиринт в консоли."""
|
||||
# Очистка консоли (опционально)
|
||||
# os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
print("\n" + "=" * (self.maze.width * 2 + 4))
|
||||
print(f"Лабиринт {self.maze.width}×{self.maze.height}")
|
||||
print("=" * (self.maze.width * 2 + 4))
|
||||
|
||||
for y in range(self.maze.height):
|
||||
row = ""
|
||||
for x in range(self.maze.width):
|
||||
cell = self.maze.get_cell(x, y)
|
||||
if not cell:
|
||||
row += " "
|
||||
continue
|
||||
|
||||
if self.player_pos and cell == self.player_pos:
|
||||
row += self.SYMBOLS['player'] + " "
|
||||
elif cell.is_start:
|
||||
row += self.SYMBOLS['start'] + " "
|
||||
elif cell.is_exit:
|
||||
row += self.SYMBOLS['exit'] + " "
|
||||
elif cell in self.solution_path:
|
||||
row += self.SYMBOLS['solution'] + " "
|
||||
elif cell.is_wall:
|
||||
row += self.SYMBOLS['wall'] * 2
|
||||
else:
|
||||
row += self.SYMBOLS['path'] * 2
|
||||
print(row)
|
||||
|
||||
print("-" * (self.maze.width * 2 + 4))
|
||||
|
||||
# Показать последние сообщения
|
||||
if self.messages:
|
||||
print("Последние события:")
|
||||
for msg in self.messages[-3:]:
|
||||
print(f" {msg}")
|
||||
|
||||
print()
|
||||
|
||||
def clear_messages(self) -> None:
|
||||
"""Очистить сообщения."""
|
||||
self.messages.clear()
|
||||
|
||||
16
MininaVD/docs2/data2/visualizationObserver.py
Normal file
16
MininaVD/docs2/data2/visualizationObserver.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# In[ ]:
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class Observer(ABC):
|
||||
"""Интерфейс наблюдателя (паттерн Observer)."""
|
||||
|
||||
@abstractmethod
|
||||
def update(self, event: str) -> None:
|
||||
"""Обработчик события."""
|
||||
pass
|
||||
|
||||
Loading…
Reference in New Issue
Block a user