algorithms

A catalog of data structures and problems with approaches to solve them

View the Project on GitHub gitgik/algorithms

Problem

Implement a class to define an LRU(Least Recently Used) Cache. The class should allow for insertion of key-value pairs, retrieve a value using a key, and getting the most recent key. When a key/value is inserted, the key should become the most recently used key.

class LRUCache:
    def __init__(self, max_size):
        self.cache = {}
        self.current_size = 0
        self.max_size = max_size or 1
        self.doubly_linked_list = DoublyLinkedList()
    
    def evict_least_recent(self):
        key_to_remove = self.doubly_linked_list.tail.key
        # remove tail from doubly linked list
        self.doubly_linked_list.remove_tail()
        # remove key from hash table pointing to the former tail we just removed
        del self.cache[key_to_remove]
        
    def get(self, key):
        """O(1) time | O(1) space."""
        
        node = self.cache.get(key, None)
        if not node:
            return None
        self.update_most_recent(node)
        return node.value
    
    def get_most_recent_key(self):
        """O(1) time | O(1) space."""
        return self.doubly_linked_list.head.key
    
    def insert(self, key, value):
        """O(1) time | O(1) space."""
        
        if key not in self.cache:
            # If no capacity, evict tail
            if self.current_size == self.max_size:
                self.evict_least_recent()
            else:
                self.current_size += 1
            # Put new key in cache to point to new node
            self.cache[key] = DoublyLinkedListNode(key, value)
        else:
            # Update already existing node
            self.update_key(key, value)
        # Update the doubly linked list
        self.update_most_recent(self.cache[key])

    def update_key(self, key, value):
        # update existing key with new value
        if key not in self.cache:
            raise Exception("The provided key is not in the cache!")
        self.cache[key].value = value
    
    def update_most_recent(self, node):
        self.doubly_linked_list.set_head(node)


class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
    
    def set_head(self, node):
        if self.head == node:
            return
        elif self.head is None:
            self.head = node
            self.tail = node
        elif self.head == self.tail:
            self.head.previous = node
            node.next = self.head
            self.head = node
        else:
            if self.tail == node:
                self.remove_tail()
            node.remove_bindings()
            self.head.previous = node
            node.next = self.head
            self.head = node
            
    def remove_tail(self):
        if self.tail is None:
            return
        if self.tail == self.head:
            self.head = None
            self.tail = None
            return
        
        # Update new tail to be previous node, & set tail to point to Null value.
        self.tail = self.tail.previous
        self.tail.next = None
        
    
class DoublyLinkListNode:
    def __init__(self, key, value):
        self.key = key
        self.value = value
        self.next = None
        self.previous = None

    def remove_bindings(self):
        if self.previous is not None:
            self.previous.next = self.next
        if self.next is not None:
            self.next.previous = self.previous
        self.previous = None
        self.next = None