classLRUCache:"""LRU Cache using OrderedDict."""def__init__(self,capacity:int)->None:ifcapacity<=0:msg=f"capacity must be positive, got {capacity}"raiseValueError(msg)self._cache:OrderedDict[int,int]=OrderedDict()self._capacity=capacitydefget(self,key:int)->int:"""Return value for key, or -1 if not found. Marks key as recently used."""ifkeynotinself._cache:return-1self._cache.move_to_end(key)returnself._cache[key]defput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._cache.move_to_end(key)self._cache[key]=valueiflen(self._cache)>self._capacity:self._cache.popitem(last=False)
defget(self,key:int)->int:"""Return value for key, or -1 if not found. Marks key as recently used."""ifkeynotinself._cache:return-1self._cache.move_to_end(key)returnself._cache[key]
defput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._cache.move_to_end(key)self._cache[key]=valueiflen(self._cache)>self._capacity:self._cache.popitem(last=False)
classLRUCacheManual:"""LRU Cache using a doubly linked list + hash map."""def__init__(self,capacity:int)->None:ifcapacity<=0:msg=f"capacity must be positive, got {capacity}"raiseValueError(msg)self._capacity=capacityself._cache:dict[int,_DNode]={}# Sentinel nodes simplify edge-case handling.self._head=_DNode()self._tail=_DNode()self._head.next=self._tailself._tail.prev=self._headdef_remove(self,node:_DNode)->None:"""Unlink a node from the doubly linked list."""assertnode.previsnotNoneassertnode.nextisnotNonenode.prev.next=node.nextnode.next.prev=node.prevdef_add_to_front(self,node:_DNode)->None:"""Insert a node right after the head sentinel (most recent)."""assertself._head.nextisnotNonenode.next=self._head.nextnode.prev=self._headself._head.next.prev=nodeself._head.next=nodedefget(self,key:int)->int:"""Return value for key, or -1 if not found."""ifkeynotinself._cache:return-1node=self._cache[key]self._remove(node)self._add_to_front(node)returnnode.valdefput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._remove(self._cache[key])node=_DNode(key,value)self._cache[key]=nodeself._add_to_front(node)iflen(self._cache)>self._capacity:assertself._tail.previsnotNonelru=self._tail.prevself._remove(lru)delself._cache[lru.key]
defget(self,key:int)->int:"""Return value for key, or -1 if not found."""ifkeynotinself._cache:return-1node=self._cache[key]self._remove(node)self._add_to_front(node)returnnode.val
defput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._remove(self._cache[key])node=_DNode(key,value)self._cache[key]=nodeself._add_to_front(node)iflen(self._cache)>self._capacity:assertself._tail.previsnotNonelru=self._tail.prevself._remove(lru)delself._cache[lru.key]
tests/linked_lists/test_lru_cache.py
"""Tests for LRU Cache."""importpytestfromalgo.linked_lists.lru_cacheimportLRUCache,LRUCacheManualclassTestLRUCache:deftest_basic_get_put(self)->None:cache=LRUCache(2)cache.put(1,1)cache.put(2,2)assertcache.get(1)==1cache.put(3,3)# evicts key 2assertcache.get(2)==-1deftest_update_existing_key(self)->None:cache=LRUCache(2)cache.put(1,1)cache.put(2,2)cache.put(1,10)# update key 1assertcache.get(1)==10cache.put(3,3)# evicts key 2 (not 1, since 1 was just updated)assertcache.get(2)==-1assertcache.get(1)==10deftest_get_missing_key(self)->None:cache=LRUCache(1)assertcache.get(99)==-1deftest_capacity_one(self)->None:cache=LRUCache(1)cache.put(1,1)cache.put(2,2)# evicts key 1assertcache.get(1)==-1assertcache.get(2)==2deftest_eviction_order(self)->None:cache=LRUCache(3)cache.put(1,1)cache.put(2,2)cache.put(3,3)cache.get(1)# 1 is now most recently usedcache.put(4,4)# evicts key 2 (least recently used)assertcache.get(2)==-1assertcache.get(1)==1assertcache.get(3)==3assertcache.get(4)==4deftest_zero_capacity_raises(self)->None:withpytest.raises(ValueError):LRUCache(0)classTestLRUCacheManual:deftest_basic_get_put(self)->None:cache=LRUCacheManual(2)cache.put(1,1)cache.put(2,2)assertcache.get(1)==1cache.put(3,3)# evicts key 2assertcache.get(2)==-1deftest_update_existing_key(self)->None:cache=LRUCacheManual(2)cache.put(1,1)cache.put(2,2)cache.put(1,10)assertcache.get(1)==10cache.put(3,3)# evicts key 2assertcache.get(2)==-1assertcache.get(1)==10deftest_get_missing_key(self)->None:cache=LRUCacheManual(1)assertcache.get(99)==-1deftest_capacity_one(self)->None:cache=LRUCacheManual(1)cache.put(1,1)cache.put(2,2)assertcache.get(1)==-1assertcache.get(2)==2deftest_eviction_order(self)->None:cache=LRUCacheManual(3)cache.put(1,1)cache.put(2,2)cache.put(3,3)cache.get(1)cache.put(4,4)# evicts key 2assertcache.get(2)==-1assertcache.get(1)==1assertcache.get(3)==3assertcache.get(4)==4deftest_zero_capacity_raises(self)->None:withpytest.raises(ValueError):LRUCacheManual(0)
Implement it yourself
Run:just challenge linked_lists lru_cache
Then implement the functions to make all tests pass.
Use just study linked_lists for watch mode.
classLRUCache:"""LRU Cache using OrderedDict."""def__init__(self,capacity:int)->None:ifcapacity<=0:msg=f"capacity must be positive, got {capacity}"raiseValueError(msg)self._cache:OrderedDict[int,int]=OrderedDict()self._capacity=capacitydefget(self,key:int)->int:"""Return value for key, or -1 if not found. Marks key as recently used."""ifkeynotinself._cache:return-1self._cache.move_to_end(key)returnself._cache[key]defput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._cache.move_to_end(key)self._cache[key]=valueiflen(self._cache)>self._capacity:self._cache.popitem(last=False)
defget(self,key:int)->int:"""Return value for key, or -1 if not found. Marks key as recently used."""ifkeynotinself._cache:return-1self._cache.move_to_end(key)returnself._cache[key]
defput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._cache.move_to_end(key)self._cache[key]=valueiflen(self._cache)>self._capacity:self._cache.popitem(last=False)
classLRUCacheManual:"""LRU Cache using a doubly linked list + hash map."""def__init__(self,capacity:int)->None:ifcapacity<=0:msg=f"capacity must be positive, got {capacity}"raiseValueError(msg)self._capacity=capacityself._cache:dict[int,_DNode]={}# Sentinel nodes simplify edge-case handling.self._head=_DNode()self._tail=_DNode()self._head.next=self._tailself._tail.prev=self._headdef_remove(self,node:_DNode)->None:"""Unlink a node from the doubly linked list."""assertnode.previsnotNoneassertnode.nextisnotNonenode.prev.next=node.nextnode.next.prev=node.prevdef_add_to_front(self,node:_DNode)->None:"""Insert a node right after the head sentinel (most recent)."""assertself._head.nextisnotNonenode.next=self._head.nextnode.prev=self._headself._head.next.prev=nodeself._head.next=nodedefget(self,key:int)->int:"""Return value for key, or -1 if not found."""ifkeynotinself._cache:return-1node=self._cache[key]self._remove(node)self._add_to_front(node)returnnode.valdefput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._remove(self._cache[key])node=_DNode(key,value)self._cache[key]=nodeself._add_to_front(node)iflen(self._cache)>self._capacity:assertself._tail.previsnotNonelru=self._tail.prevself._remove(lru)delself._cache[lru.key]
defget(self,key:int)->int:"""Return value for key, or -1 if not found."""ifkeynotinself._cache:return-1node=self._cache[key]self._remove(node)self._add_to_front(node)returnnode.val
defput(self,key:int,value:int)->None:"""Insert or update key-value pair. Evicts LRU entry if at capacity."""ifkeyinself._cache:self._remove(self._cache[key])node=_DNode(key,value)self._cache[key]=nodeself._add_to_front(node)iflen(self._cache)>self._capacity:assertself._tail.previsnotNonelru=self._tail.prevself._remove(lru)delself._cache[lru.key]