Skip to content

Commit 072f368

Browse files
authored
Update lru_cache.py
1 parent ffecd1b commit 072f368

1 file changed

Lines changed: 59 additions & 112 deletions

File tree

other/lru_cache.py

Lines changed: 59 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,23 @@
22

33
from collections.abc import Callable, Hashable
44
from 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

87
if 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

1114
T = TypeVar("T", bound=Hashable)
1215
U = TypeVar("U")
1316
P = ParamSpec("P")
1417
R = 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

2420
class 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

4533
class 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

9071
class 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

190138
if __name__ == "__main__":
191139
import doctest
192-
193140
doctest.testmod()

0 commit comments

Comments
 (0)