From 6c9df3e0ff30f5d132c96e470d05da317da0289b Mon Sep 17 00:00:00 2001 From: RomanSanaye Date: Mon, 12 Oct 2026 23:17:17 +0100 Subject: [PATCH 1/3] Implement doubly linked list operations --- Sprint-2/implement_linked_list/linked_list.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e69de29b..f8188950 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -0,0 +1,83 @@ +# A "Node" is a single building block of our list. +# Each node holds a value and two pointers: 'next' (forward) and 'previous' (backward). +class Node: + def __init__(self, value): + self.value = value + self.next = None + self.previous = None + + +# A Doubly Linked List keeps track of both ends (head and tail). +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def push_head(self, value): + """Add a new item to the front of the list in O(1) time.""" + new_node = Node(value) + + # If the list is empty, head and tail both point to this new node + if self.head is None: + self.head = new_node + self.tail = new_node + else: + # Connect the new node to the current head + new_node.next = self.head + self.head.previous = new_node + # Update the head pointer to be our new node + self.head = new_node + + # We return the node itself so it can be used as a "handle" later + return new_node + + def pop_tail(self): + """Remove and return the item from the end of the list in O(1) time.""" + # If the list is empty, there is nothing to remove + if self.tail is None: + return None + + # Save the value so we can return it at the end + removed_value = self.tail.value + + # If there is only one item left in the list + if self.head == self.tail: + self.head = None + self.tail = None + else: + # Move the tail pointer back to the previous node + self.tail = self.tail.previous + # Cut off the old tail by clearing its 'next' reference + self.tail.next = None + + return removed_value + + def remove(self, node): + """Remove a specific node from the list using its reference in O(1) time.""" + + # Scenario 1: It's the only item in the list + if self.head == self.tail and node == self.head: + self.head = None + self.tail = None + + # Scenario 2: It's the first item (head) + elif node == self.head: + self.head = self.head.next + if self.head is not None: + self.head.previous = None + + # Scenario 3: It's the last item (tail) + elif node == self.tail: + self.tail = self.tail.previous + if self.tail is not None: + self.tail.next = None + + # Scenario 4: It's somewhere safely in the middle + else: + # Bypass the node by gluing its neighbors directly to each other + node.previous.next = node.next + node.next.previous = node.previous + + # Clean up the node's own pointers + node.next = None + node.previous = None \ No newline at end of file From b8086a391c1a2414d43fea0c107eced0b5ff3ba6 Mon Sep 17 00:00:00 2001 From: RomanSanaye Date: Thu, 15 Oct 2026 15:17:28 +0100 Subject: [PATCH 2/3] refactor: optimize Node memory with __slots__ and delegate pop_tail removal logic --- Sprint-2/implement_linked_list/linked_list.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index f8188950..4ee3ad29 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,6 +1,7 @@ # A "Node" is a single building block of our list. # Each node holds a value and two pointers: 'next' (forward) and 'previous' (backward). class Node: + __slots__ = ("value", "next", "previous") def __init__(self, value): self.value = value self.next = None @@ -40,21 +41,14 @@ def pop_tail(self): # Save the value so we can return it at the end removed_value = self.tail.value - # If there is only one item left in the list - if self.head == self.tail: - self.head = None - self.tail = None - else: - # Move the tail pointer back to the previous node - self.tail = self.tail.previous - # Cut off the old tail by clearing its 'next' reference - self.tail.next = None + # Delegate the actual removal logic to the general remove() method (DRY principle) + self.remove(self.tail) return removed_value def remove(self, node): """Remove a specific node from the list using its reference in O(1) time.""" - + # Scenario 1: It's the only item in the list if self.head == self.tail and node == self.head: self.head = None @@ -80,4 +74,4 @@ def remove(self, node): # Clean up the node's own pointers node.next = None - node.previous = None \ No newline at end of file + node.previous = None From ce3cda46a691849ce9fe9d0a2a8a1da580f5fd4c Mon Sep 17 00:00:00 2001 From: RomanSanaye Date: Thu, 15 Oct 2026 15:47:07 +0100 Subject: [PATCH 3/3] refactor: add type hints to Node and LinkedList methods --- Sprint-2/implement_linked_list/linked_list.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 4ee3ad29..c832197c 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,20 +1,21 @@ +from typing import Any, Optional # A "Node" is a single building block of our list. # Each node holds a value and two pointers: 'next' (forward) and 'previous' (backward). class Node: __slots__ = ("value", "next", "previous") - def __init__(self, value): - self.value = value - self.next = None - self.previous = None + def __init__(self, value: Any)-> None: + self.value: Any = value + self.next: Optional[Node] = None + self.previous: Optional[Node] = None # A Doubly Linked List keeps track of both ends (head and tail). class LinkedList: - def __init__(self): - self.head = None - self.tail = None + def __init__(self) -> None: + self.head: Optional[Node] = None + self.tail: Optional[Node] = None - def push_head(self, value): + def push_head(self, value: Any): """Add a new item to the front of the list in O(1) time.""" new_node = Node(value) @@ -32,7 +33,7 @@ def push_head(self, value): # We return the node itself so it can be used as a "handle" later return new_node - def pop_tail(self): + def pop_tail(self) -> Any: """Remove and return the item from the end of the list in O(1) time.""" # If the list is empty, there is nothing to remove if self.tail is None: @@ -46,7 +47,7 @@ def pop_tail(self): return removed_value - def remove(self, node): + def remove(self, node: Node) -> None : """Remove a specific node from the list using its reference in O(1) time.""" # Scenario 1: It's the only item in the list