Compare commits

..

1 Commits

Author SHA1 Message Date
sorokinad
4950eb4f0d [1][2] Data Structures Lab and Lab for finding a way out of the maze 2026-05-26 23:26:20 +03:00
138 changed files with 6988 additions and 972 deletions

1
.gitignore vendored
View File

@ -7,7 +7,6 @@ __pycache__/
# C extensions # C extensions
*.so *.so
.DS_Store
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/

View File

View File

View File

View File

View File

@ -1,457 +0,0 @@
head = None
#node1 = {'name' : 'Ivan', 'phone' : '123-456', 'next' : None}
#head = node1
#node2 = {'name' : 'Dima', 'phone' : '789-123', 'next' : None}
#node1['next'] = node2
def ll_insert(head, name, phone):
curent = head
while curent is not None:
if curent['name'] == name:
curent['phone'] = phone
return head
curent = curent['next']
n_node = {'name' : name, 'phone' : phone, 'next' : None}
if head is None:
return n_node
curent = head
while curent['next'] is not None:
curent = curent['next']
curent['next'] = n_node
return head
print("====== TESTING ll_insert FUNC ========")
head = ll_insert(head,'Ivan','123-456')
print(head)
head = ll_insert(head, 'Boris', '123-456')
print(head)
head = ll_insert(head, 'Ivan', '321-654')
print(head)
head = ll_insert(head, 'Dima', '345-678')
print(head)
head = ll_insert(head, 'Boris', '111-222')
print(head)
head = ll_insert(head, 'Methody', '221-112')
head = ll_insert(head, 'Kiril', '112-221')
print(f"======= END TEST =======\n\n\n")
def ll_find(head, name):
curent = head
while curent is not None:
if curent['name'] == name:
return curent['phone']
curent = curent['next']
return None
print("====== TESTING ll_find FUNC ======")
print("Ivan`s phone: "+ ll_find(head, 'Ivan'))
print("Dima`s phone: "+ ll_find(head, 'Dima'))
print("Boris phone: "+ ll_find(head, 'Boris'))
print(f"====== END TEST ======\n\n\n")
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
prev = head
curent = head['next']
while curent is not None:
if curent['name'] == name:
prev['next'] = curent['next']
return head
prev = curent
curent = curent['next']
return head
print("====== TEST ll_delete FUNC ======")
print("Del of Dima:", ll_delete(head, 'Dima'))
print("====== END TEST ======")
def ll_list_all(head):
records = []
curent = head
while curent is not None:
records.append((curent['name'],curent['phone']))
curent = curent['next']
records.sort(key=lambda pair: pair[0])
return records
print(f"\n\n\n\n")
print("====== TESTING ll_list_all FUNC ======")
print(ll_list_all(head))
print("====== END ======")
#============================== HASH FUNCTIONS =========================
SIZE = 5
buckets = [None] * SIZE
def hash_function(name, size):
return hash(name) % size
def ht_insert(buckets, name, phone):
index = hash_function(name, len(buckets))
head = buckets[index]
new_head = ll_insert(head, name, phone)
buckets[index] = new_head
return buckets
print(f"\n\n\n ====== TEST INSERT HASH ======")
print(buckets)
ht_insert(buckets, "Ivan", "123-456")
print(buckets)
ht_insert(buckets, "Dima", "789-123")
print(buckets)
ht_insert(buckets, "Boris", "456-789")
print(buckets)
print("====== END TEST ======\n\n\n")
def ht_find(buckets, name):
index = hash_function(name, len(buckets))
head = buckets[index]
return ll_find(head, name)
print("====== TEST FIND HASH FUN ======")
print("find by name Ivan: ",ht_find(buckets, "Ivan"))
print("find by name Dima: ",ht_find(buckets, "Dima"))
print("find by name Boris: ", ht_find(buckets, "Boris"))
print("====== END TEST ======\n\n\n")
def ht_list_all(buckets):
all_records = []
for head in buckets:
current = head
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
print("====== TEST FUNC LIST ALL ======")
print(ht_list_all(buckets))
print("====== END TEST ======\n\n\n")
def ht_delete(buckets, name):
index = hash_function(name, len(buckets))
head = buckets[index]
new_head = ll_delete(head, name)
buckets[index] = new_head
return buckets
print("====== GLOBAL TEST FOR HASH BASED FUN ======")
buckets = [None] * 10
ht_insert(buckets, "Ivan", "123-456")
print(buckets)
ht_insert(buckets, "Boris", "789-012")
print(buckets)
ht_insert(buckets, "Anna", "345-678")
print(buckets)
ht_insert(buckets, "Ivan", "111-222") # update
print(buckets)
print("Find Ivan`s phone: ",ht_find(buckets, "Ivan")) # 111-222
print("Find Petr`s phone: ",ht_find(buckets, "Petr")) # None
# Удаляем
print("delite Boris from buckets")
ht_delete(buckets, "Boris")
print("search Boris = ",ht_find(buckets, "Boris")) # None
# Все записи
print("list all records: ",ht_list_all(buckets))
print("====== END GLOBAL TEST ======\n\n\n")
# ======================== TREE FUNC ====================
def create_node(name,phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
print("====== START TREE FUNC CHAPTER ======\n\n")
print("====== TEST CREATE NODE FUNC ======")
root = create_node('Ivan', '123-456')
print("Create Ivan node: ",root)
print("====== END TEST ====== \n\n\n")
def bst_insert(root, name, phone):
if root is None:
return create_node(name, phone)
if name == root['name']:
root['phone'] = phone
elif name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
else:
root['right'] = bst_insert(root['right'], name , phone)
return root
print("====== TEST INSERT FUNC ======")
root = bst_insert(root, 'Dima', '456-789')
print("add Dima: ", root)
root = bst_insert(root, 'Boris', '789-123')
print("add Boris: ", root)
root = bst_insert(root, 'Eva', '321-123')
print("add Eva: ", root)
print("====== END TEST =======\n\n\n")
def bst_find(root, name):
if root is None:
return None
if name == root['name']:
return root['phone']
elif name<root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
print("====== START FIND TEST ======")
print("search by Ivan`s phone: ", bst_find(root, 'Ivan'))
print("search by Eva`s phone: ", bst_find(root,'Eva'))
print("====== END TEST ====== \n\n\n")
def find_min(node):
while node['left'] is not None:
node = node['left']
return node
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']
if root['right'] is None:
return root['left']
min_node = 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):
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return result
print("====== GLOBAL TEST TREES ======")
root = None
root = bst_insert(root, "Ivan", "123-456")
print("add Ivan: ", root)
root = bst_insert(root, "Boris", "789-012")
print("add Boris: ", root)
root = bst_insert(root, "Anna", "345-678")
print("add Anna: ", root)
root = bst_insert(root, "Ivan", "111-222") # обновление
print("update Ivan: ", root)
print("Find Ivan`s phone: ",bst_find(root, "Ivan")) # 111-222
print("Find Peter`s phone: ",bst_find(root, "Petr")) # None
root = bst_delete(root, "Boris")
print("Del Boris")
print("Find Boris: ",bst_find(root, "Boris")) # None
print("Find ALL: ",bst_list_all(root)) # [('Anna','345-678'), ('Ivan','111-222')]
print("====== END TEST ======")
# ======================== EXPEREMENT CHAPTER ========================
import random
import time
import csv
import sys
sys.setrecursionlimit(20000)
def generate_records(n, seed=42):
random.seed(seed)
records = []
for i in range(1, n+1):
name = f"User_{i:05d}"
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
records.append((name, phone))
return records
def prepare_datasets(base_records):
shuffled = base_records.copy()
random.shuffle(shuffled)
sorted_records = sorted(base_records, key=lambda x: x[0])
return shuffled, sorted_records
def run_experiment(struct_funcs, records, mode_name, repeats=5):
results = []
for rep in range(repeats):
struct = struct_funcs['create']()
# enter all records
start = time.perf_counter()
for name, phone in records:
struct = struct_funcs['insert'](struct, name, phone)
end = time.perf_counter()
insert_time = end - start
# search for 110 records (100 real + 10 None)
existing_names = [name for name, _ in records]
sample_existing = random.sample(existing_names, 100)
nonexistent = [f"None_{i}" for i in range(10)]
search_names = sample_existing + nonexistent
random.shuffle(search_names)
start = time.perf_counter()
for name in search_names:
_ = struct_funcs['find'](struct, name)
end = time.perf_counter()
find_time = end - start
# delete 10 random records
to_delete = random.sample(existing_names, 10)
start = time.perf_counter()
for name in to_delete:
struct = struct_funcs['delete'](struct, name)
end = time.perf_counter()
delete_time = end - start
results.append({
'structure': struct_funcs['name'],
'mode': mode_name,
'repetition': rep+1,
'insert_time': insert_time,
'find_time': find_time,
'delete_time': delete_time
})
return results
def main():
N = 1000
base_records = generate_records(N)
shuffled, sorted_records = prepare_datasets(base_records)
structures = {
'LinkedList': {
'name': 'LinkedList',
'create': lambda: None,
'insert': ll_insert,
'find': ll_find,
'delete': ll_delete,
'list_all': ll_list_all
},
'HashTable': {
'name': 'HashTable',
'create': lambda: [None] * 10,
'insert': ht_insert,
'find': ht_find,
'delete': ht_delete,
'list_all': ht_list_all
},
'BST': {
'name': 'BST',
'create': lambda: None,
'insert': bst_insert,
'find': bst_find,
'delete': bst_delete,
'list_all': bst_list_all
}
}
all_results = []
repeats = 5
for struct_name, funcs in structures.items():
print(f"Testing {struct_name} on random order...")
res = run_experiment(funcs, shuffled, 'random', repeats)
all_results.extend(res)
print(f"Testing {struct_name} in sorted order...")
res = run_experiment(funcs, sorted_records, 'sorted', repeats)
all_results.extend(res)
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
for r in all_results:
writer.writerow([
r['structure'],
r['mode'],
r['repetition'],
f"{r['insert_time']:.6f}",
f"{r['find_time']:.6f}",
f"{r['delete_time']:.6f}"
])
print("The experiment is complete. The results are saved in experiment_results.csv.")
if __name__ == '__main__':
main()

