-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlru_cache.py
More file actions
86 lines (77 loc) · 2.81 KB
/
lru_cache.py
File metadata and controls
86 lines (77 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# lru_cache.py
import time
import threading
from node import DLinkedNode
class LRUCache:
def __init__(self, capacity: int, default_ttl: float = None):
"""
capacity: max number of entries
default_ttl: default time-to-live (seconds) for each entry
"""
self.capacity = capacity
self.default_ttl = default_ttl
self.cache = {} # key -> DLinkedNode
# Dummy head/tail for easy list operations
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.lock = threading.RLock()
def _add_node(self, node: DLinkedNode):
"""Insert node right after head."""
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def _remove_node(self, node: DLinkedNode):
"""Remove node from list."""
prev = node.prev
nxt = node.next
prev.next = nxt
nxt.prev = prev
def _move_to_head(self, node: DLinkedNode):
"""Move an accessed node to the front (most recently used)."""
self._remove_node(node)
self._add_node(node)
def _pop_tail(self) -> DLinkedNode:
"""Pop the least recently used node."""
node = self.tail.prev
self._remove_node(node)
return node
def _is_expired(self, node: DLinkedNode) -> bool:
return node.expiry is not None and node.expiry < time.time()
def get(self, key):
"""Retrieve value and mark as recently used, or None if missing/expired."""
with self.lock:
node = self.cache.get(key)
if not node:
return None
if self._is_expired(node):
# Evict expired entry
self._remove_node(node)
del self.cache[key]
return None
self._move_to_head(node)
return node.value
def put(self, key, value, ttl: float = None):
"""Insert or update key with optional TTL."""
with self.lock:
node = self.cache.get(key)
expiry = None
effective_ttl = ttl if ttl is not None else self.default_ttl
if effective_ttl is not None:
expiry = time.time() + effective_ttl
if node:
# Update existing
node.value = value
node.expiry = expiry
self._move_to_head(node)
else:
# Insert new
new_node = DLinkedNode(key, value, expiry)
self.cache[key] = new_node
self._add_node(new_node)
if len(self.cache) > self.capacity:
# Evict LRU
tail = self._pop_tail()
del self.cache[tail.key]