diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e69de29..b413acc 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -0,0 +1,62 @@ +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: + 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 + + node_handle.next = None + node_handle.previous = None 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