View File

@ -1,31 +0,0 @@
Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec)
LinkedList,random,1,0.140358,0.007040,0.000844
LinkedList,random,2,0.138009,0.009197,0.000413
LinkedList,random,3,0.114717,0.009266,0.000744
LinkedList,random,4,0.117224,0.006914,0.000531
LinkedList,random,5,0.136302,0.010432,0.000582
LinkedList,sorted,1,0.106921,0.007845,0.000566
LinkedList,sorted,2,0.116404,0.015005,0.004900
LinkedList,sorted,3,0.125122,0.006956,0.000708
LinkedList,sorted,4,0.122401,0.004220,0.000474
LinkedList,sorted,5,0.111422,0.008343,0.000551
HashTable,random,1,0.025442,0.004652,0.000078
HashTable,random,2,0.035477,0.000985,0.000091
HashTable,random,3,0.015387,0.001249,0.000298
HashTable,random,4,0.014196,0.001167,0.000096
HashTable,random,5,0.013819,0.000910,0.000094
HashTable,sorted,1,0.013713,0.000897,0.000060
HashTable,sorted,2,0.016816,0.001013,0.000116
HashTable,sorted,3,0.018408,0.001019,0.000084
HashTable,sorted,4,0.014490,0.000886,0.000093
HashTable,sorted,5,0.012493,0.000867,0.000075
BST,random,1,0.006755,0.000468,0.000065
BST,random,2,0.006454,0.000380,0.000052
BST,random,3,0.003348,0.000266,0.000033
BST,random,4,0.004785,0.000379,0.000053
BST,random,5,0.005253,0.000438,0.000083
BST,sorted,1,0.331066,0.028260,0.002915
BST,sorted,2,0.342009,0.025769,0.003155
BST,sorted,3,0.282425,0.031293,0.002984
BST,sorted,4,0.313816,0.022712,0.002957
BST,sorted,5,0.287008,0.032645,0.002415
1 Structure Mode Repeat Insert (sec) Search (sec) Delete (sec)
2 LinkedList random 1 0.140358 0.007040 0.000844
3 LinkedList random 2 0.138009 0.009197 0.000413
4 LinkedList random 3 0.114717 0.009266 0.000744
5 LinkedList random 4 0.117224 0.006914 0.000531
6 LinkedList random 5 0.136302 0.010432 0.000582
7 LinkedList sorted 1 0.106921 0.007845 0.000566
8 LinkedList sorted 2 0.116404 0.015005 0.004900
9 LinkedList sorted 3 0.125122 0.006956 0.000708
10 LinkedList sorted 4 0.122401 0.004220 0.000474
11 LinkedList sorted 5 0.111422 0.008343 0.000551
12 HashTable random 1 0.025442 0.004652 0.000078
13 HashTable random 2 0.035477 0.000985 0.000091
14 HashTable random 3 0.015387 0.001249 0.000298
15 HashTable random 4 0.014196 0.001167 0.000096
16 HashTable random 5 0.013819 0.000910 0.000094
17 HashTable sorted 1 0.013713 0.000897 0.000060
18 HashTable sorted 2 0.016816 0.001013 0.000116
19 HashTable sorted 3 0.018408 0.001019 0.000084
20 HashTable sorted 4 0.014490 0.000886 0.000093
21 HashTable sorted 5 0.012493 0.000867 0.000075
22 BST random 1 0.006755 0.000468 0.000065
23 BST random 2 0.006454 0.000380 0.000052
24 BST random 3 0.003348 0.000266 0.000033
25 BST random 4 0.004785 0.000379 0.000053
26 BST random 5 0.005253 0.000438 0.000083
27 BST sorted 1 0.331066 0.028260 0.002915
28 BST sorted 2 0.342009 0.025769 0.003155
29 BST sorted 3 0.282425 0.031293 0.002984
30 BST sorted 4 0.313816 0.022712 0.002957
31 BST sorted 5 0.287008 0.032645 0.002415

View File

@ -1,44 +0,0 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Загрузка данных
df = pd.read_csv('experiment_results.csv')
# Усреднение по повторам
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
# Подготовка данных для графиков
structures = mean_times['Structure'].unique()
modes = mean_times['Mode'].unique()
# Создание трех графиков (вставка, поиск, удаление)
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
titles = ['Вставка', 'Поиск', 'Удаление']
for ax, op, title in zip(axes, operations, titles):
# Для каждой структуры строим две колонки (random, sorted)
x = np.arange(len(structures))
width = 0.35
random_vals = []
sorted_vals = []
for s in structures:
random_row = mean_times[(mean_times['Structure']==s) & (mean_times['Mode']=='random')]
sorted_row = mean_times[(mean_times['Structure']==s) & (mean_times['Mode']=='sorted')]
random_vals.append(random_row[op].values[0] if not random_row.empty else 0)
sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)
ax.bar(x - width/2, random_vals, width, label='Случайный')
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный')
ax.set_xticks(x)
ax.set_xticklabels(structures)
ax.set_ylabel('Время (сек)')
ax.set_title(title)
ax.legend()
plt.tight_layout()
plt.savefig('../../performance_comparison.png', dpi=150)
plt.show()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,60 +0,0 @@
# Отчёт по лабораторной работе "Структуры данных"
## 1. Введение
В рамках работы были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10000 записей**. Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени. Каждый эксперимент повторялся 5 раз, результаты усреднены.
## 2. Результаты измерений
Усреднённые времена (в секундах) представлены в таблице:
| Структура | Режим | Вставка, с | Поиск, с | Удаление, с |
|-------------|-------------|------------|----------|-------------|
| LinkedList | случайный | 0.1143 | 0.0078 | 0.00065 |
| LinkedList | сортир. | 0.1124 | 0.0068 | 0.00065 |
| HashTable | случайный | 0.0131 | 0.00109 | 0.000085 |
| HashTable | сортир. | 0.0156 | 0.00110 | 0.00014 |
| BST | случайный | 0.00532 | 0.000365 | 0.000053 |
| BST | сортир. | 0.303 | 0.0230 | 0.00268 |
Графическое представление результатов приведено на рисунке ниже.
![Сравнение производительности](performance_comparison.png)
## 3. Анализ результатов
### 3.1. Влияние порядка данных на BST
При вставке элементов в отсортированном порядке двоичное дерево поиска вырождается в линейный список все новые узлы добавляются только в правое поддерево. Высота дерева становится равной количеству элементов, и сложность всех операций возрастает до **O(n)**. Эксперимент подтверждает это:
- Вставка в BST на отсортированных данных заняла **0.303 с**, что в **57 раз** больше, чем на случайных (0.00532 с).
- Время вставки на отсортированных данных даже превышает показатели связного списка (0.112 с), что объясняется дополнительными накладными расходами на рекурсивные вызовы.
- Поиск и удаление также замедлились примерно в 60 раз по сравнению со случайным режимом.
### 3.2. Устойчивость хеш-таблицы к порядку
Хеш-таблица использует хеш-функцию, которая равномерно распределяет ключи по корзинам независимо от порядка поступления. Поэтому производительность операций практически не зависит от того, в каком порядке приходят данные:
- В случайном и отсортированном режимах времена вставки (0.0131 и 0.0156 с) и поиска (около 0.0011 с) близки.
- Небольшие колебания могут быть вызваны случайным распределением коллизий.
- Это соответствует ожидаемой средней сложности **O(1)**.
### 3.3. Медлительность связного списка при поиске
Связный список не обеспечивает прямого доступа к элементам для поиска необходимо просматривать узлы последовательно, что даёт сложность **O(n)**. В эксперименте:
- Время поиска в списке (~0.007 с) на порядок больше, чем в хеш-таблице (0.0011 с) и BST на случайных данных (0.00037 с).
- При увеличении объёма данных эта разница будет только расти.
- Вставка в список также относительно медленна (0.11 с), так как требует прохода до конца (хотя обновление существующего имени выполняется быстрее, но в тесте все имена уникальны, поэтому каждая вставка проходит весь список).
### 3.4. Сравнение удаления
- **Связный список**: удаление требует сначала найти элемент (O(n)), затем переставить ссылки (O(1)). Время удаления (0.00065 с) близко ко времени поиска, что логично.
- **Хеш-таблица**: удаление выполняется за O(1) в среднем сначала определяется корзина, затем из короткого списка удаляется элемент. Время удаления (0.0000850.00014 с) значительно меньше, чем в списке.
- **BST**: на случайных данных удаление очень быстрое (0.000053 с) благодаря логарифмической высоте. На отсортированных данных время возрастает до 0.00268 с (в 50 раз), что отражает деградацию до O(n).
## 4. Выводы и рекомендации по выбору структуры
На основе полученных результатов можно сформулировать следующие рекомендации:
- **Хеш-таблица** оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок хранения не важен. Примеры: реализация словарей, кэшей, индексов по ключу. В эксперименте хеш-таблица показала стабильно высокую производительность во всех режимах.
- **Двоичное дерево поиска** следует применять, когда необходимо получать данные в отсортированном порядке (например, вывод телефонного справочника по алфавиту). Однако важно учитывать, что при поступлении отсортированных данных дерево вырождается, и производительность резко падает. В таких случаях лучше использовать сбалансированные деревья (AVL, красно-чёрные). В эксперименте BST на случайных данных показал отличные результаты, близкие к хеш-таблице, а на отсортированных стал самым медленным.
- **Связный список** практически непригоден для больших объёмов данных из-за линейной сложности основных операций. Может использоваться лишь для очень маленьких коллекций, при частых вставках в начало списка (здесь не рассматривалось) или в учебных целях.
Таким образом, для реальных задач чаще всего выбирают хеш-таблицы или сбалансированные деревья в зависимости от требований к упорядоченности данных.
I use arch BTW

