2
2
3
3
from collections .abc import Callable , Hashable
4
4
from functools import wraps
5
- from typing import Any , Generic , ParamSpec , TypeVar , cast
5
+ from typing import Any , ParamSpec , TypeVar , cast
6
6
7
7
P = ParamSpec ("P" )
8
8
R = TypeVar ("R" )
9
9
10
10
11
11
class DoubleLinkedListNode :
12
12
"""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 :
15
17
self .key = key
16
18
self .val = val
17
19
self .next : DoubleLinkedListNode | None = None
18
20
self .prev : DoubleLinkedListNode | None = None
19
-
21
+
20
22
def __repr__ (self ) -> str :
21
23
return f"Node(key={ self .key } , val={ self .val } )"
22
24
23
25
24
26
class DoubleLinkedList :
25
27
"""Double Linked List for LRU Cache"""
26
-
28
+
27
29
def __init__ (self ) -> None :
30
+ # Create sentinel nodes
28
31
self .head = DoubleLinkedListNode (None , None )
29
32
self .rear = DoubleLinkedListNode (None , None )
33
+ # Link sentinel nodes together
30
34
self .head .next = self .rear
31
35
self .rear .prev = self .head
32
-
36
+
33
37
def __repr__ (self ) -> str :
34
38
nodes = []
35
39
current = self .head
36
40
while current :
37
41
nodes .append (repr (current ))
38
42
current = current .next
39
43
return f"LinkedList({ nodes } )"
40
-
44
+
41
45
def add (self , node : DoubleLinkedListNode ) -> None :
42
- """Add node to list end """
46
+ """Add node before rear """
43
47
prev = self .rear .prev
44
48
if prev is None :
45
- raise ValueError ( "Invalid list state" )
46
-
49
+ return # Should never happen with sentinel nodes
50
+
47
51
prev .next = node
48
52
node .prev = prev
49
53
self .rear .prev = node
50
54
node .next = self .rear
51
-
55
+
52
56
def remove (self , node : DoubleLinkedListNode ) -> DoubleLinkedListNode | None :
53
57
"""Remove node from list"""
54
58
if node .prev is None or node .next is None :
55
59
return None
56
-
60
+
57
61
node .prev .next = node .next
58
62
node .next .prev = node .prev
59
63
node .prev = node .next = None
@@ -62,21 +66,21 @@ def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode | None:
62
66
63
67
class LRUCache :
64
68
"""LRU Cache implementation"""
65
-
69
+
66
70
def __init__ (self , capacity : int ) -> None :
67
71
self .list = DoubleLinkedList ()
68
72
self .capacity = capacity
69
73
self .size = 0
70
74
self .hits = 0
71
75
self .misses = 0
72
76
self .cache : dict [Any , DoubleLinkedListNode ] = {}
73
-
77
+
74
78
def __repr__ (self ) -> str :
75
79
return (
76
80
f"Cache(hits={ self .hits } , misses={ self .misses } , "
77
81
f"cap={ self .capacity } , size={ self .size } )"
78
82
)
79
-
83
+
80
84
def get (self , key : Any ) -> Any | None :
81
85
"""Get value for key"""
82
86
if key in self .cache :
@@ -87,7 +91,7 @@ def get(self, key: Any) -> Any | None:
87
91
return node .val
88
92
self .misses += 1
89
93
return None
90
-
94
+
91
95
def put (self , key : Any , value : Any ) -> None :
92
96
"""Set value for key"""
93
97
if key in self .cache :
@@ -96,43 +100,57 @@ def put(self, key: Any, value: Any) -> None:
96
100
node .val = value
97
101
self .list .add (node )
98
102
return
99
-
103
+
100
104
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
+
106
112
new_node = DoubleLinkedListNode (key , value )
107
113
self .cache [key ] = new_node
108
114
self .list .add (new_node )
109
115
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 ]]:
113
128
"""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
+
118
132
@wraps (func )
119
133
def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> R :
134
+ # Create normalized cache key
120
135
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 :
122
140
return cached
123
-
141
+
142
+ # Compute and cache result
124
143
result = func (* args , ** kwargs )
125
144
cache .put (key , result )
126
145
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]
130
149
return wrapper
131
-
132
- return decorator_func
150
+
151
+ return decorator
133
152
134
153
135
154
if __name__ == "__main__" :
136
155
import doctest
137
-
138
156
doctest .testmod ()
0 commit comments