#LinkedListPhoneBook

head = None #!!!!!!!!!!!!!!

print("\nLinked list\n")

def ll_insert(head, name, phone):
        
    curr = head
        
    while curr is not None:
        if curr['name'] == name:
            curr['phone'] = phone
            return head
        elif curr['next'] == None:
            curr['next'] = {'name' : name, 'phone' : phone, 'next' : None}
            return head
        curr = curr['next']
    
    return {'name' : name, 'phone' : phone, 'next' : None}


test_names = ['Andrey', 'Ivan', 'Andrey', 'Igor', 'Nagibator3000', 'Sberbank','Loshped']
test_phones = ['7-234-246','6-352-095','5-257-098','1-374-098','9-387-098','5-135-357','0-000-000']

print("\nll_insert test\n")

for i in range(len(test_names)):
    head = ll_insert(head, test_names[i], test_phones[i])
    print(head)

print("\ntest end\n\n")

def ll_find(head, name):

    curr = head
    while curr is not None:
        if curr['name'] == name:
            return curr['phone']
        curr = curr['next']
    
    return None

print('ll_find test\n')

test_names = ["Ivan", "Andrey", "Sberbank", "Nagibator3000","Ermola"]

for name in test_names:
    print(ll_find(head, name))

print('\ntest_end\n\n')

def ll_delete(head, name):
    
    if head is not None:
        
        if head['name'] == name:
            return head['next']
        
        old_curr = head
        curr = head['next']
        
        while curr is not None:
            if curr['name'] == name:
                old_curr['next'] = curr['next']
                break
            old_curr, curr = curr, curr['next']
        
    return head

print('ll_delete test\n')

test_names = ['Nagibator3000','Nagibator3000','Loshped','Ermola']

for name in test_names:
    head = ll_delete(head, name)
    print(head)


print('\ntest_end\n\n')

def ll_list_all(head):
    
    res = []
    curr = head
    
    while curr is not None:
        res += [(curr['name'],curr['phone'])]
        curr = curr['next']
        
    return sorted(res)

print('ll_list_all test\n')

print(ll_list_all(head))

print('\ntest_end\n\n')



#HashTablePhoneBook

print("\nHash Table\n")

size = 8
buckets = [None] * size

def index(name,size):
    return hash(name) % size

def ht_insert(buckets, name, phone):
    ind = index(name, size)
    buckets[ind] = ll_insert(buckets[ind], name, phone)
    return buckets

def ht_find(buckets, name):
    ind = index(name, size)
    return ll_find(buckets[ind], name)

def ht_delete(buckets, name):
    ind = index(name, size)
    buckets[ind] = ll_delete(buckets[ind], name)
    return buckets

def ht_list_all(buckets):
    res = []
    for head in buckets:        
        curr = head       
        while curr is not None:
            res += [(curr['name'],curr['phone'])]
            curr = curr['next']   
    return sorted(res)

test_names = ['Andrey', 'Ivan', 'Andrey', 'Igor', 'Nagibator3000', 'Sberbank','Loshped']
test_phones = ['7-234-246','6-352-095','5-257-098','1-374-098','9-387-098','5-135-357','0-000-000']

print("\nht_insert test\n")

print(buckets)
for i in range(len(test_names)):
    buckets = ht_insert(buckets, test_names[i], test_phones[i])
    print(buckets)

print("\ntest end\n\n")

print("ht_find test\n")

test_names = ["Ivan", "Andrey", "Sberbank", "Nagibator3000","Ermola"]

for name in test_names:
    print(ht_find(buckets, name))

print("\ntest end\n\n")

print('ht_delete test\n')

test_names = ['Nagibator3000','Nagibator3000','Loshped','Ermola']

for name in test_names:
    buckets = ht_delete(buckets, name)
    print(buckets)

print('\ntest_end\n\n')

print('ht_list_all test\n')

print(ht_list_all(buckets))

print('\ntest_end\n\n')



#BinarySearchTree

root = None

def bst_insert(root, name, phone):
    
    if root == None:
        return {'name': name, 'phone': phone, 'left': None, 'right': None}
    
    elif name == root['name']:
        root['phone'] = phone
        return root
    elif hash(name) < hash(root['name']):
        root['left'] = bst_insert(root['left'], name, phone)
        return root
    else:
        root['right'] = bst_insert(root['right'], name, phone)
        return root


def bst_find(root, name):
    
    if root == None:
        return None
    
    elif name == root['name']:
        return root['phone']
    
    elif hash(name) < hash(root['name']):
        return bst_find(root['left'],name)
    else:
        return bst_find(root['right'],name)
    
    
def bst_delete(root, name):

    if root is None:
        return None
    elif name == root['name']:
        
        if root['left'] is None and root['right'] is None:
            return None
        elif root['left'] is not None and root['right'] is None:
            return root['left']
        elif root['right'] is not None and root['left'] is None:
            return root['right']
        
        curr = root['left']
        oldcurr = root
        while curr['right'] is not None:
            oldcurr,curr = curr,curr['right']
        
        if oldcurr == root:
            root['left'] = curr['left']  
        else:
            oldcurr['right'] = curr['left']
            
        curr['left'],curr['right'] = root['left'],root['right']
        return curr
            
    elif hash(name) < hash(root['name']):
        root['left'] = bst_delete(root['left'],name)
        return root
    else:
        root['right'] = bst_delete(root['right'],name)
        return root