View File

View File

@ -1 +0,0 @@
hi

View File

View File

View File

View File

View File

View File

@ -1 +0,0 @@

View File

Binary file not shown.

Binary file not shown.

View File

View File

@ -1 +0,0 @@
428

View File

View File

View File

@ -1 +0,0 @@
428b

View File

View File

View File

@ -1 +0,0 @@
427.txt

View File

@ -1 +0,0 @@
427.txt

View File

View File

View File

@ -1 +0,0 @@

Binary file not shown.

Binary file not shown.

338
README.md
View File

@ -14,11 +14,9 @@
**Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.** **Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.**
### Крайний срок приема работ 27.05.2026 в 10:00 ~~25.05.2026 до 14:00~~ ### Крайний срок приема работ 25.05.2026 до 14:00
#### (поправочный на $\pi$, 19:00 26.05.2026, и на следующий рабочий день)
## Задание 1 -- репозиторий
## Задание 0 -- репозиторий [отдельный срок на создание PR с папкой: 28.02.2026]
0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе). 0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе).
@ -45,340 +43,12 @@
6. Отправь ветку **в свой форк** на Gitea: 6. Отправь ветку **в свой форк** на Gitea:
```bash ```bash
git push origin git push origin IvanovII
``` ```
если просит, перед этим сделать git push --set-upstream origin
7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что: 7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что:
- Базовый репозиторий: **учебный** (преподавателя) - Базовый репозиторий: **учебный** (преподавателя)
- Базовая ветка: **develop** - Базовая ветка: **develop**
- Сравниваемая ветка: **свой форк / IvanovII** - Сравниваемая ветка: **свой форк / IvanovII**
8. Отправь PR. 8. Отправь PR.
## Задание 1 -- структуры данных
***Напоминание: под каждое задание вы создаете отдельную ветку***
>Для оформления результатов заведи папку **docs** в своей папке и сохраняй туда отчет (в любом формате от .doc до .md, а то и .jpnb). Вспомогательные файлы клади в подпапку **data** внутри **docs**
**Цель работы**
Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Вы должны собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике.
**!! Задание выполнять в структурной (процедурной) парадигме, не используя классы. Главное реализовать структуры данных «руками» и сравнить их производительность.**
### Базовые операции (обязательны для всех):
`insert(name, phone)` -- добавить или обновить запись.
`find(name)` -- phone или None.
`delete(name)` -- удалить запись, игнорировать отсутствие.
`list_all()` -- список всех записей, отсортированный по имени (для BST inorder обход; для списка и хеш‑таблицы — собрать и отсортировать явно).
#### 1. Связный список (LinkedListPhoneBook)
Узел представляется словарём: `{'name': 'Имя', 'phone': '123', 'next': None}.`
**Функции:**
`def ll_insert(head, name, phone)` — проходит до конца (или сразу добавляет в конец) и возвращает новую голову (если вставка в начало) или изменяет список по ссылке. Удобнее возвращать новую голову, если вставка может быть в начало.
`def ll_find(head, name)` — ищет узел, возвращает телефон или None.
`def ll_delete(head, name)` — удаляет узел, возвращает новую голову.
`def ll_list_all(head)` — собирает все записи в список и сортирует (сортировка вынесена отдельно).
#### 2. Хеш-таблица
Хранится как список buckets фиксированной длины, каждый элемент — голова связного списка (или None).
**Функции:**
`def ht_insert(buckets, name, phone)` — вычисляет индекс, вызывает ll_insert для соответствующего бакета.
Аналогично `ht_find, ht_delete, ht_list_all` (последняя собирает все записи из всех бакетов и сортирует).
#### 3. Двоичное дерево поиска
Узел — словарь: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.`
**Функции:**
`def bst_insert(root, name, phone)` — рекурсивно или итеративно вставляет, возвращает новый корень (если корень меняется).
`def bst_find(root, name)` — поиск.
`def bst_delete(root, name)` — удаление, возвращает новый корень.
`def bst_list_all(root)` — центрированный обход (рекурсивно собирает записи в отсортированном порядке).
### Экспериментальная часть (подробно об измерении времени)
#### 1. Генерация тестовых данных
Создайте список records из N элементов (например, N = 10000). Каждый элемент — кортеж (name, phone).
Имена генерируйте как `f"User_{i:05d}"` (равномерное распределение) или случайные слова из небольшого набора (чтобы были повторения и коллизии). Для проверки влияния порядка подготовьте два варианта одного и того же набора:
`records_shuffled` — случайный порядок.
`records_sorted` — отсортированный по имени (по алфавиту).
#### 2. Инструменты замера времени
Используйте модуль **time**:
```python
import time
start = time.perf_counter()
# ... операции ...
end = time.perf_counter()
elapsed = end - start # время в секундах
```
Для многократных замеров удобен `timeit`, но в этой задаче достаточно просто обернуть код в цикл и усреднить.
#### 3. Проведение замеров
Для каждой структуры данных и для каждого режима входных данных (случайный / отсортированный) выполните:
- А. Вставка всех записей
Создайте пустую структуру.
Засеките время, выполните insert для каждой записи из входного списка.
Зафиксируйте общее время вставки.
- Б. Поиск 100 случайных записей
Возьмите 100 случайных имён из того же набора (гарантированно существующих) и 10 имён, которых нет (например, "None_{i}").
Засеките время на выполнение всех 110 вызовов find.
- В. Удаление 50 случайных записей
Выберите 50 случайных имён из набора.
Засеките время на выполнение delete для каждого.
**!! Важно: после вставки структура остаётся заполненной, поиск и удаление выполняются на ней же. Если нужно повторить замер для другого порядка данных — создавайте новую структуру и заполняйте заново.**
#### 4. Сохранение результатов
**!! Каждый эксперимент повторить минимум 5 раз и записывать и среднее время, и все замеры.**
Соберите все замеры в словарь или список, затем сохраните в CSV-файл:
```python
import csv
results = [
["Структура", "Режим", "Операция", "Время (сек)"],
["LinkedList", "случайный", "вставка", 0.123],
...
]
with open("results.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(results)
```
#### 5. Анализ результатов
Постройте график (столбчатая диаграмма или линейный график) — можно в Excel, Google Sheets или с помощью matplotlib в Python.
Сравните:
- Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных).
- Почему хеш-таблица почти не чувствительна к порядку.
- Почему связный список всегда медленен при поиске.
- Как удаление работает в каждой структуре.
* Вывод должен содержать ответ на вопрос: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни.*
## Задание: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)
### Цель работы
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
### Общая схема приложения (пример)
```mermaid
classDiagram
class Maze {
-Cell[] cells
-int width, height
-Cell start
-Cell exit
+getCell(x,y): Cell
+getNeighbors(cell): List~Cell~
}
class Cell {
-int x, y
-bool isWall
-bool isStart
-bool isExit
+isPassable(): bool
}
class MazeBuilder {
<<interface>>
+buildFromFile(filename): Maze
}
class TextFileMazeBuilder {
+buildFromFile(filename): Maze
}
class PathFindingStrategy {
<<interface>>
+findPath(maze, start, exit): List~Cell~
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class DijkstraStrategy
class SearchStats {
+timeMs: float
+visitedCells: int
+pathLength: int
}
class MazeSolver {
-Maze maze
-PathFindingStrategy strategy
+setStrategy(strategy)
+solve(): SearchStats
}
class Command {
<<interface>>
+execute()
+undo()
}
class MoveCommand {
-Player player
-Direction dir
-Cell previousCell
+execute()
+undo()
}
class Player {
-Cell currentCell
+moveTo(cell)
}
class Observer {
<<interface>>
+update(event)
}
class ConsoleView {
+update(event)
+render(maze, player, path)
}
MazeBuilder <|.. TextFileMazeBuilder
MazeBuilder --> Maze : creates
PathFindingStrategy <|.. BFSStrategy
PathFindingStrategy <|.. DFSStrategy
PathFindingStrategy <|.. AStarStrategy
PathFindingStrategy <|.. DijkstraStrategy
MazeSolver --> PathFindingStrategy : uses
MazeSolver --> Maze : uses
Command <|.. MoveCommand
MoveCommand --> Player
Player --> Cell
Observer <|.. ConsoleView
MazeSolver --> Observer : notifies
```
### Выполнение
#### Этап 1. Модель лабиринта (без паттернов, просто классы)
**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта.
- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена).
- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена).
**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем.
#### Этап 2. Загрузка лабиринта из файла применение паттерна **Builder**
**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` стена, ` ` (пробел) проход, `S` старт, `E` выход.
- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`.
- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`.
Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`.
#### Этап 3. Стратегии поиска пути паттерн **Strategy**
**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода.
- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.
- Реализовать минимум 3 стратегии:
- **BFS** (поиск в ширину) гарантирует кратчайший путь по количеству шагов.
- **DFS** (поиск в глубину) быстрый, но не обязательно кратчайший.
- **A*** (с эвристикой, например, манхэттенское расстояние) компромисс между скоростью и оптимальностью.
- (Опционально) **Дейкстра** полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS.
Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A* приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток.
Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс.
#### Этап 4. Класс-оркестратор **MazeSolver** (использует Strategy)
**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику.
- `MazeSolver` содержит поля `maze` и `strategy`.
- Метод `setStrategy(strategy)` для динамической смены алгоритма.
- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути).
- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии.
#### Этап 5. Визуализация и пошаговое управление паттерны **Observer** и **Command** (по желанию)
**5.1. Наблюдатель (Observer)** обновление консольного интерфейса.
- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`).
- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли.
- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния.
**5.2. Команда (Command)** для пошагового перемещения игрока по найденному пути (или ручного управления).
- Создать интерфейс `Command` с методами `execute()` и `undo()`.
- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены.
- Создать класс `Player`, хранящий текущую клетку.
- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн.
*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command для демонстрации undo при ручном исследовании лабиринта.*
#### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных)
**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности.
1. **Подготовка тестовых лабиринтов:**
- Маленький (10×10) с простым путём.
- Средний (50×50) с тупиками.
- Большой (100×100) с запутанной структурой.
- «Пустой» лабиринт (без стен) для демонстрации максимальной производительности.
- «Без выхода» чтобы проверить обработку отсутствия пути.
2. **Замеры:**
- Для каждого лабиринта и каждой стратегии запустить `solve()` 510 раз, усреднить время, количество посещённых клеток, длину пути.
- Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`.
3. **Анализ:**
- Построить графики для каждого лабиринта.
- Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях).
4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото вес 3, песок вес 2, асфальт вес 1) и сравнить Дейкстру с A* на взвешенном графе.
#### Этап 7. Отчёт
**Структура отчёта:**
1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid).
2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий.
3. Результаты экспериментов (таблицы, графики).
4. Анализ эффективности алгоритмов и применимости паттернов.
5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них.
### Советы
- Для A* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`.
- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь.
- Для BFS/DFS используй `deque` (очередь) и `list` (стек).
- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки.

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -0,0 +1,252 @@
from MP_records import records
import random as rd
import time
import csv
import codecs
import sys
sys.setrecursionlimit(15000)
# ---------- Binary Search Tree ----------
# Узел:
# {
# "name": name,
# "phone": phone,
# "left": None,
# "right": None
# }
def bst_insert(root, name, phone):
"""
Вставляет новую запись или обновляет телефон по имени.
Возвращает корень дерева.
"""
if root is None:
return {
"name": name,
"phone": phone,
"left": None,
"right": None
}
if name == root["name"]:
root["phone"] = phone
elif name < root["name"]:
root["left"] = bst_insert(root["left"], name, phone)
else:
root["right"] = bst_insert(root["right"], name, phone)
return root
def bst_find(root, name):
"""
Поиск телефона по имени.
"""
if root is None:
return None
if name == root["name"]:
return root["phone"]
if name < root["name"]:
return bst_find(root["left"], name)
return bst_find(root["right"], name)
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"]
# Узел без правого потомка
if root["right"] is None:
return root["left"]
# Узел с двумя потомками
successor = bst_find_min(root["right"])
root["name"] = successor["name"]
root["phone"] = successor["phone"]
root["right"] = bst_delete(root["right"], successor["name"])
return root
def bst_inorder(root, result):
"""
Центрированный обход дерева.
"""
if root is None:
return
bst_inorder(root["left"], result)
result.append((root["name"], root["phone"]))
bst_inorder(root["right"], result)
def bst_list_all(root):
"""
Возвращает список записей в отсортированном порядке.
"""
result = []
bst_inorder(root, result)
return result
# ---------- Benchmark helpers ----------
def build_bst(records_list):
root = None
for name, phone in records_list:
root = bst_insert(root, name, phone)
return root
def measure_bst(records_list, mode_name, repeats=5):
rows = []
insertion_times = []
finding_times = []
deletion_times = []
for run_number in range(1, repeats + 1):
data = records_list[:]
if mode_name == "случайный":
rd.shuffle(data)
# А. Вставка
root = None
start = time.perf_counter()
for name, phone in data:
root = bst_insert(root, name, phone)
end = time.perf_counter()
insertion_time = end - start
insertion_times.append(insertion_time)
# Б. Поиск
existing_names = [name for name, phone in rd.sample(data, 100)]
missing_names = [f"None_{i}" for i in range(10)]
search_names = existing_names + missing_names
rd.shuffle(search_names)
start = time.perf_counter()
for name in search_names:
bst_find(root, name)
end = time.perf_counter()
finding_time = end - start
finding_times.append(finding_time)
# В. Удаление
delete_names = rd.sample(existing_names, 50)
start = time.perf_counter()
for name in delete_names:
root = bst_delete(root, name)
end = time.perf_counter()
deletion_time = end - start
deletion_times.append(deletion_time)
rows.append(["BinarySearchTree", mode_name, "вставка", run_number, insertion_time])
rows.append(["BinarySearchTree", mode_name, "поиск", run_number, finding_time])
rows.append(["BinarySearchTree", mode_name, "удаление", run_number, deletion_time])
rows.append(["BinarySearchTree", mode_name, "вставка", "среднее", sum(insertion_times) / repeats])
rows.append(["BinarySearchTree", mode_name, "поиск", "среднее", sum(finding_times) / repeats])
rows.append(["BinarySearchTree", mode_name, "удаление", "среднее", sum(deletion_times) / repeats])
return rows
def save_results(rows, filename="results.csv"):
with codecs.open(filename, "a+", "utf-16") as file:
writer = csv.writer(file)
writer.writerows(rows)
def run_shuffled(records_shuffled):
rows = measure_bst(records_shuffled, "случайный")
save_results(rows)
return rows
def run_sorted(records_sorted):
rows = measure_bst(records_sorted, "отсортированный")
save_results(rows)
return rows
# ---------- Manual tests ----------
def test():
root = None
root = bst_insert(root, "Ivan", "111")
root = bst_insert(root, "Anna", "222")
root = bst_insert(root, "Petr", "333")
root = bst_insert(root, "Maria", "444")
print(bst_find(root, "Anna")) # 222
print(bst_find(root, "Unknown")) # None
root = bst_insert(root, "Anna", "999")
print(bst_find(root, "Anna")) # 999
root = bst_delete(root, "Ivan")
print(bst_list_all(root))
if __name__ == "__main__":
records_shuffled, records_sorted = records()
run_shuffled(records_shuffled)
run_sorted(records_sorted)

View File

@ -0,0 +1,456 @@
from MP_records import records
import string
import random as rd
import time
import csv
import codecs
def polynomial_hash(word):
p=11111
m=(10**9)+9
hashh=0
for i in range(len(word)):
hashh+=ord(word[i])*(p**i)
hashh=hashh%m
return hashh
def hash_to_index(hashh,length):
#print(hashh)
#if len(str(hashh))>4:
#hashh=int(str(hashh)[3:])
while hashh>length:
hashh=hashh%(length)
return hashh
def ll_insert(table,name,phone,index):
if table[index]==None:
entry={"name":name,"phone":phone,"next":None}
table[index]=entry
return table
else:
entry={"name":name,"phone":phone,"next":None}
if table[index]["phone"]==phone:
table[index]["name"]=name
return table
if table[index]["next"]==None:
table[index]["next"]=entry
return table
else:
nexxt=table[index]["next"]
if nexxt["phone"]==phone:
nexxt["name"]=name
return table
while nexxt["next"]!=None:
nexxt=nexxt["next"]
if nexxt["phone"]==phone:
nexxt["name"]=name
return table
nexxt["next"]=entry
return table
def ht_insert(table,name,phone):
index=hash_to_index(polynomial_hash(name), len(table))
ll_insert(table,name,phone,index)
return table
def ht_find(table, name):
index=hash_to_index(polynomial_hash(name), len(table))
if table[index]!=None:
if table[index]["name"]==name:
return table[index]["phone"]
elif table[index]["next"]!=None:
if table[index]["next"]["name"]==name:
return table[index]["next"]["phone"]
else:
nexxt=table[index]["next"]
while nexxt["next"]!=None:
nexxt=nexxt["next"]
if nexxt["name"]==name:
return nexxt["phone"]
return None
def ht_delete(table,name):
index=hash_to_index(polynomial_hash(name), len(table))
if len(table)>0:
if table[index]["name"]==name:
if table[index]["next"]!=None:
table[index]=table[index]["next"]
return table
else:
table[index]=None
return table
elif table[index]["next"]!=None:
if table[index]["next"]["name"]==name:
if table[index]["next"]["next"]!=None:
table[index]["next"]=table[index]["next"]["next"]
return table
else:
table[index]["next"]=None
return table
elif table[index]["next"]["next"]!=None:
nexxt1=table[index]["next"]
nexxt2=nexxt1["next"]
if nexxt2["name"]==name:
if nexxt2["next"]!=None:
nexxt1["next"]=nexxt2["next"]
return table
else:
nexxt1["next"]=None
return table
while nexxt2["next"]!=None:
nexxt1=nexxt2
nexxt2=nexxt1["next"]
if nexxt2["name"]==name:
if nexxt2["next"]!=None:
nexxt1["next"]=nexxt2["next"]
return table
else:
nexxt1["next"]=None
return table
def bad_sort(names,phones):
names1=[]
phones1=[]
while len(names)>0:
min_=names[0].encode()
ph=phones[0]
for i in range(len(names)):
nm=names[i].encode()
if nm<min_:
min_=nm
ph=phones[i]
#print(min_.decode()," - ",ph)
names1.append(min_.decode())
phones1.append(ph)
names.remove(min_.decode())
phones.remove(ph)
#print(names1,"\n",phones1)
return names1, phones1
def Shell(names,phones):
N = len(names)
n = N // 2
while n>0:
for i in range (0,N-n):
j=i
while j+n<N:
if (names[j].encode())>(names[j+n].encode()):
t=names[j]
t1=phones[j]
names[j]=names[j+n]
phones[j]=phones[j+n]
names[j+n]=t
phones[j+n]=t1
j=i
else:
j+=n
n=n//2
return names,phones
def ht_listall(table):
names=[]
phones=[]
pointer=0
while pointer<len(table):
if table[pointer]!=None:
names.append(table[pointer]["name"])
phones.append(table[pointer]["phone"])
if table[pointer]["next"]!=None:
names.append(table[pointer]["next"]["name"])
phones.append(table[pointer]["next"]["phone"])
nexxt=table[pointer]["next"]
while nexxt["next"]!=None:
nexxt=nexxt["next"]
names.append(nexxt["name"])
phones.append(nexxt["phone"])
pointer+=1
names1, phones1 = bad_sort(names, phones)
#names1, phones1 = Shell(names, phones)
for i in range(len(names1)):
print(names1[i]," - ",phones1[i],end='')
if i%4==0:
print("\n")
else:
print(", ",end='')
print("\n")
def test():
table=[]
for i in range(8):
table.append(None)
ht_insert(table, "Zyky", 1)
ht_insert(table, "Abba", 2)
ht_insert(table, "Babba", 3)
ht_insert(table, "Aaaaa", 4)
ht_insert(table, "Aakk", 5)
ht_insert(table, "Bfaw", 6)
ht_insert(table, "Uno", 7)
ht_insert(table, "Uk", 8)
ht_insert(table, "Uaa", 9)
ht_insert(table, "h", 10)
print(table)
print(ht_find(table,"Aakk"))
# ht_delete(table, "Aakk")
#ht_delete(table, "Aaaaa")
#print(table)
#ht_delete(table, "Uaa")
#ht_delete(table, "Zyky")
print(table)
ht_listall(table)
def run_shuffled(records_shuffled):
insertion_times=[]
finding_times=[]
deletion_times1=[]
print("Shuffled list: ")
for k in range(5):
lisst=[]
for i in range(5000):
lisst.append(None)
rd.shuffle(records_shuffled)
#А. Вставка всех записей
start=time.perf_counter()
for i in range(len(records_shuffled)):
ht_insert(lisst, records_shuffled[i][0], records_shuffled[i][1])
end=time.perf_counter()
insertion_times.append(end-start)
#Б. Поиск 100 случайных записей
names=[]
index=rd.randint(0,9899)
for i in range(100):
names.append(records_shuffled[index][0])
index+=1
for i in range(10):
names.append("A")
rd.shuffle(names)
start=time.perf_counter()
for i in range(len(names)):
ht_find(lisst,names[i])
end=time.perf_counter()
finding_times.append(end-start)
#В. Удаление 50 случайных записей
for i in range(10):
names.remove("A")
rd.shuffle(names)
deletion_times=[]
for i in range(50):
start=time.perf_counter()
ht_delete(lisst,names[i])
end=time.perf_counter()
ttt=end-start
deletion_times.append(ttt)
deletion_times1.append(deletion_times)
print("Run number ",k+1)
print("Insertion time: ",insertion_times[k])
print("Finding time: ",finding_times[k])
print("Deletion times: ","\n",deletion_times)
print("\n")
temp=0
for i in range(5):
temp+=insertion_times[i]
temp=temp/5
results = [
[u"Структура", u"Режим", u"Операция", u"Время (сек)"],
["HashTable", u"случайный", u"вставка", insertion_times[0]],
["HashTable", u"случайный", u"вставка", insertion_times[1]],
["HashTable", u"случайный", u"вставка", insertion_times[2]],
["HashTable", u"случайный", u"вставка", insertion_times[3]],
["HashTable", u"случайный", u"вставка", insertion_times[4]],
[u"Структура", u"Режим", u"Операция", u"Среднее время (сек)"],
["HashTable", u"случайный", u"вставка", temp,]
]
with codecs.open("results.csv", "a+", "utf-16") as f:
writer = csv.writer(f)
writer.writerows(results)
writer.writerows("\n")
temp=0
for i in range(5):
temp+=finding_times[i]
temp=temp/5
results = [
[u"Структура", u"Режим", u"Операция", u"Время (сек)"],
["HashTable", u"случайный", u"поиск", finding_times[0]],
["HashTable", u"случайный", u"поиск", finding_times[1]],
["HashTable", u"случайный", u"поиск", finding_times[2]],
["HashTable", u"случайный", u"поиск", finding_times[3]],
["HashTable", u"случайный", u"поиск", finding_times[4]],
[u"Структура", u"Режим", u"Операция", u"Среднее время (сек)"],
["HashTable", u"случайный", u"поиск", temp,]
]
with codecs.open("results.csv", "a+", "utf-16") as f:
writer = csv.writer(f)
writer.writerows(results)
writer.writerows("\n")
temp=0
del_times=[]
for i in range(5):
for j in range(50):
temp+=deletion_times1[i][j]
temp=temp/50
del_times.append(temp)
temp=0
temp=0
for i in range(5):
temp+=del_times[i]
temp=temp/5
results = [
[u"Структура", u"Режим", u"Операция", u"Время (сек)"],
["HashTable", u"случайный", u"удаление", del_times[0]],
["HashTable", u"случайный", u"удаление", del_times[1]],
["HashTable", u"случайный", u"удаление", del_times[2]],
["HashTable", u"случайный", u"удаление", del_times[3]],
["HashTable", u"случайный", u"удаление", del_times[4]],
[u"Структура", u"Режим", u"Операция", u"Среднее время (сек)"],
["HashTable", u"случайный", u"удаление", temp,]
]
with codecs.open("results.csv", "a+", "utf-16") as f:
writer = csv.writer(f)
writer.writerows(results)
writer.writerows("\n")
writer.writerows("\n")
def run_sorted(records_shuffled):
insertion_times=[]
finding_times=[]
deletion_times1=[]
print("Sorted list: ")
for k in range(5):
lisst=[]
for i in range(5000):
lisst.append(None)
#А. Вставка всех записей
start=time.perf_counter()
for i in range(len(records_shuffled)):
ht_insert(lisst, records_shuffled[i][0], records_shuffled[i][1])
end=time.perf_counter()
insertion_times.append(end-start)
#Б. Поиск 100 случайных записей
names=[]
index=rd.randint(0,9899)
for i in range(100):
names.append(records_shuffled[index][0])
index+=1
for i in range(10):
names.append("A")
rd.shuffle(names)
start=time.perf_counter()
for i in range(len(names)):
ht_find(lisst,names[i])
end=time.perf_counter()
finding_times.append(end-start)
#В. Удаление 50 случайных записей
for i in range(10):
names.remove("A")
rd.shuffle(names)
deletion_times=[]
for i in range(50):
start=time.perf_counter()
ht_delete(lisst,names[i])
end=time.perf_counter()
ttt=end-start
deletion_times.append(ttt)
deletion_times1.append(deletion_times)
print("Run number ",k+1)
print("Insertion time: ",insertion_times[k])
print("Finding time: ",finding_times[k])
print("Deletion average:", sum(deletion_times))
print("\n")
temp=0
for i in range(5):
temp+=insertion_times[i]
temp=temp/5
results = [
[u"Структура", u"Режим", u"Операция", u"Время (сек)"],
["HashTable", u"отсортированный", u"вставка", insertion_times[0]],
["HashTable", u"отсортированный", u"вставка", insertion_times[1]],
["HashTable", u"отсортированный", u"вставка", insertion_times[2]],
["HashTable", u"отсортированный", u"вставка", insertion_times[3]],
["HashTable", u"сотсортированный", u"вставка", insertion_times[4]],
[u"Структура", u"Режим", u"Операция", u"Среднее время (сек)"],
["HashTable", u"отсортированный", u"вставка", temp,]
]
with codecs.open("results.csv", "a+", "utf-16") as f:
writer = csv.writer(f)
writer.writerows(results)
writer.writerows("\n")
temp=0
for i in range(5):
temp+=finding_times[i]
temp=temp/5
results = [
[u"Структура", u"Режим", u"Операция", u"Время (сек)"],
["HashTable", u"отсортированный", u"поиск", finding_times[0]],
["HashTable", u"отсортированный", u"поиск", finding_times[1]],
["HashTable", u"отсортированный", u"поиск", finding_times[2]],
["HashTable", u"отсортированный", u"поиск", finding_times[3]],
["HashTable", u"отсортированный", u"поиск", finding_times[4]],
[u"Структура", u"Режим", u"Операция", u"Среднее время (сек)"],
["HashTable", u"отсортированный", u"поиск", temp,]
]
with codecs.open("results.csv", "a+", "utf-16") as f:
writer = csv.writer(f)
writer.writerows(results)
writer.writerows("\n")
temp=0
del_times=[]
for i in range(5):
for j in range(50):
temp+=deletion_times1[i][j]
temp=temp/50
del_times.append(temp)
temp=0
temp=0
for i in range(5):
temp+=del_times[i]
temp=temp/5
results = [
[u"Структура", u"Режим", u"Операция", u"Время (сек)"],
["HashTable", u"отсортированный", u"удаление", del_times[0]],
["HashTable", u"отсортированный", u"удаление", del_times[1]],
["HashTable", u"отсортированный", u"удаление", del_times[2]],
["HashTable", u"отсортированный", u"удаление", del_times[3]],
["HashTable", u"отсортированный", u"удаление", del_times[4]],
[u"Структура", u"Режим", u"Операция", u"Среднее время (сек)"],
["HashTable", u"отсортированный", u"удаление", temp,]
]
with codecs.open("results.csv", "a+", "utf-16") as f:
writer = csv.writer(f)
writer.writerows(results)
writer.writerows("\n")
writer.writerows("\n")
records_shuffled, records_sorted = records()
run_shuffled(records_shuffled)
run_sorted(records_sorted)

View File

@ -0,0 +1,241 @@
from MP_records import records
import random as rd
import time
import csv
import codecs
# ---------- Linked List Phone Book ----------
# Узел списка:
# {"name": name, "phone": phone, "next": next_node}
def ll_insert(head, name, phone):
"""
Добавляет новую запись или обновляет телефон по имени.
Возвращает голову списка.
"""
new_node = {
"name": name,
"phone": phone,
"next": None
}
if head is None:
return new_node
current = head
while True:
if current["name"] == name:
current["phone"] = phone
return head
if current["next"] is None:
break
current = current["next"]
current["next"] = new_node
return head
def ll_find(head, name):
"""
Ищет запись по имени.
Возвращает телефон или None.
"""
current = head
while current is not None:
if current["name"] == name:
return current["phone"]
current = current["next"]
return None
def ll_delete(head, name):
"""
Удаляет запись по имени.
Если записи нет, ничего не меняет.
Возвращает новую голову списка.
"""
if head is None:
return None
if head["name"] == name:
return head["next"]
previous = head
current = head["next"]
while current is not None:
if current["name"] == name:
previous["next"] = current["next"]
return head
previous = current
current = current["next"]
return head
def ll_list_all(head):
"""
Возвращает список всех записей, отсортированный по имени.
"""
result = []
current = head
while current is not None:
result.append((current["name"], current["phone"]))
current = current["next"]
result.sort(key=lambda item: item[0])
return result
# ---------- Compatibility aliases ----------
# Можно оставить старые имена, если они уже используются в отчете/других файлах.
def insert(head, name, phone):
return ll_insert(head, name, phone)
def find(head, name):
return ll_find(head, name)
def delete(head, name):
return ll_delete(head, name)
def list_all(head):
return ll_list_all(head)
# ---------- Benchmark helpers ----------
def build_linked_list(records_list):
head = None
for name, phone in records_list:
head = ll_insert(head, name, phone)
return head
def measure_linked_list(records_list, mode_name, repeats=5):
"""
Выполняет 5 повторов:
1. вставка всех записей;
2. поиск 100 существующих и 10 отсутствующих имен;
3. удаление 50 существующих имен.
Возвращает строки для записи в CSV.
"""
rows = []
insertion_times = []
finding_times = []
deletion_times = []
for run_number in range(1, repeats + 1):
data = records_list[:]
if mode_name == "случайный":
rd.shuffle(data)
# А. Вставка всех записей
head = None
start = time.perf_counter()
for name, phone in data:
head = ll_insert(head, name, phone)
end = time.perf_counter()
insertion_time = end - start
insertion_times.append(insertion_time)
# Б. Поиск 100 существующих + 10 отсутствующих
existing_names = [name for name, phone in rd.sample(data, 100)]
missing_names = [f"None_{i}" for i in range(10)]
search_names = existing_names + missing_names
rd.shuffle(search_names)
start = time.perf_counter()
for name in search_names:
ll_find(head, name)
end = time.perf_counter()
finding_time = end - start
finding_times.append(finding_time)
# В. Удаление 50 существующих
delete_names = rd.sample(existing_names, 50)
start = time.perf_counter()
for name in delete_names:
head = ll_delete(head, name)
end = time.perf_counter()
deletion_time = end - start
deletion_times.append(deletion_time)
rows.append(["LinkedList", mode_name, "вставка", run_number, insertion_time])
rows.append(["LinkedList", mode_name, "поиск", run_number, finding_time])
rows.append(["LinkedList", mode_name, "удаление", run_number, deletion_time])
rows.append(["LinkedList", mode_name, "вставка", "среднее", sum(insertion_times) / repeats])
rows.append(["LinkedList", mode_name, "поиск", "среднее", sum(finding_times) / repeats])
rows.append(["LinkedList", mode_name, "удаление", "среднее", sum(deletion_times) / repeats])
return rows
def save_results(rows, filename="results.csv"):
with codecs.open(filename, "a+", "utf-16") as file:
writer = csv.writer(file)
writer.writerows(rows)
def run_shuffled(records_shuffled):
rows = measure_linked_list(records_shuffled, "случайный")
save_results(rows)
return rows
def run_sorted(records_sorted):
rows = measure_linked_list(records_sorted, "отсортированный")
save_results(rows)
return rows
# ---------- Manual tests ----------
def test():
head = None
head = ll_insert(head, "Ivan", "111")
head = ll_insert(head, "Anna", "222")
head = ll_insert(head, "Petr", "333")
print(ll_find(head, "Anna")) # 222
print(ll_find(head, "Unknown")) # None
head = ll_insert(head, "Anna", "999")
print(ll_find(head, "Anna")) # 999
head = ll_delete(head, "Ivan")
print(ll_list_all(head)) # [('Anna', '999'), ('Petr', '333')]
if __name__ == "__main__":
records_shuffled, records_sorted = records()
run_shuffled(records_shuffled)
run_sorted(records_sorted)

View File

@ -0,0 +1,34 @@
import random
VOWELS = "aeiou"
CONSONANTS = "bcdfghjklmnpqrstvwxyz"
def generate_name():
length = random.randint(4, 10)
name = ""
for i in range(length):
if i % 2 == 0:
name += random.choice(CONSONANTS)
else:
name += random.choice(VOWELS)
return name.capitalize()
def generate_unique_names(count):
names = set()
while len(names) < count:
names.add(generate_name())
return list(names)
if __name__ == "__main__":
names = generate_unique_names(5000)
with open("names.txt", "w", encoding="utf-8") as file:
for name in names:
file.write(name + "\n")
print("names.txt generated")

View File

@ -0,0 +1,73 @@
import random as rd
def Shell(arr):
N = len(arr)
n = N // 2
while n>0:
for i in range (0,N-n):
j=i
while j+n<N:
if arr[j]>arr[j+n]:
t=arr[j]
arr[j]=arr[j+n]
arr[j+n]=t
j=i
else:
j+=n
n=n//2
return arr
def records():
phones=[]
first=0
second=0
third=0
fourth=0
for i in range(10000):
phones.append(str(first)+str(second)+str(third)+str(fourth))
fourth+=1
if fourth==10:
third+=1
fourth=0
if third==10:
second+=1
third=0
if second==10:
first+=1
second=0
phones2=phones.copy()
f=open("names.txt","r")
count=0
names=[]
while count<5000:
name=f.readline()
names.append(name[:len(name)-1])
names.append(name[:len(name)-1])
count+=1
f.close()
names_sorted=names.copy()
for i in range(10000):
names_sorted[i]=names_sorted[i].encode()
Shell(names_sorted)
for i in range(10000):
names_sorted[i]=names_sorted[i].decode()
records_shuffled=[]
records_sorted=[]
count=0
while count<10000:
name_var=rd.randint(0,len(names)-1)
phone_var=rd.randint(0,len(phones2)-1)
records_shuffled.append((names[name_var],phones[count]))
records_sorted.append((names_sorted[count],phones2[phone_var]))
names.remove(names[name_var])
phones2.remove(phones2[phone_var])
count+=1
rd.shuffle(records_shuffled)
return records_shuffled, records_sorted
#print(records_shuffled)
#print(records_sorted)

Binary file not shown.

5000
SorokinAD/[1]lab_1/names.txt Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.
Can't render this file because it has a wrong number of fields in line 37.

View File

@ -0,0 +1,143 @@
import csv
import os
from builders import TextFileMazeBuilder
from solver import MazeSolver
from strategies import (
BFSStrategy,
DFSStrategy,
AStarStrategy
)
# =========================================================
# Benchmark
# =========================================================
class BenchmarkRunner:
def __init__(self):
self.strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("A*", AStarStrategy()),
]
# =====================================================
# Run benchmark
# =====================================================
def run(
self,
maze_files: list[str],
runs_per_test: int = 5
):
results = []
builder = TextFileMazeBuilder()
for maze_file in maze_files:
print()
print(f"Testing: {maze_file}")
maze = builder.build_from_file(
maze_file
)
for strategy_name, strategy in self.strategies:
total_time = 0
total_visited = 0
total_path_length = 0
for _ in range(runs_per_test):
solver = MazeSolver(
maze,
strategy
)
path, stats = solver.solve()
total_time += stats.time_ms
total_visited += stats.visited_cells
total_path_length += stats.path_length
avg_time = (
total_time / runs_per_test
)
avg_visited = (
total_visited / runs_per_test
)
avg_path_length = (
total_path_length / runs_per_test
)
result = {
"maze": maze_file,
"strategy": strategy_name,
"time_ms": round(avg_time, 3),
"visited_cells": int(avg_visited),
"path_length": int(avg_path_length),
}
results.append(result)
print(
f"{strategy_name}: "
f"time={avg_time:.3f} ms, "
f"visited={avg_visited:.0f}, "
f"path={avg_path_length:.0f}"
)
self.save_to_csv(results)
# =====================================================
# Save CSV
# =====================================================
@staticmethod
def save_to_csv(results):
base_dir = os.path.dirname(__file__)
csv_path = os.path.join(
base_dir,
"benchmark_results.csv"
)
with open(
csv_path,
"w",
newline="",
encoding="utf-8"
) as file:
writer = csv.DictWriter(
file,
fieldnames=[
"maze",
"strategy",
"time_ms",
"visited_cells",
"path_length"
]
)
writer.writeheader()
for row in results:
writer.writerow(row)
print()
print(
f"Results saved to: {csv_path}"
)

