From e5c8b9f836b858045e9ba0b1e781b41c7e05da3a Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Tue, 30 Jun 2026 23:55:02 +0100 Subject: [PATCH 1/3] feat: implement lru cache --- Sprint-2/implement_lru_cache/lru_cache.py | 193 ++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index e69de29..5cfb568 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -0,0 +1,193 @@ +class Node: + """ + A node in a double linked list. + + Each node supports a data, previous, and next + - data: the value + - previous: pointer to the previous node + - next: pointer to the next node + """ + + def __init__(self, data): + self.data = data + self.previous = None + self.next = None + +class LinkedList: + """ + Doubly Linked List + + Supports O(1) operations on insertion at head, removal from tail, and removal from arbitrary node + """ + def __init__(self): + # Head points to the first node in the list + self.head = None + + # Tail points to the last node in the list + self.tail = None + + def is_empty(self): + """Returns True if the list contains no nodes.""" + return self.head is None + + def push_head(self, data): + """ + Insert a new node at the head of the list. + + Time Complexity: O(1) + + Returns: + Node: reference to the inserted node (handle for later removal) + """ + node = Node(data) + + if self.is_empty(): + # If list is empty, head and tail both point to new node + self.head = node + self.tail = node + else: + # Link new node before current head + node.next = self.head + self.head.previous = node + + # Update head pointer + self.head = node + + return node + + def pop_tail(self): + """ + Remove and return the value at the tail of the list. + + Time Complexity: O(1) + + Raises: + ValueError: if the list is empty + """ + + if self.is_empty(): + raise ValueError("List is empty.") + + # store value before removing node + value = self.tail.data + + if self.head == self.tail: + # Only one element in the list + self.head = None + self.tail = None + + else: + # Move tail pointer backwards + self.tail = self.tail.previous + + # Disconnect old tail node + self.tail.next = None + + return value + + def remove(self, node): + """ + Remove a node from the list using its reference. + + Time Complexity: O(1) + + Args: + node (Node): the node to remove + """ + if node.previous: + node.previous.next = node.next + else: + self.head = node.next + + if node.next: + node.next.previous = node.previous + else: + self.tail = node.previous + + node.previous = None + node.next = None + + def move_to_head(self, node): + """ + Move an existing node to the head of the list + + Time complexity: O(1) + """ + + if node is self.head: + return + + self.remove(node) + + if self.is_empty(): + self.head = None + self.tail = None + else: + node.previous = None + node.next = self.head + self.head.previous = node + self.head = node + +class LruCache: + """ + Least Recently Used (LRU) Cache. + + Uses: + - dictionary for O(1) lookup + - doubly linked list for O(1) ordering + + Head = most recently used. + Tail = least recently used. + """ + def __init__(self, limit): + if limit <= 0: + raise ValueError("limit must be positive") + + self.limit = limit + self.cache = {} + self.list = LinkedList() + + def get(self, key): + """ + Return value associated with key. + + Access counts as use. + """ + node = self.cache.get(key) + + if node is None: + return None + + self.list.move_to_head(node) + + return node.data[1] + + def set(self, key, value): + """ + Insert or update a key. + + Update counts as use. + """ + + node = self.cache.get(key) + + # Existing key + if node is not None: + node.data = (key, value) + self.list.move_to_head(node) + return + + # Evict least recently used if full + if len(self.cache) >= self.limit: + evicted_key, _ = self.list.pop_tail() + del self.cache[evicted_key] + + # Insert new entry + node = self.list.push_head((key, value)) + self.cache[key] = node + + def __contains__(self, key): + return key in self.cache + + def __len__(self): + return len(self.cache) From ae84f75f471025007e0ce2d6833476729f047639 Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Wed, 1 Jul 2026 00:58:36 +0100 Subject: [PATCH 2/3] refactor: reuse remove() in pop_tail to avoid duplication --- Sprint-2/implement_lru_cache/lru_cache.py | 26 ++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index 5cfb568..afe1588 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -8,6 +8,8 @@ class Node: - next: pointer to the next node """ + _slots_ = ("data", "previous", "next") + def __init__(self, data): self.data = data self.previous = None @@ -69,19 +71,23 @@ def pop_tail(self): raise ValueError("List is empty.") # store value before removing node - value = self.tail.data + node = self.tail + value = node.data - if self.head == self.tail: - # Only one element in the list - self.head = None - self.tail = None + self.remove(node) + # value = self.tail.data - else: - # Move tail pointer backwards - self.tail = self.tail.previous + # if self.head == self.tail: + # # Only one element in the list + # self.head = None + # self.tail = None + + # else: + # # Move tail pointer backwards + # self.tail = self.tail.previous - # Disconnect old tail node - self.tail.next = None + # # Disconnect old tail node + # self.tail.next = None return value From 79db1e23ae8aff35a792ea0e32ca7f0f0f1e86bc Mon Sep 17 00:00:00 2001 From: JanefrancessC Date: Wed, 1 Jul 2026 00:59:44 +0100 Subject: [PATCH 3/3] refactor: reuse remove() in pop_tail to avoid duplication --- Sprint-2/implement_lru_cache/lru_cache.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Sprint-2/implement_lru_cache/lru_cache.py b/Sprint-2/implement_lru_cache/lru_cache.py index afe1588..53c821c 100644 --- a/Sprint-2/implement_lru_cache/lru_cache.py +++ b/Sprint-2/implement_lru_cache/lru_cache.py @@ -75,19 +75,6 @@ def pop_tail(self): value = node.data self.remove(node) - # value = self.tail.data - - # if self.head == self.tail: - # # Only one element in the list - # self.head = None - # self.tail = None - - # else: - # # Move tail pointer backwards - # self.tail = self.tail.previous - - # # Disconnect old tail node - # self.tail.next = None return value