Katkat

Implement a size-bounded LRU cache

SnapchatSoftware Engineer · Onsite

Last updated

This problem tests your ability to extend the standard LRU cache pattern to support items with heterogeneous 'weights', requiring a data structure that maintains access order while managing a cumulative capacity constraint.

Problem: Size-Bounded LRU Cache

Design and implement a thread-safe, size-bounded cache that supports storing items with variable sizes. Unlike a standard LRU cache that limits the count of elements, this cache is limited by the total sum of the weights of all stored items.

You must implement the following interface:

class WeightedLRUCache:
    def __init__(self, capacity: int):
        # Initialize the cache with a maximum total weight capacity.
        pass

    def get(self, key: int) -> int:
        # Return the value if the key exists, updating its status to 'most recently used'.
        # Return -1 if the key does not exist.
        pass

    def put(self, key: int, value: int, weight: int) -> None:
        # Add/update the key-value pair with the associated weight.
        # If the total weight exceeds the capacity, evict items from the 'least recently used' 
        # end until the total weight is within the capacity.
        pass

Constraints

  • capacity > 0.
  • weight > 0 for all items.
  • If a single item's weight exceeds the capacity, the cache should not store it (or handle the error appropriately).
  • get and put operations should run in O(1) average time complexity.

Example

cache = WeightedLRUCache(10)
cache.put(1, 100, 4) # Cache: {1: 100}, Current weight: 4
cache.put(2, 200, 7) # Cache: {2: 200}, Evicts 1 (Total weight 7  10)
cache.get(1)        # Returns -1
cache.get(2)        # Returns 200

Edge Cases

  • Updating an existing key with a new weight that forces eviction of other items.
  • A new item weight being larger than the total cache capacity.
  • Repeatedly calling put with the same key.

Hints

Hint 1

Use a doubly linked list to maintain the access order of keys and a hash map to map keys to their corresponding nodes.

Hint 2

Store the 'weight' as a property within each node of the linked list to allow for quick subtraction from the current total weight when an eviction occurs.

Hint 3

In the 'put' method, check if the key already exists; if it does, remove the old node and its weight before adding the new node, then evict the tail of the list while the total current weight exceeds capacity.

Follow-up questions

  • How would you modify this implementation to handle multi-threaded access while minimizing lock contention?
  • If the cache needs to be distributed across multiple physical machines, what strategies would you use to partition the keys?
  • How does the performance of this cache change if the distribution of 'weights' is highly skewed?