View File

@ -0,0 +1,13 @@
maze,strategy,time_ms,visited_cells,path_length
mazes/small.txt,BFS,0.034,17,12
mazes/small.txt,DFS,0.026,13,12
mazes/small.txt,A*,0.048,17,12
mazes/open.txt,BFS,0.219,100,19
mazes/open.txt,DFS,0.135,55,55
mazes/open.txt,A*,0.334,100,19
mazes/medium.txt,BFS,0.093,36,0
mazes/medium.txt,DFS,0.059,36,0
mazes/medium.txt,A*,0.087,36,0
mazes/no_exit.txt,BFS,0.008,5,0
mazes/no_exit.txt,DFS,0.007,5,0
mazes/no_exit.txt,A*,0.011,5,0
1 maze strategy time_ms visited_cells path_length
2 mazes/small.txt BFS 0.034 17 12
3 mazes/small.txt DFS 0.026 13 12
4 mazes/small.txt A* 0.048 17 12
5 mazes/open.txt BFS 0.219 100 19
6 mazes/open.txt DFS 0.135 55 55
7 mazes/open.txt A* 0.334 100 19
8 mazes/medium.txt BFS 0.093 36 0
9 mazes/medium.txt DFS 0.059 36 0
10 mazes/medium.txt A* 0.087 36 0
11 mazes/no_exit.txt BFS 0.008 5 0
12 mazes/no_exit.txt DFS 0.007 5 0
13 mazes/no_exit.txt A* 0.011 5 0

