From 8bdd2a24df6f75908b1a71fabd91445916b7d969 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Tue, 7 Jul 2026 12:19:50 +0100 Subject: [PATCH 1/3] Create LInkdList optimizing performance --- Sprint-2/implement_linked_list/linked_list.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e69de29..1288122 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -0,0 +1,61 @@ +from dataclasses import dataclass +from typing import Any, Optional +# to reduce memory usage we use slots true to tells Python not to create a dynamic __dict__ for every node, +@dataclass(slots=True) +class Node: + value: Any + next: Optional['Node'] = None + previous: Optional['Node'] = None + + +class LinkedList: + def __init__(self) -> None: + self.head: Optional[Node] = None + self.tail: Optional[Node] = None + + def push_head(self, value: Any) -> Node: + new_node = Node(value) + + if not self.head: + self.head = new_node + self.tail = new_node + else: + new_node.next = self.head + self.head.previous = new_node + self.head = new_node + + return new_node + + def pop_tail(self) -> Optional[Any]: + if not self.tail: + return None + + value_to_return = self.tail.value + self.remove(self.tail) + return value_to_return + + def remove(self, node_handle: Optional[Node]) -> None: + if not node_handle: + return + + if node_handle == self.head: + self.head = node_handle.next + if self.head: + self.head.previous = None + else: + self.tail = None + + elif node_handle == self.tail: + self.tail = node_handle.previous + if self.tail: + self.tail.next = None + else: + self.head = None + + else: + if node_handle.previous and node_handle.next: + node_handle.previous.next = node_handle.next + node_handle.next.previous = node_handle.previous + + node_handle.next = None + node_handle.previous = None From ddafbbc137229ea4a1853730c8f06f06227c83de Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Thu, 9 Jul 2026 14:53:54 +0100 Subject: [PATCH 2/3] Clean the script from redundant checks. --- Sprint-2/implement_linked_list/linked_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 1288122..b413acc 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -53,7 +53,8 @@ def remove(self, node_handle: Optional[Node]) -> None: self.head = None else: - if node_handle.previous and node_handle.next: + assert node_handle.previous is not None + assert node_handle.next is not None node_handle.previous.next = node_handle.next node_handle.next.previous = node_handle.previous From c0721a5f3bccd54dba84c5bd45c9827cf140d8a8 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Mon, 13 Jul 2026 11:33:49 +0100 Subject: [PATCH 3/3] feat: implement LRU Cache using LinkedList for ordering --- Sprint-2/implement_lru_cache/lru_cache.py | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index e69de29..d3e47e8 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -0,0 +1,50 @@ +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../implement_linked_list"))) + +from linked_list import LinkedList, Node # type:ignore +from typing import Any + +class LruCache: + def __init__(self, limit: int): + if limit <= 0: + raise ValueError("Limit must be greater than 0") + self.capacity = limit + # maps key + self.cache: dict[Any, Node] = {} + # tracking the usage order head first tail last. + self.list = LinkedList() + + def get(self, key: Any) -> Any: + # If key not exist return None + if key not in self.cache: + return None + + node_handle = self.cache[key] + + # we get the value to return + _, value = node_handle.value + + # we use remove() and push_head() to remove it from the position to the head + self.list.remove(node_handle) + self.cache[key] = self.list.push_head((key, value)) + + return value + + def set(self, key: Any, value: Any) -> None: + if key in self.cache: + self.list.remove(self.cache[key]) + # we do this because the position and value about to change. + del self.cache[key] + + # we call pop_tail() to cut off the tail + elif len(self.cache) >= self.capacity: + # return the value stored in the tail node + oldest_item = self.list.pop_tail() + if oldest_item: + oldest_key, _ = oldest_item + del self.cache[oldest_key] + + ## place the new items at the head of stack and update and return the object Node. + new_node = self.list.push_head((key, value)) + self.cache[key] = new_node