1
1
from __future__ import annotations
2
2
3
- from collections .abc import Callable , Hashable
3
+ from collections .abc import Callable
4
4
from functools import wraps
5
5
from typing import Any , ParamSpec , TypeVar
6
6
10
10
11
11
class DoubleLinkedListNode :
12
12
"""Node for LRU Cache"""
13
-
14
- __slots__ = ("key" , "val " , "next " , "prev " )
15
-
13
+
14
+ __slots__ = ("key" , "next " , "prev " , "val " )
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
49
return
50
-
50
+
51
51
# Insert node between prev and rear
52
52
prev .next = node
53
53
node .prev = prev
54
54
self .rear .prev = node
55
55
node .next = self .rear
56
-
56
+
57
57
def remove (self , node : DoubleLinkedListNode ) -> DoubleLinkedListNode | None :
58
58
"""Remove node from list"""
59
59
if node .prev is None or node .next is None :
60
60
return None
61
-
61
+
62
62
# Bypass node
63
63
node .prev .next = node .next
64
64
node .next .prev = node .prev
65
-
65
+
66
66
# Clear node references
67
67
node .prev = None
68
68
node .next = None
@@ -71,21 +71,21 @@ def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode | None:
71
71
72
72
class LRUCache :
73
73
"""LRU Cache implementation"""
74
-
74
+
75
75
def __init__ (self , capacity : int ) -> None :
76
76
self .list = DoubleLinkedList ()
77
77
self .capacity = capacity
78
78
self .size = 0
79
79
self .hits = 0
80
80
self .misses = 0
81
81
self .cache : dict [Any , DoubleLinkedListNode ] = {}
82
-
82
+
83
83
def __repr__ (self ) -> str :
84
84
return (
85
85
f"Cache(hits={ self .hits } , misses={ self .misses } , "
86
86
f"cap={ self .capacity } , size={ self .size } )"
87
87
)
88
-
88
+
89
89
def get (self , key : Any ) -> Any | None :
90
90
"""Get value for key"""
91
91
if key in self .cache :
@@ -96,7 +96,7 @@ def get(self, key: Any) -> Any | None:
96
96
return node .val
97
97
self .misses += 1
98
98
return None
99
-
99
+
100
100
def put (self , key : Any , value : Any ) -> None :
101
101
"""Set value for key"""
102
102
if key in self .cache :
@@ -106,59 +106,62 @@ def put(self, key: Any, value: Any) -> None:
106
106
node .val = value
107
107
self .list .add (node )
108
108
return
109
-
109
+
110
110
# Evict LRU item if at capacity
111
111
if self .size >= self .capacity :
112
112
first_node = self .list .head .next
113
- if first_node and first_node .key and first_node != self .list .rear :
114
- if self .list .remove (first_node ):
115
- del self .cache [first_node .key ]
116
- self .size -= 1
117
-
113
+ if (
114
+ first_node
115
+ and first_node .key is not None
116
+ and first_node != self .list .rear
117
+ and self .list .remove (first_node )
118
+ ):
119
+ del self .cache [first_node .key ]
120
+ self .size -= 1
121
+
118
122
# Add new node
119
123
new_node = DoubleLinkedListNode (key , value )
120
124
self .cache [key ] = new_node
121
125
self .list .add (new_node )
122
126
self .size += 1
123
-
127
+
124
128
def cache_info (self ) -> dict [str , Any ]:
125
129
"""Get cache statistics"""
126
130
return {
127
131
"hits" : self .hits ,
128
132
"misses" : self .misses ,
129
133
"capacity" : self .capacity ,
130
- "size" : self .size ,
134
+ "size" : self .size
131
135
}
132
136
133
137
134
138
def lru_cache (maxsize : int = 128 ) -> Callable [[Callable [P , R ]], Callable [P , R ]]:
135
139
"""LRU Cache decorator"""
136
-
137
140
def decorator (func : Callable [P , R ]) -> Callable [P , R ]:
138
141
cache = LRUCache (maxsize )
139
-
142
+
140
143
@wraps (func )
141
144
def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> R :
142
145
# Create normalized cache key
143
146
key = (args , tuple (sorted (kwargs .items ())))
144
-
147
+
145
148
# Try to get cached result
146
- if (cached := cache .get (key )) is not None :
149
+ cached = cache .get (key )
150
+ if cached is not None :
147
151
return cached
148
-
152
+
149
153
# Compute and cache result
150
154
result = func (* args , ** kwargs )
151
155
cache .put (key , result )
152
156
return result
153
-
157
+
154
158
# Attach cache info method
155
159
wrapper .cache_info = cache .cache_info # type: ignore[attr-defined]
156
160
return wrapper
157
-
161
+
158
162
return decorator
159
163
160
164
161
165
if __name__ == "__main__" :
162
166
import doctest
163
-
164
167
doctest .testmod ()
0 commit comments