2
2
3
3
from collections .abc import Callable , Hashable
4
4
from functools import wraps
5
- from typing import Any , ParamSpec , TypeVar , cast
5
+ from typing import Any , ParamSpec , TypeVar
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
-
13
+
14
14
__slots__ = ("key" , "val" , "next" , "prev" )
15
-
15
+
16
16
def __init__ (self , key : Any , val : Any ) -> None :
17
17
self .key = key
18
18
self .val = val
19
19
self .next : DoubleLinkedListNode | None = None
20
20
self .prev : DoubleLinkedListNode | None = None
21
-
21
+
22
22
def __repr__ (self ) -> str :
23
23
return f"Node(key={ self .key } , val={ self .val } )"
24
24
25
25
26
26
class DoubleLinkedList :
27
27
"""Double Linked List for LRU Cache"""
28
-
28
+
29
29
def __init__ (self ) -> None :
30
30
# Create sentinel nodes
31
31
self .head = DoubleLinkedListNode (None , None )
32
32
self .rear = DoubleLinkedListNode (None , None )
33
33
# Link sentinel nodes together
34
34
self .head .next = self .rear
35
35
self .rear .prev = self .head
36
-
36
+
37
37
def __repr__ (self ) -> str :
38
38
nodes = []
39
39
current = self .head
40
40
while current :
41
41
nodes .append (repr (current ))
42
42
current = current .next
43
43
return f"LinkedList({ nodes } )"
44
-
44
+
45
45
def add (self , node : DoubleLinkedListNode ) -> None :
46
46
"""Add node before rear"""
47
47
prev = self .rear .prev
48
48
if prev is None :
49
- return # Should never happen with sentinel nodes
50
-
49
+ return
50
+
51
+ # Insert node between prev and rear
51
52
prev .next = node
52
53
node .prev = prev
53
54
self .rear .prev = node
54
55
node .next = self .rear
55
-
56
+
56
57
def remove (self , node : DoubleLinkedListNode ) -> DoubleLinkedListNode | None :
57
58
"""Remove node from list"""
58
59
if node .prev is None or node .next is None :
59
60
return None
60
-
61
+
62
+ # Bypass node
61
63
node .prev .next = node .next
62
64
node .next .prev = node .prev
63
- node .prev = node .next = None
65
+
66
+ # Clear node references
67
+ node .prev = None
68
+ node .next = None
64
69
return node
65
70
66
71
67
72
class LRUCache :
68
73
"""LRU Cache implementation"""
69
-
74
+
70
75
def __init__ (self , capacity : int ) -> None :
71
76
self .list = DoubleLinkedList ()
72
77
self .capacity = capacity
73
78
self .size = 0
74
79
self .hits = 0
75
80
self .misses = 0
76
81
self .cache : dict [Any , DoubleLinkedListNode ] = {}
77
-
82
+
78
83
def __repr__ (self ) -> str :
79
84
return (
80
85
f"Cache(hits={ self .hits } , misses={ self .misses } , "
81
86
f"cap={ self .capacity } , size={ self .size } )"
82
87
)
83
-
88
+
84
89
def get (self , key : Any ) -> Any | None :
85
90
"""Get value for key"""
86
91
if key in self .cache :
@@ -91,67 +96,68 @@ def get(self, key: Any) -> Any | None:
91
96
return node .val
92
97
self .misses += 1
93
98
return None
94
-
99
+
95
100
def put (self , key : Any , value : Any ) -> None :
96
101
"""Set value for key"""
97
102
if key in self .cache :
103
+ # Update existing node
98
104
node = self .cache [key ]
99
105
if self .list .remove (node ):
100
106
node .val = value
101
107
self .list .add (node )
102
108
return
103
-
109
+
110
+ # Evict LRU item if at capacity
104
111
if self .size >= self .capacity :
105
- # Remove least recently used item
106
112
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 :
108
114
if self .list .remove (first_node ):
109
115
del self .cache [first_node .key ]
110
116
self .size -= 1
111
-
117
+
118
+ # Add new node
112
119
new_node = DoubleLinkedListNode (key , value )
113
120
self .cache [key ] = new_node
114
121
self .list .add (new_node )
115
122
self .size += 1
116
-
123
+
117
124
def cache_info (self ) -> dict [str , Any ]:
118
125
"""Get cache statistics"""
119
126
return {
120
127
"hits" : self .hits ,
121
128
"misses" : self .misses ,
122
129
"capacity" : self .capacity ,
123
- "size" : self .size ,
130
+ "size" : self .size
124
131
}
125
132
126
133
127
134
def lru_cache (maxsize : int = 128 ) -> Callable [[Callable [P , R ]], Callable [P , R ]]:
128
135
"""LRU Cache decorator"""
129
-
130
136
def decorator (func : Callable [P , R ]) -> Callable [P , R ]:
131
137
cache = LRUCache (maxsize )
132
-
138
+
133
139
@wraps (func )
134
140
def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> R :
135
141
# Create normalized cache key
136
142
key = (args , tuple (sorted (kwargs .items ())))
137
-
143
+
138
144
# 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 :
140
147
return cached
141
-
148
+
142
149
# Compute and cache result
143
150
result = func (* args , ** kwargs )
144
151
cache .put (key , result )
145
152
return result
146
-
153
+
147
154
# Attach cache info method
148
155
wrapper .cache_info = cache .cache_info # type: ignore[attr-defined]
149
156
return wrapper
150
-
157
+
151
158
return decorator
152
159
153
160
154
161
if __name__ == "__main__" :
155
162
import doctest
156
-
157
163
doctest .testmod ()
0 commit comments