Grok - X - x.ai - ML Engineer Phone Interview

545
Apr 01, 2025
Apr 01, 2025

-> Sized LRU Cache

the cache has a total size limit, and each item has an individual size. Unlike the standard LRU cache (where capacity is the number of items), here we evict items based on their cumulative size until the new item fits. This twist requires tracking both recency and total size

class Node:
    def __init__(self, key=0, value=None, size=0):
        self.key = key
        self.value = value
        self.size = size
        self.prev = None
        self.next = None

class SizedLRUCache:
    def __init__(self, max_size: int):
        self.max_size = max_size
        self.current_size = 0
        self.map = {}
        self.head = Node()
        self.tail = Node()
        self.head.next = self.tail
        self.tail.prev = self.head
    
    def _add_node(self, node):
        """Add node after head (MRU)."""
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node):
        """Remove node from list."""
        prev = node.prev
        new = node.next
        prev.next = new
        new.prev = prev
    
    def _move_to_head(self, node):
        """Move node to head (MRU)."""
        self._remove_node(node)
        self._add_node(node)
    
    def _evict_until_fits(self, required_size: int):
        """Evict LRU items until required_size fits."""
        while self.current_size + required_size > self.max_size and self.map:
            lru = self.tail.prev
            self._remove_node(lru)
            self.current_size -= lru.size
            del self.map[lru.key]
    
    def get(self, key: int) -> any:
        if key in self.map:
            node = self.map[key]
            self._move_to_head(node)
            return node.value
        return -1
    
    def put(self, key: int, value: any, size: int) -> None:
        if size > self.max_size:
            return  # Item too large to ever fit
        
        if key in self.map:
            # Update existing item
            node = self.map[key]
            old_size = node.size
            node.value = value
            node.size = size
            self.current_size += size - old_size
            self._move_to_head(node)
            # Evict if size increased too much
            if self.current_size > self.max_size:
                self._evict_until_fits(0)
        else:
            # Add new item
            self._evict_until_fits(size)
            if self.current_size + size <= self.max_size:
                new_node = Node(key, value, size)
                self.map[key] = new_node
                self._add_node(new_node)
                self.current_size += size

# Test
cache = SizedLRUCache(10)
cache.put(1, "a", 4)  # {1:a,4}, total=4
cache.put(2, "b", 4)  # {2:b,4, 1:a,4}, total=8
cache.put(3, "c", 5)  # Evict 1, {2:b,4, 3:c,5}, total=9
print(cache.get(2))   # "b"
cache.put(4, "d", 3)  # Evict 3, {2:b,4, 4:d,3}, total=7
print(cache.get(3))   # -1
print(cache.get(2))   # "b"
Comments (1)