```python """ LRU Cache implementation with O(1) get and put operations. Uses a doubly-linked list for O(1) eviction and a dictionary for O(1) lookup. """ from __future__ import annotations from typing import Any, Optional class Node: """Doubly-linked list node for LRU cache.""" __slots__ = ("key", "value", "prev", "next") def __init__(self, key: Any = None, value: Any = None) -> None: self.key = key self.value = value self.prev: Optional[Node] = None self.next: Optional[Node] = None class LRUCache: """ LRU Cache with O(1) get and put operations. Invariants: - The cache holds at most `capacity` items. - The doubly-linked list maintains items in order of recent use: head.next is the most recently used, tail.prev is the least recently used. - The dictionary maps keys to nodes in the linked list. - On get/put, the accessed/updated node is moved to the front (most recent). - On put when cache is full, the least recently used item (tail.prev) is evicted. """ def __init__(self, capacity: int) -> None: if capacity <= 0: raise ValueError("Capacity must be a positive integer") self.capacity = capacity self.cache: dict[Any, Node] = {} # Sentinel nodes to simplify edge cases self.head = Node() # Most recently used side self.tail = Node() # Least recently used side self.head.next = self.tail self.tail.prev = self.head def _remove(self, node: Node) -> None: """Remove a node from the linked list.""" node.prev.next = node.next node.next.prev = node.prev def _add_to_front(self, node: Node) -> None: """Add a node right after head (most recently used).""" node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def get(self, key: Any) -> Optional[Any]: """ Get the value for the given key. Returns the value if key exists, otherwise None. Moves the accessed node to the front (most recently used). """ if key not in self.cache: return None node = self.cache[key] # Move to front self._remove(node) self._add_to_front(node) return node.value def put(self, key: Any, value: Any) -> None: """ Put a key-value pair into the cache. If the key exists, update its value and move to front. If the cache is at capacity, evict the least recently used item. """ if key in self.cache: # Update existing key node = self.cache[key] node.value = value # Move to front self._remove(node) self._add_to_front(node) else: # New key if len(self.cache) >= self.capacity: # Evict least recently used (tail.prev) lru = self.tail.prev self._remove(lru) del self.cache[lru.key] # Add new node to front new_node = Node(key, value) self.cache[key] = new_node self._add_to_front(new_node) # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- def test_basic_get_put() -> None: """Test basic get and put operations.""" cache = LRUCache(2) cache.put(1, 1) assert cache.get(1) == 1 cache.put(2, 2) assert cache.get(2) == 2 assert cache.get(3) is None # Key not present def test_update_existing_key() -> None: """Test updating an existing key.""" cache = LRUCache(2) cache.put(1, 1) cache.put(2, 2) cache.put(1, 10) # Update key 1 assert cache.get(1) == 10 assert cache.get(2) == 2 def test_eviction_order() -> None: """Test that eviction removes the least recently used item.""" cache = LRUCache(2) cache.put(1, 1) cache.put(2, 2) # Access key 1 to make it most recently used assert cache.get(1) == 1 # Now key 2 is least recently used, so adding key 3 should evict key 2 cache.put(3, 3) assert cache.get(2) is None # Key 2 was evicted assert cache.get(1) == 1 assert cache.get(3) == 3 def test_eviction_after_update() -> None: """Test that updating a key moves it to most recently used.""" cache = LRUCache(3) cache.put(1, 1) cache.put(2, 2) cache.put(3, 3) # Access key 1, making it most recently used assert cache.get(1) == 1 # Now order of recency: 1, 3, 2 (2 is least recently used) # Add key 4, should evict key 2 cache.put(4, 4) assert cache.get(2) is None # Key 2 was evicted assert cache.get(1) == 1 assert cache.get(3) == 3 assert cache.get(4) == 4 def test_capacity_one() -> None: """Test cache with capacity 1.""" cache = LRUCache(1) cache.put(1, 1) assert cache.get(1) == 1 cache.put(2, 2) assert cache.get(1) is None # Key 1 was evicted assert cache.get(2) == 2 def test_multiple_updates() -> None: """Test multiple updates to the same key.""" cache = LRUCache(2) cache.put(1, 1) cache.put(1, 2) cache.put(1, 3) assert cache.get(1) == 3 def test_eviction_sequence() -> None: """Test a sequence of operations that exercises eviction.""" cache = LRUCache(2) cache.put(1, 1) cache.put(2, 2) assert cache.get(1) == 1 # 1 is now MRU, 2 is LRU cache.put(3, 3) # Evicts 2 assert cache.get(2) is None assert cache.get(1) == 1 assert cache.get(3) == 3 def test_invalid_capacity() -> None: """Test that invalid capacity raises ValueError.""" try: LRUCache(0) assert False, "Should have raised ValueError" except ValueError: pass try: LRUCache(-1) assert False, "Should have raised ValueError" except ValueError: pass def test_large_capacity() -> None: """Test cache with larger capacity.""" cache = LRUCache(5) for i in range(10): cache.put(i, i * 10) # Only last 5 should remain for i in range(5): assert cache.get(i) is None for i in range(5, 10): assert cache.get(i) == i * 10 if __name__ == "__main__": test_basic_get_put() test_update_existing_key() test_eviction_order() test_eviction_after_update() test_capacity_one() test_multiple_updates() test_eviction_sequence() test_invalid_capacity() test_large_capacity() print("All tests passed!") ```