View File

@ -0,0 +1,60 @@
from abc import ABC, abstractmethod
from cell import Cell
from maze import Maze
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, "r", encoding="utf-8") as file:
lines = [line.rstrip("\n") for line in file]
cells = []
start = None
exit = None
for y, line in enumerate(lines):
row = []
for x, char in enumerate(line):
is_wall = char == "#"
is_start = char == "S"
is_exit = char == "E"
cell = Cell(
x=x,
y=y,
is_wall=is_wall,
is_start=is_start,
is_exit=is_exit
)
if is_start:
start = cell
if is_exit:
exit = cell
row.append(cell)
cells.append(row)
if start is None:
raise ValueError("Старт S не найден")
if exit is None:
raise ValueError("Выход E не найден")
return Maze(cells, start, exit)

View File

@ -0,0 +1,13 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class Cell:
x: int
y: int
is_wall: bool = False
is_start: bool = False
is_exit: bool = False
def is_passable(self) -> bool:
return not self.is_wall

View File

@ -0,0 +1,91 @@
from abc import ABC, abstractmethod
from cell import Cell
from maze import Maze
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
# =========================================================
# Command
# =========================================================
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# =========================================================
# MoveCommand
# =========================================================
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.upper()
self.previous_cell = None
def execute(self):
if self.direction not in self.DIRECTIONS:
return False
dx, dy = self.DIRECTIONS[self.direction]
current = self.player.current_cell
new_x = current.x + dx
new_y = current.y + dy
target = self.maze.get_cell(
new_x,
new_y
)
if target is None:
return False
if not target.is_passable():
return False
self.previous_cell = current
self.player.current_cell = target
return True
def undo(self):
if self.previous_cell is not None:
self.player.current_cell = (
self.previous_cell
)

