Skip to content

Commit 34fc829

Browse files
committed
Updating from basic O(n) LRU to full O(1) design with node references and doubly linked list
1 parent c406883 commit 34fc829

File tree

1 file changed

+66
-26
lines changed

1 file changed

+66
-26
lines changed

Sprint-2/implement_lru_cache/lru_cache.py

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,87 @@
1+
#Implementing a doubly linked list to reduce complexity of each operation which was previously O(n) to O(1).
2+
class Node:
3+
def __init__(self, key, value):
4+
self.key = key
5+
self.value = value
6+
self.prev = None
7+
self.next = None
8+
9+
class DoublyLinkedList:
10+
def __init__(self):
11+
self.head = None # MRU
12+
self.tail = None # LRU
13+
14+
def push_head(self, node): #accessing or adding a new key at head- MRU
15+
node.prev = None
16+
node.next = self.head
17+
18+
if self.head:
19+
self.head.prev = node
20+
self.head = node
21+
22+
if self.tail is None:
23+
self.tail = node
24+
25+
def remove(self, node):
26+
if node.prev:
27+
node.prev.next = node.next #previous node connects to the next node.
28+
else:
29+
self.head = node.next
30+
31+
if node.next:
32+
node.next.prev = node.prev
33+
else:
34+
self.tail = node.prev
35+
36+
node.prev = None
37+
node.next = None
38+
39+
def pop_tail(self): #removes to return lru node
40+
if self.tail is None:
41+
return None
42+
node = self.tail
43+
self.remove(node)
44+
return node
45+
46+
47+
148
class LruCache:
249
def __init__(self,limit) -> None:
350
if limit <= 0:
451
raise ValueError("Limit must be greater than zero")
552
self.limit = limit #defines a limit, after which before adding anything in our cache we need to remove it
6-
self.storage = {} #stores data as key value pairs for fast lookups. accessing or updating a key both make it a Most recently used item
7-
self.order = [] #lRU at index 0, MRU at last index position, tracks usage order
53+
self.storage = {} #key(cachekey) and node(key value pair)
54+
self.order = DoublyLinkedList() #self.head is MRU and self.tail is LRU
855
pass
956

1057
#If a key already exists move it to MRU position
11-
def touch (self,key):
12-
if key in self.order:
13-
self.order.remove(key)
14-
self.order.append(key)
58+
def touch (self,node):
59+
self.order.remove(node)
60+
self.order.push_head(node)
1561

1662
# If we want to add or update a key value pair
1763
def set(self, key, value):
1864
if key in self.storage:
19-
self.storage[key] = value #update the value of the key
20-
self.touch(key)
65+
node = self.storage[key] #update the value of the key
66+
node.value = value
67+
self.touch(node)
2168
return
2269

23-
#if we are adding a new key and at capacity
70+
#if we are adding a new key and at our limit
2471
if len(self.storage) >= self.limit:
25-
lru_key =self.order.pop(0)
26-
del self.storage[lru_key]
72+
lru_node =self.order.pop_tail()
73+
del self.storage[lru_node.key]
2774

28-
#Add a new key and set it as MRU
29-
self.storage[key]= value
30-
self.touch(key)
75+
#insert a new node
76+
new_node = Node(key, value)
77+
self.order.push_head(new_node)
78+
self.storage[key] = new_node
79+
3180

3281
def get(self,key):
3382
if key not in self.storage:
3483
return None
35-
#if it exist set it as used
36-
self.touch(key)
37-
return self.storage[key]
84+
node = self.storage[key]
85+
self.touch(node)
86+
return node.value
3887

39-
40-
cache = LruCache(2)
41-
cache.set("a", 1) # store = {a:1}, order = [a]
42-
cache.set("b", 2) # store = {a:1,b:2}, order = [a,b]
43-
print(cache.get("a")) # should print 1
44-
cache.get("a") # returns 1, order = [b,a] (a is now MRU)
45-
cache.set("c", 3) # capacity reached, evict LRU = b
46-
cache.get("b")
47-
print(cache.order) # store = {a:1,c:3}, order = [a,c]

0 commit comments

Comments
 (0)