Skip to content

Commit c58ca7e

Browse files
authored
Update lru_cache.py
1 parent 24f8370 commit c58ca7e

File tree

1 file changed

+36
-30
lines changed

1 file changed

+36
-30
lines changed

other/lru_cache.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,85 +2,90 @@
22

33
from collections.abc import Callable, Hashable
44
from functools import wraps
5-
from typing import Any, ParamSpec, TypeVar, cast
5+
from typing import Any, ParamSpec, TypeVar
66

77
P = ParamSpec("P")
88
R = TypeVar("R")
99

1010

1111
class DoubleLinkedListNode:
1212
"""Node for LRU Cache"""
13-
13+
1414
__slots__ = ("key", "val", "next", "prev")
15-
15+
1616
def __init__(self, key: Any, val: Any) -> None:
1717
self.key = key
1818
self.val = val
1919
self.next: DoubleLinkedListNode | None = None
2020
self.prev: DoubleLinkedListNode | None = None
21-
21+
2222
def __repr__(self) -> str:
2323
return f"Node(key={self.key}, val={self.val})"
2424

2525

2626
class DoubleLinkedList:
2727
"""Double Linked List for LRU Cache"""
28-
28+
2929
def __init__(self) -> None:
3030
# Create sentinel nodes
3131
self.head = DoubleLinkedListNode(None, None)
3232
self.rear = DoubleLinkedListNode(None, None)
3333
# Link sentinel nodes together
3434
self.head.next = self.rear
3535
self.rear.prev = self.head
36-
36+
3737
def __repr__(self) -> str:
3838
nodes = []
3939
current = self.head
4040
while current:
4141
nodes.append(repr(current))
4242
current = current.next
4343
return f"LinkedList({nodes})"
44-
44+
4545
def add(self, node: DoubleLinkedListNode) -> None:
4646
"""Add node before rear"""
4747
prev = self.rear.prev
4848
if prev is None:
49-
return # Should never happen with sentinel nodes
50-
49+
return
50+
51+
# Insert node between prev and rear
5152
prev.next = node
5253
node.prev = prev
5354
self.rear.prev = node
5455
node.next = self.rear
55-
56+
5657
def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode | None:
5758
"""Remove node from list"""
5859
if node.prev is None or node.next is None:
5960
return None
60-
61+
62+
# Bypass node
6163
node.prev.next = node.next
6264
node.next.prev = node.prev
63-
node.prev = node.next = None
65+
66+
# Clear node references
67+
node.prev = None
68+
node.next = None
6469
return node
6570

6671

6772
class LRUCache:
6873
"""LRU Cache implementation"""
69-
74+
7075
def __init__(self, capacity: int) -> None:
7176
self.list = DoubleLinkedList()
7277
self.capacity = capacity
7378
self.size = 0
7479
self.hits = 0
7580
self.misses = 0
7681
self.cache: dict[Any, DoubleLinkedListNode] = {}
77-
82+
7883
def __repr__(self) -> str:
7984
return (
8085
f"Cache(hits={self.hits}, misses={self.misses}, "
8186
f"cap={self.capacity}, size={self.size})"
8287
)
83-
88+
8489
def get(self, key: Any) -> Any | None:
8590
"""Get value for key"""
8691
if key in self.cache:
@@ -91,67 +96,68 @@ def get(self, key: Any) -> Any | None:
9196
return node.val
9297
self.misses += 1
9398
return None
94-
99+
95100
def put(self, key: Any, value: Any) -> None:
96101
"""Set value for key"""
97102
if key in self.cache:
103+
# Update existing node
98104
node = self.cache[key]
99105
if self.list.remove(node):
100106
node.val = value
101107
self.list.add(node)
102108
return
103-
109+
110+
# Evict LRU item if at capacity
104111
if self.size >= self.capacity:
105-
# Remove least recently used item
106112
first_node = self.list.head.next
107-
if first_node and first_node != self.list.rear:
113+
if first_node and first_node.key and first_node != self.list.rear:
108114
if self.list.remove(first_node):
109115
del self.cache[first_node.key]
110116
self.size -= 1
111-
117+
118+
# Add new node
112119
new_node = DoubleLinkedListNode(key, value)
113120
self.cache[key] = new_node
114121
self.list.add(new_node)
115122
self.size += 1
116-
123+
117124
def cache_info(self) -> dict[str, Any]:
118125
"""Get cache statistics"""
119126
return {
120127
"hits": self.hits,
121128
"misses": self.misses,
122129
"capacity": self.capacity,
123-
"size": self.size,
130+
"size": self.size
124131
}
125132

126133

127134
def lru_cache(maxsize: int = 128) -> Callable[[Callable[P, R]], Callable[P, R]]:
128135
"""LRU Cache decorator"""
129-
130136
def decorator(func: Callable[P, R]) -> Callable[P, R]:
131137
cache = LRUCache(maxsize)
132-
138+
133139
@wraps(func)
134140
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
135141
# Create normalized cache key
136142
key = (args, tuple(sorted(kwargs.items())))
137-
143+
138144
# Try to get cached result
139-
if (cached := cache.get(key)) is not None:
145+
cached = cache.get(key)
146+
if cached is not None:
140147
return cached
141-
148+
142149
# Compute and cache result
143150
result = func(*args, **kwargs)
144151
cache.put(key, result)
145152
return result
146-
153+
147154
# Attach cache info method
148155
wrapper.cache_info = cache.cache_info # type: ignore[attr-defined]
149156
return wrapper
150-
157+
151158
return decorator
152159

153160

154161
if __name__ == "__main__":
155162
import doctest
156-
157163
doctest.testmod()

0 commit comments

Comments
 (0)