164
SorokinAD/[2]lab_2/main.py Normal file
View File

@ -0,0 +1,164 @@
from builders import TextFileMazeBuilder
from strategies import (
BFSStrategy,
DFSStrategy,
AStarStrategy
)
from solver import MazeSolver
from visualization import ConsoleView
from commands import (
Player,
MoveCommand
)
def test_strategy(name, strategy, maze):
print()
print("=" * 40)
view = ConsoleView()
solver = MazeSolver(
maze,
strategy
)
solver.add_observer(view)
path, stats = solver.solve()
print()
print(f"Strategy: {name}")
print(
f"Time: {stats.time_ms:.3f} ms"
)
print(
f"Visited cells: {stats.visited_cells}"
)
print(
f"Path length: {stats.path_length}"
)
print()
view.render(
maze,
path
)
# =========================================================
# Manual mode
# =========================================================
def manual_mode(maze):
print()
print("=" * 40)
print("MANUAL MODE")
print("W/A/S/D - move")
print("U - undo")
print("Q - quit")
view = ConsoleView()
player = Player(
maze.start
)
history = []
while True:
print()
view.render(
maze,
current=player.current_cell
)
if player.current_cell == maze.exit:
print()
print("YOU WIN!")
break
command_input = input(
"\nCommand: "
).upper()
if command_input == "Q":
break
if command_input == "U":
if history:
last_command = history.pop()
last_command.undo()
continue
command = MoveCommand(
player,
maze,
command_input
)
success = command.execute()
if success:
history.append(command)
else:
print("Invalid move")
def main():
builder = TextFileMazeBuilder()
maze = builder.build_from_file(
"mazes/small.txt"
)
# =====================================
# Strategies
# =====================================
test_strategy(
"BFS",
BFSStrategy(),
maze
)
test_strategy(
"DFS",
DFSStrategy(),
maze
)
test_strategy(
"A*",
AStarStrategy(),
maze
)
# =====================================
# Manual mode
# =====================================
manual_mode(maze)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,33 @@
from cell import Cell
class Maze:
def __init__(self, cells: list[list[Cell]], start: Cell, exit: Cell):
self.cells = cells
self.height = len(cells)
self.width = len(cells[0]) if self.height > 0 else 0
self.start = start
self.exit = exit
def get_cell(self, x: int, y: int) -> Cell | None:
if 0 <= y < self.height and 0 <= x < self.width:
return self.cells[y][x]
return None
def get_neighbors(self, cell: Cell) -> list[Cell]:
directions = [
(0, -1),
(0, 1),
(-1, 0),
(1, 0),
]
neighbors = []
for dx, dy in directions:
neighbor = self.get_cell(cell.x + dx, cell.y + dy)
if neighbor is not None and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors

