From 74ebe6d9ed6951226f46330917e91c018ee204b3 Mon Sep 17 00:00:00 2001 From: Mikiyas-STP Date: Wed, 15 Oct 2025 17:43:00 +0100 Subject: [PATCH 1/5] i created a node class giving it a key to evict it when needed --- Sprint-2/implement_lru_cache/lru_cache.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index e69de29..6edc43b 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -0,0 +1,7 @@ +#a node class is created with key,value... key is used to evict a specific char +class Node: + def __init__(self, key, value): + self.key = key + self.value = value + self.previous = None + self.next = None \ No newline at end of file From 866b1762465668cbf45d0f2179177c3296564745 Mon Sep 17 00:00:00 2001 From: Mikiyas-STP Date: Wed, 15 Oct 2025 17:52:59 +0100 Subject: [PATCH 2/5] implementing the same concept of double linked list to add/remove node --- Sprint-2/implement_lru_cache/lru_cache.py | 32 ++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index 6edc43b..d6b8e78 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -4,4 +4,34 @@ def __init__(self, key, value): self.key = key self.value = value self.previous = None - self.next = None \ No newline at end of file + self.next = None + +#next step i define helper functions to add and remove node : +#I use the same concept of the previous project of the linked list +class DoublyLinkedList: + def __init__(self): + self.head = Node(None, None) + self.tail = Node(None, None) + self.head.next = self.tail #this is most recently used + self.tail.previous = self.head #this is the least recently used + + def _add_to_head(self, node): + node.previous = self.head + node.next = self.head.next + self.head.next.previous = node + self.head.next = node + + def _remove_node(self, node): + prev = node.previous + nxt = node.next + prev.next = nxt + nxt.previous = prev + + def _move_to_head(self, node): + self._remove_node(node) + self._add_to_head(node) + + def _pop_tail(self): + node = self.tail.previous + self._remove_node(node) + return node \ No newline at end of file From 825e5299949fbb7f8cca2384ad146d2359e46db6 Mon Sep 17 00:00:00 2001 From: Mikiyas-STP Date: Wed, 15 Oct 2025 17:55:57 +0100 Subject: [PATCH 3/5] lru cache class defining all the logic to check for the cache and evict the LRU least recently used if the limit reaches --- Sprint-2/implement_lru_cache/lru_cache.py | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index d6b8e78..be942c1 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -34,4 +34,36 @@ def _move_to_head(self, node): def _pop_tail(self): node = self.tail.previous self._remove_node(node) - return node \ No newline at end of file + return node + + +#Lru class for the logic +class LruCache: + def __init__(self, limit): + if limit <= 0: + raise ValueError("Cache limit must be greater than zero") + self.limit = limit + self.cache = {} #key->Node + self.dll = DoublyLinkedList() + def get(self, key): + node = self.cache.get(key) + if not node: + return None + # Move the node to the head (most recently used) + self.dll._move_to_head(node) + return node.value + def set(self, key, value): + node = self.cache.get(key) + if node: + # Update existing node and move it to head + node.value = value + self.dll._move_to_head(node) + else: + # Create a new node + new_node = Node(key, value) + self.cache[key] = new_node + self.dll._add_to_head(new_node) + if len(self.cache) > self.limit: + # Remove least recently used node + tail = self.dll._pop_tail() + del self.cache[tail.key] From 04f4933906ce135b48250ee59980e5caefd963f6 Mon Sep 17 00:00:00 2001 From: Mikiyas-STP Date: Wed, 15 Oct 2025 18:25:49 +0100 Subject: [PATCH 4/5] better readable forme --- Sprint-2/implement_lru_cache/lru_cache.py | 40 +++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index be942c1..e039230 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -15,55 +15,55 @@ def __init__(self): self.head.next = self.tail #this is most recently used self.tail.previous = self.head #this is the least recently used - def _add_to_head(self, node): + def add_to_head(self, node): node.previous = self.head node.next = self.head.next self.head.next.previous = node self.head.next = node - def _remove_node(self, node): + def remove_node(self, node): prev = node.previous nxt = node.next prev.next = nxt nxt.previous = prev - def _move_to_head(self, node): - self._remove_node(node) - self._add_to_head(node) + def move_to_head(self, node): + self.remove_node(node) + self.add_to_head(node) - def _pop_tail(self): + def pop_tail(self): node = self.tail.previous - self._remove_node(node) + self.remove_node(node) return node #Lru class for the logic class LruCache: - def __init__(self, limit): + def __init__(self, limit): #constructing if limit <= 0: - raise ValueError("Cache limit must be greater than zero") - self.limit = limit - self.cache = {} #key->Node - self.dll = DoublyLinkedList() - def get(self, key): + raise ValueError("Cache limit must be greater than zero") #b/c it is not logical to have a cache with negative or 0 capacity + self.limit = limit #to tell the limit of our cache + self.cache = {} #empty cache with key->node + self.dll = DoublyLinkedList() #i set the helper method as dll + def get(self, key): #Retrieve value and mark as recently used node = self.cache.get(key) if not node: return None # Move the node to the head (most recently used) - self.dll._move_to_head(node) + self.dll.move_to_head(node) return node.value - def set(self, key, value): + def set(self, key, value): #Insert/update value and handle eviction node = self.cache.get(key) if node: - # Update existing node and move it to head + #Update existing node and move it to head node.value = value - self.dll._move_to_head(node) + self.dll.move_to_head(node) else: - # Create a new node + #Create a new node new_node = Node(key, value) self.cache[key] = new_node - self.dll._add_to_head(new_node) + self.dll.add_to_head(new_node) if len(self.cache) > self.limit: # Remove least recently used node - tail = self.dll._pop_tail() + tail = self.dll.pop_tail() del self.cache[tail.key] From f9ac74113624e468d43b2872e706e7d508f10d99 Mon Sep 17 00:00:00 2001 From: Mikiyas-STP Date: Wed, 15 Oct 2025 18:31:55 +0100 Subject: [PATCH 5/5] complexity analysis --- Sprint-2/implement_lru_cache/lru_cache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index e039230..326bd97 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -67,3 +67,5 @@ def set(self, key, value): #Insert/update value and handle eviction # Remove least recently used node tail = self.dll.pop_tail() del self.cache[tail.key] +#Time Complexity = O(1) since each operations have the same O(1) complexity +#Total Space complexity is O(n) i.e. One node and one dictionary entry per cached item