[1] task 1
This commit is contained in:
parent
25341dc814
commit
634b76c127
BIN
osipovamd/.DS_Store
vendored
Normal file
BIN
osipovamd/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
osipovamd/docs/.DS_Store
vendored
Normal file
BIN
osipovamd/docs/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
osipovamd/docs/Task1.docx
Normal file
BIN
osipovamd/docs/Task1.docx
Normal file
Binary file not shown.
BIN
osipovamd/docs/data/.DS_Store
vendored
Normal file
BIN
osipovamd/docs/data/.DS_Store
vendored
Normal file
Binary file not shown.
19
osipovamd/docs/results.csv
Normal file
19
osipovamd/docs/results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Структура,Режим,Операция,Замер1,Замер2,Замер3,Среднее
|
||||||
|
LinkedList,случайный,вставка,0.014099,0.014181,0.014180,0.014153
|
||||||
|
LinkedList,случайный,поиск,0.001849,0.001841,0.001897,0.001862
|
||||||
|
LinkedList,случайный,удаление,0.000859,0.000863,0.000825,0.000849
|
||||||
|
LinkedList,отсортированный,вставка,0.012913,0.012512,0.012566,0.012663
|
||||||
|
LinkedList,отсортированный,поиск,0.001616,0.001457,0.001696,0.001590
|
||||||
|
LinkedList,отсортированный,удаление,0.000992,0.000931,0.000940,0.000954
|
||||||
|
HashTable,случайный,вставка,0.001368,0.001415,0.001385,0.001389
|
||||||
|
HashTable,случайный,поиск,0.000162,0.000153,0.000146,0.000153
|
||||||
|
HashTable,случайный,удаление,0.000076,0.000075,0.000075,0.000075
|
||||||
|
HashTable,отсортированный,вставка,0.001275,0.001258,0.001376,0.001303
|
||||||
|
HashTable,отсортированный,поиск,0.000169,0.000139,0.000140,0.000149
|
||||||
|
HashTable,отсортированный,удаление,0.000080,0.000080,0.000079,0.000080
|
||||||
|
BST,случайный,вставка,0.000514,0.000482,0.000507,0.000501
|
||||||
|
BST,случайный,поиск,0.000081,0.000056,0.000055,0.000064
|
||||||
|
BST,случайный,удаление,0.000042,0.000038,0.000039,0.000040
|
||||||
|
BST,отсортированный,вставка,0.018947,0.018836,0.018665,0.018816
|
||||||
|
BST,отсортированный,поиск,0.002172,0.002200,0.001973,0.002115
|
||||||
|
BST,отсортированный,удаление,0.001400,0.001407,0.001390,0.001399
|
||||||
|
493
osipovamd/docs/task1.py
Normal file
493
osipovamd/docs/task1.py
Normal file
|
|
@ -0,0 +1,493 @@
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import csv
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
# Увеличиваем лимит рекурсии для BST
|
||||||
|
sys.setrecursionlimit(10000)
|
||||||
|
|
||||||
|
|
||||||
|
def ll_insert(head, name, phone):
|
||||||
|
current = head
|
||||||
|
while current:
|
||||||
|
if current['name'] == name:
|
||||||
|
current['phone'] = phone
|
||||||
|
return head
|
||||||
|
current = current['next']
|
||||||
|
new_node = {'name': name, 'phone': phone, 'next': head}
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
def ll_find(head, name):
|
||||||
|
current = head
|
||||||
|
while current:
|
||||||
|
if current['name'] == name:
|
||||||
|
return current['phone']
|
||||||
|
current = current['next']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def ll_delete(head, name):
|
||||||
|
if not head:
|
||||||
|
return None
|
||||||
|
if head['name'] == name:
|
||||||
|
return head['next']
|
||||||
|
current = head
|
||||||
|
while current['next']:
|
||||||
|
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:
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
records.sort(key=lambda x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
def hash_function(name, size):
|
||||||
|
return sum(ord(c) for c in name) % size
|
||||||
|
|
||||||
|
def ht_create(size=1000):
|
||||||
|
return [None] * size
|
||||||
|
|
||||||
|
def ht_insert(buckets, name, phone):
|
||||||
|
index = hash_function(name, len(buckets))
|
||||||
|
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||||
|
|
||||||
|
def ht_find(buckets, name):
|
||||||
|
index = hash_function(name, len(buckets))
|
||||||
|
return ll_find(buckets[index], name)
|
||||||
|
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
index = hash_function(name, len(buckets))
|
||||||
|
buckets[index] = ll_delete(buckets[index], name)
|
||||||
|
|
||||||
|
def ht_list_all(buckets):
|
||||||
|
records = []
|
||||||
|
for bucket in buckets:
|
||||||
|
current = bucket
|
||||||
|
while current:
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['next']
|
||||||
|
records.sort(key=lambda x: x[0])
|
||||||
|
return records
|
||||||
|
|
||||||
|
def bst_insert(root, name, phone):
|
||||||
|
"""Итеративная вставка для избежания RecursionError"""
|
||||||
|
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
return new_node
|
||||||
|
|
||||||
|
current = root
|
||||||
|
while True:
|
||||||
|
if name < current['name']:
|
||||||
|
if current['left'] is None:
|
||||||
|
current['left'] = new_node
|
||||||
|
break
|
||||||
|
current = current['left']
|
||||||
|
elif name > current['name']:
|
||||||
|
if current['right'] is None:
|
||||||
|
current['right'] = new_node
|
||||||
|
break
|
||||||
|
current = current['right']
|
||||||
|
else:
|
||||||
|
current['phone'] = phone
|
||||||
|
break
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
def bst_find(root, name):
|
||||||
|
current = root
|
||||||
|
while current:
|
||||||
|
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 and current['left']:
|
||||||
|
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 = []
|
||||||
|
stack = []
|
||||||
|
current = root
|
||||||
|
|
||||||
|
while stack or current:
|
||||||
|
while current:
|
||||||
|
stack.append(current)
|
||||||
|
current = current['left']
|
||||||
|
current = stack.pop()
|
||||||
|
records.append((current['name'], current['phone']))
|
||||||
|
current = current['right']
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
def copy_linked_list(head):
|
||||||
|
if not head:
|
||||||
|
return None
|
||||||
|
new_head = {'name': head['name'], 'phone': head['phone'], 'next': None}
|
||||||
|
current_new = new_head
|
||||||
|
current_old = head['next']
|
||||||
|
while current_old:
|
||||||
|
current_new['next'] = {'name': current_old['name'], 'phone': current_old['phone'], 'next': None}
|
||||||
|
current_new = current_new['next']
|
||||||
|
current_old = current_old['next']
|
||||||
|
return new_head
|
||||||
|
|
||||||
|
def copy_bst(node):
|
||||||
|
if not node:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
'name': node['name'],
|
||||||
|
'phone': node['phone'],
|
||||||
|
'left': copy_bst(node['left']),
|
||||||
|
'right': copy_bst(node['right'])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_test_data(N=10000):
|
||||||
|
|
||||||
|
names = [f"User_{i:05d}" for i in range(N)]
|
||||||
|
records = [(name, f"+7-999-{random.randint(1000000, 9999999)}") for name in names]
|
||||||
|
|
||||||
|
records_shuffled = records.copy()
|
||||||
|
random.shuffle(records_shuffled)
|
||||||
|
|
||||||
|
records_sorted = sorted(records, key=lambda x: x[0])
|
||||||
|
|
||||||
|
return records_shuffled, records_sorted
|
||||||
|
|
||||||
|
def get_test_queries(records, num_existing=100, num_nonexisting=10):
|
||||||
|
existing_names = [name for name, _ in random.sample(records, min(num_existing, len(records)))]
|
||||||
|
nonexisting_names = [f"None_{i:05d}" for i in range(num_nonexisting)]
|
||||||
|
|
||||||
|
queries = existing_names + nonexisting_names
|
||||||
|
random.shuffle(queries)
|
||||||
|
|
||||||
|
return queries
|
||||||
|
|
||||||
|
def get_delete_names(records, num_to_delete=50):
|
||||||
|
return [name for name, _ in random.sample(records, min(num_to_delete, len(records)))]
|
||||||
|
|
||||||
|
|
||||||
|
def measure_insertion(structure_type, records, repeats=3):
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
if structure_type == "LinkedList":
|
||||||
|
structure = None
|
||||||
|
insert_func = ll_insert
|
||||||
|
elif structure_type == "HashTable":
|
||||||
|
structure = ht_create(2000)
|
||||||
|
insert_func = ht_insert
|
||||||
|
elif structure_type == "BST":
|
||||||
|
structure = None
|
||||||
|
insert_func = bst_insert
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown structure: {structure_type}")
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name, phone in records:
|
||||||
|
if structure_type == "HashTable":
|
||||||
|
insert_func(structure, name, phone)
|
||||||
|
else:
|
||||||
|
structure = insert_func(structure, name, phone)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def measure_search(structure_type, structure, queries, repeats=3):
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name in queries:
|
||||||
|
if structure_type == "LinkedList":
|
||||||
|
ll_find(structure, name)
|
||||||
|
elif structure_type == "HashTable":
|
||||||
|
ht_find(structure, name)
|
||||||
|
elif structure_type == "BST":
|
||||||
|
bst_find(structure, name)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def measure_deletion(structure_type, structure, names_to_delete, repeats=3):
|
||||||
|
times = []
|
||||||
|
|
||||||
|
for _ in range(repeats):
|
||||||
|
if structure_type == "LinkedList":
|
||||||
|
temp_structure = copy_linked_list(structure)
|
||||||
|
delete_func = ll_delete
|
||||||
|
|
||||||
|
elif structure_type == "HashTable":
|
||||||
|
temp_structure = structure.copy()
|
||||||
|
for i in range(len(temp_structure)):
|
||||||
|
if temp_structure[i]:
|
||||||
|
temp_structure[i] = copy_linked_list(temp_structure[i])
|
||||||
|
delete_func = ht_delete
|
||||||
|
|
||||||
|
elif structure_type == "BST":
|
||||||
|
temp_structure = copy_bst(structure)
|
||||||
|
delete_func = bst_delete
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
for name in names_to_delete:
|
||||||
|
if structure_type == "HashTable":
|
||||||
|
delete_func(temp_structure, name)
|
||||||
|
else:
|
||||||
|
temp_structure = delete_func(temp_structure, name)
|
||||||
|
|
||||||
|
end = time.perf_counter()
|
||||||
|
times.append(end - start)
|
||||||
|
|
||||||
|
return times
|
||||||
|
|
||||||
|
def run_experiment(N=2000):
|
||||||
|
"""Запускает все эксперименты и возвращает результаты"""
|
||||||
|
|
||||||
|
print(f"Генерация тестовых данных (N={N})...")
|
||||||
|
records_shuffled, records_sorted = generate_test_data(N)
|
||||||
|
|
||||||
|
queries = get_test_queries(records_shuffled, num_existing=100, num_nonexisting=10)
|
||||||
|
delete_names = get_delete_names(records_shuffled, num_to_delete=50)
|
||||||
|
|
||||||
|
structures = ["LinkedList", "HashTable", "BST"]
|
||||||
|
modes = ["случайный", "отсортированный"]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
print("\nНачало экспериментов:")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
for structure in structures:
|
||||||
|
print(f"\nТестирование {structure}...")
|
||||||
|
|
||||||
|
for mode in modes:
|
||||||
|
print(f" Режим: {mode}")
|
||||||
|
records = records_shuffled if mode == "случайный" else records_sorted
|
||||||
|
|
||||||
|
# Вставка
|
||||||
|
print(f" Измерение вставки...")
|
||||||
|
try:
|
||||||
|
insert_times = measure_insertion(structure, records, repeats=3)
|
||||||
|
avg_insert = sum(insert_times) / len(insert_times)
|
||||||
|
except RecursionError:
|
||||||
|
print(f" ОШИБКА: Превышена глубина рекурсии при вставке в {structure} для {mode} режима")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f" Создание финальной структуры...")
|
||||||
|
if structure == "LinkedList":
|
||||||
|
final_structure = None
|
||||||
|
for name, phone in records:
|
||||||
|
final_structure = ll_insert(final_structure, name, phone)
|
||||||
|
elif structure == "HashTable":
|
||||||
|
final_structure = ht_create(2000)
|
||||||
|
for name, phone in records:
|
||||||
|
ht_insert(final_structure, name, phone)
|
||||||
|
elif structure == "BST":
|
||||||
|
final_structure = None
|
||||||
|
for name, phone in records:
|
||||||
|
final_structure = bst_insert(final_structure, name, phone)
|
||||||
|
|
||||||
|
print(f" Измерение поиска...")
|
||||||
|
search_times = measure_search(structure, final_structure, queries, repeats=3)
|
||||||
|
avg_search = sum(search_times) / len(search_times)
|
||||||
|
|
||||||
|
print(f" Измерение удаления...")
|
||||||
|
deletion_times = measure_deletion(structure, final_structure, delete_names, repeats=3)
|
||||||
|
avg_deletion = sum(deletion_times) / len(deletion_times)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"Структура": structure,
|
||||||
|
"Режим": mode,
|
||||||
|
"Операция": "вставка",
|
||||||
|
"Замеры": insert_times,
|
||||||
|
"Среднее": avg_insert
|
||||||
|
})
|
||||||
|
results.append({
|
||||||
|
"Структура": structure,
|
||||||
|
"Режим": mode,
|
||||||
|
"Операция": "поиск",
|
||||||
|
"Замеры": search_times,
|
||||||
|
"Среднее": avg_search
|
||||||
|
})
|
||||||
|
results.append({
|
||||||
|
"Структура": structure,
|
||||||
|
"Режим": mode,
|
||||||
|
"Операция": "удаление",
|
||||||
|
"Замеры": deletion_times,
|
||||||
|
"Среднее": avg_deletion
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f" Вставка: {avg_insert:.6f} сек")
|
||||||
|
print(f" Поиск: {avg_search:.6f} сек")
|
||||||
|
print(f" Удаление: {avg_deletion:.6f} сек")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# СОХРАНЕНИЕ РЕЗУЛЬТАТОВ В CSV
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def save_to_csv(results, filename="results.csv"):
|
||||||
|
save_dir = "/Users/mariiaos/2026-rff_mp/osipovamd/docs"
|
||||||
|
filepath = os.path.join(save_dir, filename)
|
||||||
|
|
||||||
|
with open(filepath, "w", newline="", encoding="utf-8") as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerow(["Структура", "Режим", "Операция", "Замер1", "Замер2", "Замер3", "Среднее"])
|
||||||
|
|
||||||
|
for res in results:
|
||||||
|
row = [
|
||||||
|
res["Структура"],
|
||||||
|
res["Режим"],
|
||||||
|
res["Операция"],
|
||||||
|
*[f"{t:.6f}" for t in res["Замеры"]],
|
||||||
|
f"{res['Среднее']:.6f}"
|
||||||
|
]
|
||||||
|
writer.writerow(row)
|
||||||
|
|
||||||
|
print(f"\nРезультаты сохранены в: {filepath}")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
def plot_results(results):
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
print("Нет данных для построения графиков!")
|
||||||
|
return
|
||||||
|
|
||||||
|
plt.style.use('seaborn-v0_8-darkgrid')
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
|
|
||||||
|
operations = ["вставка", "поиск", "удаление"]
|
||||||
|
structures = ["LinkedList", "HashTable", "BST"]
|
||||||
|
modes = ["случайный", "отсортированный"]
|
||||||
|
|
||||||
|
colors = {'LinkedList': '#FF6B6B', 'HashTable': '#4ECDC4', 'BST': '#45B7D1'}
|
||||||
|
|
||||||
|
for idx, operation in enumerate(operations):
|
||||||
|
ax = axes[idx]
|
||||||
|
|
||||||
|
x = np.arange(len(modes))
|
||||||
|
width = 0.25
|
||||||
|
multiplier = 0
|
||||||
|
|
||||||
|
for structure in structures:
|
||||||
|
values = []
|
||||||
|
for mode in modes:
|
||||||
|
found = False
|
||||||
|
for res in results:
|
||||||
|
if (res["Структура"] == structure and
|
||||||
|
res["Режим"] == mode and
|
||||||
|
res["Операция"] == operation):
|
||||||
|
values.append(res["Среднее"])
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
values.append(0)
|
||||||
|
|
||||||
|
if max(values) > 0:
|
||||||
|
offset = width * multiplier
|
||||||
|
bars = ax.bar(x + offset, values, width, label=structure, color=colors[structure])
|
||||||
|
multiplier += 1
|
||||||
|
|
||||||
|
ax.set_xlabel('Режим данных', fontsize=12)
|
||||||
|
ax.set_ylabel('Время (секунды)', fontsize=12)
|
||||||
|
ax.set_title(f'{operation.capitalize()}', fontsize=14, fontweight='bold')
|
||||||
|
ax.set_xticks(x + width)
|
||||||
|
ax.set_xticklabels(modes)
|
||||||
|
ax.legend(loc='upper left')
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
plt.suptitle('Сравнение производительности структур данных',
|
||||||
|
fontsize=16, fontweight='bold')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig('performance_comparison.png', dpi=300, bbox_inches='tight')
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
print("\nГрафик сохранён как 'performance_comparison.png'")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("ТЕСТИРОВАНИЕ ПРОИЗВОДИТЕЛЬНОСТИ СТРУКТУР ДАННЫХ")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
results = run_experiment(N=1000)
|
||||||
|
save_to_csv(results)
|
||||||
|
|
||||||
|
if results:
|
||||||
|
print("\nПостроение графиков...")
|
||||||
|
plot_results(results)
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ (среднее время в секундах)")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"{'Структура':<12} {'Режим':<12} {'Вставка':<10} {'Поиск':<10} {'Удаление':<10}")
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
for structure in ["LinkedList", "HashTable", "BST"]:
|
||||||
|
for mode in ["случайный", "отсортированный"]:
|
||||||
|
insert_time = search_time = delete_time = 0
|
||||||
|
for res in results:
|
||||||
|
if res["Структура"] == structure and res["Режим"] == mode:
|
||||||
|
if res["Операция"] == "вставка":
|
||||||
|
insert_time = res["Среднее"]
|
||||||
|
elif res["Операция"] == "поиск":
|
||||||
|
search_time = res["Среднее"]
|
||||||
|
elif res["Операция"] == "удаление":
|
||||||
|
delete_time = res["Среднее"]
|
||||||
|
|
||||||
|
if insert_time > 0 or search_time > 0 or delete_time > 0:
|
||||||
|
print(f"{structure:<12} {mode:<12} {insert_time:<10.6f} {search_time:<10.6f} {delete_time:<10.6f}")
|
||||||
|
else:
|
||||||
|
print("\nЭксперимент не дал результатов из-за ошибок.")
|
||||||
|
|
||||||
|
print("\nЭксперимент завершён!")
|
||||||
BIN
osipovamd/docs/~$Task1.docx
Normal file
BIN
osipovamd/docs/~$Task1.docx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user