"""class HashTable
"""


class HashTable:
    """
    A hash table for (key, value) 2-tuples

    === Attributes ===
    @param int capacity: total slots available
    @param list[list[tuple]] table: contents of table
    @param int collisions: number of collisions
    @param int items: number of items
    """

    def __init__(self, capacity):
        """
        Create a hash table with capacity slots

        @param HashTable self: this hash table
        @param int capacity: number of slots in this table
        @rtype: None
        """
        self.capacity, self.collisions, self.items = capacity, 0, 0
        self.table = [[] for _ in range(self.capacity)]

    def __contains__(self, key):
        """ Return whether HashTable self contains key"

        @param HashTable self: this hash table
        @param object key: key to search for
        @rtype: bool
        """
        return key in [i[0] for i in self.table[hash(key) % self.capacity]]

    def double(self):
        """
        Double the capacity of this hash table, and re-hash all items.

        @param HashTable self: this hash table
        @rtype: None
        """
        # stats before doubling
        print("Stats before doubling: {}".format(self.stats()))
        # temporarily save self.table
        tmp_table = self.table
        # reset items
        self.items = 0
        self.capacity *= 2
        # create double-sized table
        self.table = [[] for _ in range(self.capacity)]
        # insert old items into new table
        for bucket in tmp_table:
            for item in bucket:
                self.insert(item)
        # stats after doubling
        print("Stats after doubling: {}".format(self.stats()))

    def insert(self, item):
        """
        Insert (key, value) item into HashTable self.

        @param HashTable self: this HashTable
        @param (object, object) item: key/value pair, key is hashable
        @rtype: None
        """
        # find the appropriate bucket
        bucket = self.table[hash(item[0]) % self.capacity]
        # insert item if it's not already there
        for i in range(len(bucket)):
            if bucket[i][0] == item[0]:
                bucket[i] = item
        if item not in bucket:
            bucket.append(item)
            self.items += 1
            # update items and collisions
            if len(bucket) > 1:
                self.collisions += 1
        # if the capacity is high, double it
        if (self.items / self.capacity) > 0.7:
            self.double()

    def retrieve(self, key):
        """
        Return value corresponding to key, or else raise Exception.

        @param HashTable key: this hash table
        @param object key: hashable key
        @rtype: object
        """
        # get the right bucket
        bucket = self.table[hash(key) % self.capacity]
        # get item from bucket
        for item in bucket:
            if item[0] == key:
                return item[1]
        # raise an error if key not present
        raise KeyError(key)

    def stats(self):
        """
        Provide statistics.

        @param HashTable self: this hash table
        @rtype: str
        """
        buckets = sum([1 for b in self.table if len(b) > 0])
        max_bucket_length = max([len(b) for b in self.table])
        average = "Average bucket length: {}.\n".format(self.items / buckets)
        ideal = "Density: {}\n".format(self.items / self.capacity)
        collisions = "Collisions: {}\n".format(self.collisions)
        maximum = "Maximum bucket length: {}".format(max_bucket_length)
        return average + ideal + collisions + maximum


if __name__ == '__main__':
    import random
    word_list = open('words').readlines()
    extra_word_list = word_list[:]
    random.shuffle(word_list)
    ht = HashTable(2)
    for j in range(99171):
        ht.insert((word_list[j], hash(word_list[j])))
    print(ht.stats())
    from time import time
    for size in [9, 90, 900, 9000, 90000]:
        ht = HashTable(2)
        list_ = extra_word_list[:size]
        for word in extra_word_list[:size]:
            ht.insert((word, hash(word)))
        start = time()
        for i in range(20):
            x = word_list[i] in ht
        print("ht of size {} searches 20 words in {} seconds.".format(size, time() - start))
        start = time()
        for i in range(20):
            x = word_list[i] in list_
        print("list of size {} searches 20 words in {} seconds.".format(size, time() - start))
    print(ht.retrieve("center\n"))
    print(ht.retrieve("centre\n"))