View File

@ -0,0 +1,11 @@
####################
#S # # #
### ### ##### ### ##
# # # ##
# ### ### # ##### ##
# # # # # #
# # ##### ##### # #
# # # # # #
# ##### ##### # # #
# # # E#
####################

View File

@ -0,0 +1,5 @@
##########
#S# #E#
# ### ####
# # #
##########

View File

@ -0,0 +1,10 @@
S
E

View File

@ -0,0 +1,5 @@
##########
#S #E#
# ### ## #
# # #
##########

Binary file not shown.

View File

@ -0,0 +1,20 @@
from benchmark import BenchmarkRunner
def main():
benchmark = BenchmarkRunner()
benchmark.run(
maze_files=[
"mazes/small.txt",
"mazes/open.txt",
"mazes/medium.txt",
"mazes/no_exit.txt",
],
runs_per_test=10
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,86 @@
import time
from dataclasses import dataclass
from maze import Maze
from strategies import PathFindingStrategy
@dataclass
class SearchStats:
time_ms: float
visited_cells: int
path_length: int
class MazeSolver:
def __init__(
self,
maze: Maze,
strategy: PathFindingStrategy
):
self.maze = maze
self.strategy = strategy
self.observers = []
# =====================================
# Observer
# =====================================
def add_observer(self, observer):
self.observers.append(observer)
def notify(self, event: str):
for observer in self.observers:
observer.update(event)
# =====================================
# Strategy
# =====================================
def set_strategy(
self,
strategy: PathFindingStrategy
):
self.strategy = strategy
self.notify(
f"Strategy changed to {strategy.__class__.__name__}"
)
# =====================================
# Solve
# =====================================
def solve(self):
self.notify("Search started")
start_time = time.perf_counter()
path, visited_cells = self.strategy.find_path(
self.maze,
self.maze.start,
self.maze.exit
)
end_time = time.perf_counter()
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path)
)
if path:
self.notify("Path found")
else:
self.notify("No path found")
return path, stats