def bst_list_all(root):
    
    if root is None:
        return []
    
    return bst_list_all(root['left']) + [(root['name'],root['phone'])] + bst_list_all(root['right'])


test_names = ['Andrey', 'Ivan', 'Andrey', 'Igor', 'Nagibator3000', 'Sberbank','Loshped']
test_phones = ['7-234-246','6-352-095','5-257-098','1-374-098','9-387-098','5-135-357','0-000-000']

print("\nbst_insert test\n")

print(root)
for i in range(len(test_names)):
    root = bst_insert(root, test_names[i], test_phones[i])
    print(root)

print("\ntest end\n\n")

print("bst_find test\n")

test_names = ["Ivan", "Andrey", "Sberbank", "Nagibator3000","Ermola"]

for name in test_names:
    print(bst_find(root, name))

print("\ntest end\n\n")

print('bst_delete test\n')

test_names = ['Nagibator3000','Nagibator3000','Andrey','Ermola']

for name in test_names:
    root = bst_delete(root, name)
    print(root)

print('\ntest_end\n\n')

print('bst_list_all test\n')

print(bst_list_all(root))

print('\ntest_end\n\n')



###ЭКСПЕРЕМЕНТАЛЬНАЯ ЧАСТЬ

#1 Генерация

import random

records_shuffled = []
records_sorted = []

N = 10000

for i in range(1,N+1):
    number =  str(random.randint(1,9)) + '-' + str(random.randint(100,999)) + '-' + str(random.randint(100,999)) + '-' + str(random.randint(10,99)) + '-' + str(random.randint(10,99))
    records_sorted += [(f"User_{i:05d}", number)]

records_shuffled = records_sorted[:] #срезал чтобы не ссылка была
random.shuffle(records_shuffled)

#2 Инструменты замера времени

import time

#start = time.perf_counter()
# ... операции ...
#end = time.perf_counter()
#elapsed = end - start  # время в секундах

results = []
types = ["shuffled","sorted"]
for dt in range(2):
    data = (records_shuffled, records_sorted)[dt]
    time_res_sum = [0]*9
    for iteration in range(5):
        names = [x for x,_ in data]
        test_names = random.sample(names, 150)
        
        find_names = test_names[0:100]
        find_names += [f"None_{i}" for i in range(10)]
        
        delete_names = test_names[100:150]
        
        #LinkedList
        time_res = []
        
        head = None
        
        
        start = time.perf_counter()
        
        for a in data:
            head = ll_insert(head, a[0], a[1])
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        
        start = time.perf_counter()
        
        for name in find_names:
            ll_find(head, name)
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        
        start = time.perf_counter()
       
        for name in delete_names:
            head = ll_delete(head, name)
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        #HashTable
        size = 15013 #простое число от которого 10000 - это примерно 0.7 (коэффициент заполнения)
        buckets = [None] * size
        
        
        start = time.perf_counter()
        
        for a in data:
            buckets = ht_insert(buckets, a[0], a[1])
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        
        start = time.perf_counter()
        
        for name in find_names:
            ht_find(buckets, name)
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        
        start = time.perf_counter()
       
        for name in delete_names:
            buckets = ht_delete(buckets, name)
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        #BinarySearchTree
        root = None
        
        
        start = time.perf_counter()
        
        for a in data:
            root = bst_insert(root, a[0], a[1])
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        
        start = time.perf_counter()
        
        for name in find_names:
            bst_find(root, name)
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        
        start = time.perf_counter()
       
        for name in delete_names:
            root = bst_delete(root, name)
        
        end = time.perf_counter()
        time_res.append(end - start)
        
        print("Итерация ", iteration+1)
        print("Время Связного Списка: ", time_res[0], time_res[1], time_res[2])
        print("Время Хеш-таблицы: ", time_res[3], time_res[4], time_res[5])
        print("Время Двоичного Дерева Поиска: ", time_res[6], time_res[7], time_res[8])
        
        for i in range(9):
            time_res_sum[i] += time_res[i]
    
    for i in range(9):
        time_res_sum[i] /= 5
    
    results.append(["Linked list", types[dt], "Insert",str(time_res_sum[0])])
    results.append(["Linked list", types[dt], "Find",str(time_res_sum[1])])
    results.append(["Linked list", types[dt], "Delete",str(time_res_sum[2])])
    results.append(["Hash-table", types[dt], "Insert",str(time_res_sum[3])])
    results.append(["Hash-table", types[dt], "Find",str(time_res_sum[4])])
    results.append(["Hash-table", types[dt], "Delete",str(time_res_sum[5])])
    results.append(["BST", types[dt], "Insert",str(time_res_sum[6])])
    results.append(["BST", types[dt], "Find",str(time_res_sum[7])])
    results.append(["BST", types[dt], "Delete",str(time_res_sum[8])])

print(results)

import csv

with open("results.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerows(results)
