Skip to content

Commit 51e3876

Browse files
authored
Update lru_cache.py
1 parent 36e3e54 commit 51e3876

File tree

1 file changed

+55
-37
lines changed

1 file changed

+55
-37
lines changed

other/lru_cache.py

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,62 @@
22

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

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

1010

1111
class DoubleLinkedListNode:
1212
"""Node for LRU Cache"""
13-
14-
def __init__(self, key: Any | None, val: Any | None) -> None:
13+
14+
__slots__ = ("key", "val", "next", "prev")
15+
16+
def __init__(self, key: Any, val: Any) -> None:
1517
self.key = key
1618
self.val = val
1719
self.next: DoubleLinkedListNode | None = None
1820
self.prev: DoubleLinkedListNode | None = None
19-
21+
2022
def __repr__(self) -> str:
2123
return f"Node(key={self.key}, val={self.val})"
2224

2325

2426
class DoubleLinkedList:
2527
"""Double Linked List for LRU Cache"""
26-
28+
2729
def __init__(self) -> None:
30+
# Create sentinel nodes
2831
self.head = DoubleLinkedListNode(None, None)
2932
self.rear = DoubleLinkedListNode(None, None)
33+
# Link sentinel nodes together
3034
self.head.next = self.rear
3135
self.rear.prev = self.head
32-
36+
3337
def __repr__(self) -> str:
3438
nodes = []
3539
current = self.head
3640
while current:
3741
nodes.append(repr(current))
3842
current = current.next
3943
return f"LinkedList({nodes})"
40-
44+
4145
def add(self, node: DoubleLinkedListNode) -> None:
42-
"""Add node to list end"""
46+
"""Add node before rear"""
4347
prev = self.rear.prev
4448
if prev is None:
45-
raise ValueError("Invalid list state")
46-
49+
return # Should never happen with sentinel nodes
50+
4751
prev.next = node
4852
node.prev = prev
4953
self.rear.prev = node
5054
node.next = self.rear
51-
55+
5256
def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode | None:
5357
"""Remove node from list"""
5458
if node.prev is None or node.next is None:
5559
return None
56-
60+
5761
node.prev.next = node.next
5862
node.next.prev = node.prev
5963
node.prev = node.next = None
@@ -62,21 +66,21 @@ def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode | None:
6266

6367
class LRUCache:
6468
"""LRU Cache implementation"""
65-
69+
6670
def __init__(self, capacity: int) -> None:
6771
self.list = DoubleLinkedList()
6872
self.capacity = capacity
6973
self.size = 0
7074
self.hits = 0
7175
self.misses = 0
7276
self.cache: dict[Any, DoubleLinkedListNode] = {}
73-
77+
7478
def __repr__(self) -> str:
7579
return (
7680
f"Cache(hits={self.hits}, misses={self.misses}, "
7781
f"cap={self.capacity}, size={self.size})"
7882
)
79-
83+
8084
def get(self, key: Any) -> Any | None:
8185
"""Get value for key"""
8286
if key in self.cache:
@@ -87,7 +91,7 @@ def get(self, key: Any) -> Any | None:
8791
return node.val
8892
self.misses += 1
8993
return None
90-
94+
9195
def put(self, key: Any, value: Any) -> None:
9296
"""Set value for key"""
9397
if key in self.cache:
@@ -96,43 +100,57 @@ def put(self, key: Any, value: Any) -> None:
96100
node.val = value
97101
self.list.add(node)
98102
return
99-
103+
100104
if self.size >= self.capacity:
101-
first = self.list.head.next
102-
if first and first.key and self.list.remove(first):
103-
del self.cache[first.key]
104-
self.size -= 1
105-
105+
# Remove least recently used item
106+
first_node = self.list.head.next
107+
if first_node and first_node != self.list.rear:
108+
if self.list.remove(first_node):
109+
del self.cache[first_node.key]
110+
self.size -= 1
111+
106112
new_node = DoubleLinkedListNode(key, value)
107113
self.cache[key] = new_node
108114
self.list.add(new_node)
109115
self.size += 1
110-
111-
112-
def lru_cache(size: int = 128) -> Callable[[Callable[P, R]], Callable[P, R]]:
116+
117+
def cache_info(self) -> dict[str, Any]:
118+
"""Get cache statistics"""
119+
return {
120+
"hits": self.hits,
121+
"misses": self.misses,
122+
"capacity": self.capacity,
123+
"size": self.size
124+
}
125+
126+
127+
def lru_cache(maxsize: int = 128) -> Callable[[Callable[P, R]], Callable[P, R]]:
113128
"""LRU Cache decorator"""
114-
115-
def decorator_func(func: Callable[P, R]) -> Callable[P, R]:
116-
cache = LRUCache(size)
117-
129+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
130+
cache = LRUCache(maxsize)
131+
118132
@wraps(func)
119133
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
134+
# Create normalized cache key
120135
key = (args, tuple(sorted(kwargs.items())))
121-
if (cached := cache.get(key)) is not None:
136+
137+
# Try to get cached result
138+
cached = cache.get(key)
139+
if cached is not None:
122140
return cached
123-
141+
142+
# Compute and cache result
124143
result = func(*args, **kwargs)
125144
cache.put(key, result)
126145
return result
127-
128-
# Add cache_info attribute
129-
wrapper.cache_info = lambda: cache # type: ignore[attr-defined]
146+
147+
# Attach cache info method
148+
wrapper.cache_info = cache.cache_info # type: ignore[attr-defined]
130149
return wrapper
131-
132-
return decorator_func
150+
151+
return decorator
133152

134153

135154
if __name__ == "__main__":
136155
import doctest
137-
138156
doctest.testmod()

0 commit comments

Comments
 (0)