LeetCode-Problems / 0706. Design HashMap / 0706. Design HashMap.py
0706. Design HashMap.py
Raw
"""04-22-2022 Leetcode 706. Design HashMap"""

# A SUPER basic, quick and dirty way to do it is simply init a list of 10**6 + 1 ints as all -1 value.
# The key would then just be the index of the array. Fast access, fast insertion and deletion.
# Notably this is not really a hash, or is maybe "the trivial hash"
# Absurdly wasteful for a sparse list. I'd like to explore a more robust solution, but it gets
# complicated really quickly, particularly if we are trying to avoid "fancy features".
# For fast search of an initially empty list we could build and search our own BST Tree for instance
# but maybe we can just use built in heapsort for ease. For now maybe I'll start with in between
# and lean on Pythons magic "is in"

# Doing some reading on just how a hashmap actually works. I understood the concept:
# "apply algorithm to generate address of key" but didnt understand fundamentally
# that this does not generate a unique exact address but rather an adress for a
# relatively small bucket. We then iterate through the bucket (or BST search or whatever)
# to find the exact key. Hashing REDUCES the list we need to search,  it does not eliminate it.
# There is a lot of complexity therefore as to "but how many buckets?", and how full they get
# This ALSO dispells me of the seemingly incorrect notion (oft cited?) that hashing access is
# O(1). This depends on how many collisions there are in the space, thus how many keys in the
# bucket right? Or I guess we know how many keys CAN map to the bucket, so the big O assumes
# worst case: The bucket has a key:val pair for every key that COULD map to said bucket, and
# thus searching them is O(1), the size of the bucket rather than say single operation

# Well, this is a slightly more involved one than I thought. The concepts are easy though
# (assuming we use linear search for the buckets). I'm going to basically straight rip
# the answer as I want some practise using OOP and I like how it defines the buckets as a
# class with their methods, then the hashmap as a seperate class that implements these


class Buckets:
    def __init__(self):
        self.bucket = []  # simple list of (key, value) tuples

    def put(self, key, value) -> None:
        # linear search. Good hashing algorithms use BST or other optomized search
        for i, key_val in enumerate(self.bucket):
            if key_val[0] == key:
                self.bucket[i] = (key, value)
                # Balls. Tuples are immutable. Cannot reassign just their value, need to
                # overwrite the whole tuple at the index location. (or remove and add)
                return
        self.bucket.append((key, value))

    def get(self, key) -> int:
        for key_val in self.bucket:
            if key_val[0] == key:
                return key_val[1]
        return -1

    def remove(self, key) -> None:
        # self.put(key, -1) cheeky, but no... It adds a "blank" (key, val) rather than removing it.
        # A LOT faster as insertions and deletions of a list are costly. THATS why these are really
        # done with linked lists? If doubly linked you could binary search them
        for i, key_val in enumerate(self.bucket):
            if key_val[0] == key:
                del self.bucket[i]


class MyHashMap:
    def __init__(self):
        self.mod_val = 2069  # a prime value, thus 2069 buckets, each potentially with 484 collisions
        self.hmap = [Buckets() for i in range(self.mod_val)]

        # Man, I really need to learn how to OOP in Python. Variable decs go here? Privatize?
        # self.dumb_map = [-1]*((10**6)+1)       #LoL, giant list for 1:1 key IS index. Terrible

    def put(self, key: int, value: int) -> None:
        self.hmap[key % self.mod_val].put(key, value)
        # self.dumb_map[key] = value

    def get(self, key: int) -> int:
        return self.hmap[key % self.mod_val].get(key)
        # return self.dumb_map[key]

    def remove(self, key: int) -> None:
        self.hmap[key % self.mod_val].remove(key)
        # self.dumb_map[key] = -1


# Your MyHashMap object will be instantiated and called as such:
# obj = MyHashMap()
# obj.put(key,value)
# param_2 = obj.get(key)
# obj.remove(key)