View File

@ -0,0 +1,218 @@
from abc import ABC, abstractmethod
from collections import deque
from heapq import heappush, heappop
from maze import Maze
from cell import Cell
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(
self,
maze: Maze,
start: Cell,
exit: Cell
) -> tuple[list[Cell], int]:
pass
# =========================================================
# BFS
# =========================================================
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
queue = deque([start])
visited = {start}
parent = {}
visited_count = 0
while queue:
current = queue.popleft()
visited_count += 1
if current == exit:
path = self._restore_path(parent, start, exit)
return path, visited_count
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return [], visited_count
@staticmethod
def _restore_path(parent, start, exit):
path = []
current = exit
while current != start:
path.append(current)
current = parent[current]
path.append(start)
path.reverse()
return path
# =========================================================
# DFS
# =========================================================
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
stack = [start]
visited = {start}
parent = {}
visited_count = 0
while stack:
current = stack.pop()
visited_count += 1
if current == exit:
path = self._restore_path(parent, start, exit)
return path, visited_count
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
return [], visited_count
@staticmethod
def _restore_path(parent, start, exit):
path = []
current = exit
while current != start:
path.append(current)
current = parent[current]
path.append(start)
path.reverse()
return path
# =========================================================
# A*
# =========================================================
class AStarStrategy(PathFindingStrategy):
def heuristic(self, cell: Cell, exit: Cell):
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def find_path(self, maze, start, exit):
open_set = []
heappush(open_set, (0, start.x, start.y, start))
g_score = {
start: 0
}
parent = {}
visited = set()
visited_count = 0
while open_set:
_, _, _, current = heappop(open_set)
if current in visited:
continue
visited.add(current)
visited_count += 1
if current == exit:
path = self._restore_path(parent, start, exit)
return path, visited_count
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if (
neighbor not in g_score
or tentative_g < g_score[neighbor]
):
g_score[neighbor] = tentative_g
f_score = tentative_g + self.heuristic(
neighbor,
exit
)
parent[neighbor] = current
heappush(
open_set,
(
f_score,
neighbor.x,
neighbor.y,
neighbor
)
)
return [], visited_count
@staticmethod
def _restore_path(parent, start, exit):
path = []
current = exit
while current != start:
path.append(current)
current = parent[current]
path.append(start)
path.reverse()
return path

View File

@ -0,0 +1,56 @@
from abc import ABC, abstractmethod
from maze import Maze
from cell import Cell
class Observer(ABC):
@abstractmethod
def update(self, event: str):
pass
class ConsoleView(Observer):
def update(self, event: str):
print(f"[EVENT]: {event}")
def render(
self,
maze: Maze,
path: list[Cell] = None,
current: Cell = None
):
path = path or []
path_set = set(path)
for row in maze.cells:
line = ""
for cell in row:
if current and cell == current:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif cell in path_set:
line += "."
else:
line += " "
print(line)

View File

@ -1 +0,0 @@
428b

View File

View File

View File

View File

View File

@ -1 +0,0 @@
428

View File

View File

View File

View File

View File

0
code
View File

View File

View File

View File

View File

@ -1 +0,0 @@
427

View File

View File

View File

@ -1 +0,0 @@
856

View File

View File

View File

Binary file not shown.

View File

@ -1 +0,0 @@
429

View File

View File

View File

@ -1 +0,0 @@
428b.md

View File

Some files were not shown because too many files have changed in this diff Show More