22
33from collections .abc import Callable , Hashable
44from functools import wraps
5- from typing import Generic , TypeVar , Any , cast , overload , TYPE_CHECKING
6- from typing_extensions import ParamSpec
5+ from typing import Any , Generic , ParamSpec , TypeVar , overload , TYPE_CHECKING
76
87if TYPE_CHECKING :
9- from typing_extensions import TypeAlias
8+ type NodeKey = T | None
9+ type NodeValue = U | None
10+ else :
11+ NodeKey = TypeVar ("NodeKey" , bound = Hashable )
12+ NodeValue = TypeVar ("NodeValue" )
1013
1114T = TypeVar ("T" , bound = Hashable )
1215U = TypeVar ("U" )
1316P = ParamSpec ("P" )
1417R = TypeVar ("R" )
1518
16- if TYPE_CHECKING :
17- NodeKey : TypeAlias = T | None
18- NodeValue : TypeAlias = U | None
19- else :
20- NodeKey = TypeVar ("NodeKey" , bound = Hashable )
21- NodeValue = TypeVar ("NodeValue" )
22-
2319
2420class DoubleLinkedListNode (Generic [T , U ]):
25- """
26- Double Linked List Node built specifically for LRU Cache
27-
28- >>> DoubleLinkedListNode(1,1)
29- Node: key: 1, val: 1, has next: False, has prev: False
30- """
21+ """Node built for LRU Cache"""
3122
3223 def __init__ (self , key : NodeKey , val : NodeValue ) -> None :
3324 self .key = key
@@ -36,158 +27,114 @@ def __init__(self, key: NodeKey, val: NodeValue) -> None:
3627 self .prev : DoubleLinkedListNode [T , U ] | None = None
3728
3829 def __repr__ (self ) -> str :
39- return (
40- f"Node: key: { self .key } , val: { self .val } , "
41- f"has next: { bool (self .next )} , has prev: { bool (self .prev )} "
42- )
30+ return f"Node(key={ self .key } , val={ self .val } )"
4331
4432
4533class DoubleLinkedList (Generic [T , U ]):
46- """
47- Double Linked List built specifically for LRU Cache
48- ... [docstring unchanged] ...
49- """
34+ """Double Linked List for LRU Cache"""
5035
5136 def __init__ (self ) -> None :
5237 self .head : DoubleLinkedListNode [T , U ] = DoubleLinkedListNode (None , None )
5338 self .rear : DoubleLinkedListNode [T , U ] = DoubleLinkedListNode (None , None )
5439 self .head .next , self .rear .prev = self .rear , self .head
5540
5641 def __repr__ (self ) -> str :
57- rep = ["DoubleLinkedList" ]
58- node = self .head
59- while node .next is not None :
60- rep .append (str (node ))
61- node = node .next
62- rep .append (str (self .rear ))
63- return ",\n " .join (rep )
42+ nodes = []
43+ current = self .head
44+ while current :
45+ nodes .append (repr (current ))
46+ current = current .next
47+ return f"LinkedList({ nodes } )"
6448
6549 def add (self , node : DoubleLinkedListNode [T , U ]) -> None :
66- """Adds the given node to the end of the list (before rear) """
67- previous = self .rear .prev
68- if previous is None :
69- raise ValueError ("Invalid list state: rear.prev is None " )
70-
71- previous .next = node
72- node .prev = previous
50+ """Add node to list end """
51+ prev = self .rear .prev
52+ if not prev :
53+ raise ValueError ("Invalid list state" )
54+
55+ prev .next = node
56+ node .prev = prev
7357 self .rear .prev = node
7458 node .next = self .rear
7559
76- def remove (
77- self , node : DoubleLinkedListNode [T , U ]
78- ) -> DoubleLinkedListNode [T , U ] | None :
79- """Removes and returns the given node from the list"""
80- if node .prev is None or node .next is None :
60+ def remove (self , node : DoubleLinkedListNode [T , U ]) -> DoubleLinkedListNode [T , U ] | None :
61+ """Remove node from list"""
62+ if not node .prev or not node .next :
8163 return None
82-
64+
8365 node .prev .next = node .next
8466 node .next .prev = node .prev
85- node .prev = None
86- node .next = None
67+ node .prev = node .next = None
8768 return node
8869
8970
9071class LRUCache (Generic [T , U ]):
91- """
92- LRU Cache to store a given capacity of data
93- ... [docstring unchanged] ...
94- """
72+ """LRU Cache implementation"""
9573
9674 def __init__ (self , capacity : int ) -> None :
97- self .list : DoubleLinkedList [T , U ] = DoubleLinkedList ()
75+ self .list = DoubleLinkedList [T , U ]()
9876 self .capacity = capacity
99- self .num_keys = 0
77+ self .size = 0
10078 self .hits = 0
101- self .miss = 0
79+ self .misses = 0
10280 self .cache : dict [T , DoubleLinkedListNode [T , U ]] = {}
10381
10482 def __repr__ (self ) -> str :
105- return (
106- f"CacheInfo(hits={ self .hits } , misses={ self .miss } , "
107- f"capacity={ self .capacity } , current size={ self .num_keys } )"
108- )
83+ return f"Cache(hits={ self .hits } , misses={ self .misses } , cap={ self .capacity } , size={ self .size } )"
10984
11085 def __contains__ (self , key : T ) -> bool :
11186 return key in self .cache
11287
11388 def get (self , key : T ) -> U | None :
114- """Returns the value for the input key"""
89+ """Get value for key"""
11590 if key in self .cache :
11691 self .hits += 1
117- value_node = self .cache [key ]
118- node = self .list .remove (value_node )
119- if node is None :
120- return None
121- self .list .add (node )
92+ node = self .cache [key ]
93+ if self .list .remove (node ):
94+ self .list .add (node )
12295 return node .val
123- self .miss += 1
96+ self .misses += 1
12497 return None
125-
12698 def put (self , key : T , value : U ) -> None :
127- """Sets the value for the input key"""
99+ """Set value for key"""
128100 if key in self .cache :
129- node = self .list .remove (self .cache [key ])
130- if node is None :
131- return
132- node .val = value
133- self .list .add (node )
101+ node = self .cache [key ]
102+ if self .list .remove (node ):
103+ node .val = value
104+ self .list .add (node )
134105 return
135- if self .num_keys >= self .capacity :
136- first_node = self .list .head .next
137- if first_node is None or first_node .key is None :
138- return
139- if self .list .remove (first_node ) is not None :
140- del self .cache [first_node .key ]
141- self .num_keys -= 1
142-
143- new_node = DoubleLinkedListNode (key , value )
144- self .cache [key ] = new_node
145- self .list .add (new_node )
146- self .num_keys += 1
147106
148- @ overload
149- @ classmethod
150- def decorator (
151- cls , size : int = 128
152- ) -> Callable [[ Callable [ P , R ]], Callable [ P , R ]]: ...
107+ if self . size >= self . capacity :
108+ first = self . list . head . next
109+ if first and first . key and self . list . remove ( first ):
110+ del self . cache [ first . key ]
111+ self . size -= 1
153112
154- @overload
155- @classmethod
156- def decorator (cls , func : Callable [P , R ]) -> Callable [P , R ]: ...
113+ new_node : DoubleLinkedListNode [T , U ] = DoubleLinkedListNode (key , value )
114+ self .cache [key ] = new_node
115+ self .list .add (new_node )
116+ self .size += 1
157117
158118 @classmethod
159- def decorator (
160- cls , size : int | Callable [P , R ] = 128
161- ) -> Callable [[Callable [P , R ]], Callable [P , R ]] | Callable [P , R ]:
162- """Decorator version of LRU Cache"""
163- if callable (size ):
164- # Called without parentheses (@LRUCache.decorator)
165- return cls .decorator ()(size )
166-
119+ def decorator (cls , size : int = 128 ) -> Callable [[Callable [P , R ]], Callable [P , R ]]:
120+ """LRU Cache decorator"""
167121 def decorator_func (func : Callable [P , R ]) -> Callable [P , R ]:
168- cache_instance = cls [Any , R ](size ) # type: ignore[valid-type]
122+ cache = cls [Any , R ](size )
169123
170124 @wraps (func )
171125 def wrapper (* args : P .args , ** kwargs : P .kwargs ) -> R :
172- # Create normalized key
173- sorted_kwargs = tuple (sorted (kwargs .items (), key = lambda x : x [0 ]))
174- key = (args , sorted_kwargs )
175- result = cache_instance .get (key )
176- if result is None :
126+ key = (args , tuple (sorted (kwargs .items ())))
127+ if (result := cache .get (key )) is None :
177128 result = func (* args , ** kwargs )
178- cache_instance .put (key , result )
129+ cache .put (key , result )
179130 return result
180131
181- def cache_info () -> LRUCache [Any , R ]: # type: ignore[valid-type]
182- return cache_instance
183-
184- wrapper .cache_info = cache_info # Direct assignment
132+ wrapper .cache_info = lambda : cache # Direct attribute assignment
185133 return wrapper
186-
134+
187135 return decorator_func
188136
189137
190138if __name__ == "__main__" :
191139 import doctest
192-
193140 doctest .testmod ()
0 commit comments