From d38ea40f608fa8f206569fa8985e6d92d097cc6f Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 04:10:21 +0100 Subject: [PATCH 01/11] initial singly linked list --- Sprint-2/implement_linked_list/linked_list.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e69de29b..9f6b0639 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -0,0 +1,30 @@ +class LinkedList: + def __init__(self, head=None, tail=None): + self.head = head + self.tail = tail + self.size = 0 + if self.head is not None: + self.size += 1 + if self.tail is not None and self.head != self.tail: + self.size += 1 + + def push_head(self, new_head): + new_head.next = self.head + self.head = new_head + self.size += 1 + return self.head + + def pop_tail(self): + node_before_last = self.head + for i in range(self.size - 2): + node_before_last = node_before_last.next + tail = node_before_last.next + self.tail = node_before_last + return tail + +# size 5 +# Node(12) -> Node(2) -> Node(22) -> Node(43) -> Node(4) +# +# +# + From cc892d68c0468d94d507e50ee725261f20a31da1 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 04:20:08 +0100 Subject: [PATCH 02/11] doubly linked list, push_head need doubly linked list to get O(1) insert at head, delete at tail and to delete a given node. push_head works. --- Sprint-2/implement_linked_list/linked_list.py | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 9f6b0639..e0119171 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,26 +1,34 @@ +class Node: + def __init__(self, value): + self.value = value + self.prev = None + self.next = None + self.removed = False + + class LinkedList: - def __init__(self, head=None, tail=None): - self.head = head - self.tail = tail - self.size = 0 - if self.head is not None: - self.size += 1 - if self.tail is not None and self.head != self.tail: - self.size += 1 + def __init__(self): + self.head = None + self.tail = None - def push_head(self, new_head): - new_head.next = self.head - self.head = new_head - self.size += 1 - return self.head + def push_head(self, value): + node = Node(value) + node.next = self.head + + # set current head's previous node + # to be the new head node + if self.head is not None: + self.head.prev = node + else: + self.tail = node + self.head = node + # returns the node so it can be removed by the remove method + return node - def pop_tail(self): - node_before_last = self.head - for i in range(self.size - 2): - node_before_last = node_before_last.next - tail = node_before_last.next - self.tail = node_before_last - return tail +ll = LinkedList() +ll.push_head(12) +print(ll.head) # a .Node object at 0x... +print(ll.head.value) # 12 . # size 5 # Node(12) -> Node(2) -> Node(22) -> Node(43) -> Node(4) From 6e730a1ac498b043cdd3b14c77a2c4f4d28f37b2 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 04:58:01 +0100 Subject: [PATCH 03/11] pop tail works. --- Sprint-2/implement_linked_list/linked_list.py | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e0119171..a07dc424 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -20,19 +20,66 @@ def push_head(self, value): if self.head is not None: self.head.prev = node else: - self.tail = node + # if no previous head, make current head also the tail + self.tail = node self.head = node # returns the node so it can be removed by the remove method return node + def pop_tail(self): + if self.tail is None: + raise IndexError("Cannot pop from empty list") + + # get current tail + current_tail = self.tail + # if currnet tail has a previous, set its next to be None + # if current tail has a previous, set that to be the new tail + if current_tail.prev is not None: + current_tail.prev.next = None + self.tail = current_tail.prev + else: + # if current tail has no previous, it must also be the head + # if it's deleted, list is empty + self.head = None + self.tail = None + + # delink current tail's prev, and set removed to True + # so that putting it inside of remove won't try to remove something + # that's not in the list + current_tail.prev = None + current_tail.removed = True + return current_tail + + + ll = LinkedList() +ll.push_head(2) ll.push_head(12) -print(ll.head) # a .Node object at 0x... -print(ll.head.value) # 12 . + +if ll.tail is not None: + print(ll.tail.value) +else: + print("list is empty") + +ll.pop_tail() + +if ll.tail is not None: + print(ll.tail.value) +else: + print("list is empty") + +ll.pop_tail() + +if ll.tail is not None: + print(ll.tail.value) +else: + print("list is empty") + # size 5 # Node(12) -> Node(2) -> Node(22) -> Node(43) -> Node(4) +# Node(12) -> Node(2) +# Node(12) head: Node(12) tail: Node(12) # -# -# + From fd29b1aa4b517ab14d4062ba5e5007e402c1ea3c Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 05:18:36 +0100 Subject: [PATCH 04/11] remove works --- Sprint-2/implement_linked_list/linked_list.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index a07dc424..a322adb2 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -50,30 +50,37 @@ def pop_tail(self): current_tail.removed = True return current_tail + def remove(self, node): + # check if node is a removed node + if node.removed: + return + # delink the node + if node.prev is not None: + node.prev.next = node.next -ll = LinkedList() -ll.push_head(2) -ll.push_head(12) + if node.next: + node.next.prev = node.prev + + # this is necessary to prevent this node from + # refering other removed nodes + node.prev = None + node.next = None + + node.removed = True -if ll.tail is not None: - print(ll.tail.value) -else: - print("list is empty") -ll.pop_tail() -if ll.tail is not None: - print(ll.tail.value) -else: - print("list is empty") -ll.pop_tail() +ll = LinkedList() +tail = ll.push_head(2) +middle = ll.push_head(12) +head = ll.push_head(22) + +print(head.next.value) +ll.remove(middle) +print(tail.prev.value) -if ll.tail is not None: - print(ll.tail.value) -else: - print("list is empty") # size 5 From 387f68884a7f24268d7f51cc38ff24188b31e9f5 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 05:26:50 +0100 Subject: [PATCH 05/11] use owner instead of removed flag for more robust protection against removing invalid nodes --- Sprint-2/implement_linked_list/linked_list.py | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index a322adb2..031dff93 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,9 +1,12 @@ class Node: - def __init__(self, value): + def __init__(self, value, owner): self.value = value self.prev = None self.next = None - self.removed = False + # this links a node to a specific list + # prevents attempt to remove a node from a list + # it does not belong to. + self.owner = owner class LinkedList: @@ -12,7 +15,7 @@ def __init__(self): self.tail = None def push_head(self, value): - node = Node(value) + node = Node(value, self) # makes this list the owner node.next = self.head # set current head's previous node @@ -47,14 +50,17 @@ def pop_tail(self): # so that putting it inside of remove won't try to remove something # that's not in the list current_tail.prev = None - current_tail.removed = True + current_tail.owner = None return current_tail def remove(self, node): # check if node is a removed node - if node.removed: + if node.owner is None: return + if node.owner is not self: + raise ValueError("This node does not belong to this list") + # delink the node if node.prev is not None: node.prev.next = node.next @@ -67,26 +73,6 @@ def remove(self, node): node.prev = None node.next = None - node.removed = True - - - - -ll = LinkedList() -tail = ll.push_head(2) -middle = ll.push_head(12) -head = ll.push_head(22) - -print(head.next.value) -ll.remove(middle) -print(tail.prev.value) - - - -# size 5 -# Node(12) -> Node(2) -> Node(22) -> Node(43) -> Node(4) -# Node(12) -> Node(2) -# Node(12) head: Node(12) tail: Node(12) -# + node.owner = None From d632e9c2069e21a89fb989a7abcb66e64abafd21 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 05:35:24 +0100 Subject: [PATCH 06/11] fix remove: update head and tail --- Sprint-2/implement_linked_list/linked_list.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 031dff93..ef18938d 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -64,9 +64,16 @@ def remove(self, node): # delink the node if node.prev is not None: node.prev.next = node.next + else: + # removing head + self.head = node.next - if node.next: + + if node.next is not None: node.next.prev = node.prev + else: + # removing tail + self.tail = node.prev # this is necessary to prevent this node from # refering other removed nodes From 1dcdee0fde5d30429a90e7c3562f748224fd4e9e Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 05:36:08 +0100 Subject: [PATCH 07/11] pop should return value, not node --- Sprint-2/implement_linked_list/linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index ef18938d..1fa0fe5a 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -51,7 +51,7 @@ def pop_tail(self): # that's not in the list current_tail.prev = None current_tail.owner = None - return current_tail + return current_tail.value def remove(self, node): # check if node is a removed node From bdde2540116a4cdea55d74e56a0468bc91fcc278 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 05:37:20 +0100 Subject: [PATCH 08/11] use remove in pop tail instead of duplicating logic --- Sprint-2/implement_linked_list/linked_list.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 1fa0fe5a..cf230539 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -35,22 +35,8 @@ def pop_tail(self): # get current tail current_tail = self.tail - # if currnet tail has a previous, set its next to be None - # if current tail has a previous, set that to be the new tail - if current_tail.prev is not None: - current_tail.prev.next = None - self.tail = current_tail.prev - else: - # if current tail has no previous, it must also be the head - # if it's deleted, list is empty - self.head = None - self.tail = None - - # delink current tail's prev, and set removed to True - # so that putting it inside of remove won't try to remove something - # that's not in the list - current_tail.prev = None - current_tail.owner = None + self.remove(current_tail) + return current_tail.value def remove(self, node): From 0cfc2c2d8dfb7d919b37c735f4e2cdacc8d30493 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 05:41:44 +0100 Subject: [PATCH 09/11] clarify reason to remove links --- Sprint-2/implement_linked_list/linked_list.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index cf230539..56d502f5 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -33,10 +33,9 @@ def pop_tail(self): if self.tail is None: raise IndexError("Cannot pop from empty list") - # get current tail current_tail = self.tail self.remove(current_tail) - + return current_tail.value def remove(self, node): @@ -61,8 +60,10 @@ def remove(self, node): # removing tail self.tail = node.prev - # this is necessary to prevent this node from - # refering other removed nodes + # removed node should not keep refereces into the list + # otherwise it could leave other nodes alive. + # could mislead users of the removed node about the state + # of the list node.prev = None node.next = None From fe6338ea88eb27114f56d4085d1d1b72b1c8771a Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 14:42:50 +0100 Subject: [PATCH 10/11] use __slots__ to improve efficiency --- Sprint-2/implement_linked_list/linked_list.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 56d502f5..abd6be2e 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,4 +1,6 @@ class Node: + __slots__ = ("value", "prev", "next", "owner") + def __init__(self, value, owner): self.value = value self.prev = None From 4f4961df776cf3fbf048326c2de2d3e1ef986486 Mon Sep 17 00:00:00 2001 From: Raihan Sharif Date: Sat, 24 Oct 2026 16:13:43 +0100 Subject: [PATCH 11/11] add type hints --- Sprint-2/implement_linked_list/linked_list.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index abd6be2e..40803442 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,22 +1,28 @@ -class Node: +from __future__ import annotations +from typing import Generic, Optional, TypeVar + +T = TypeVar("T") + + +class Node(Generic[T]): __slots__ = ("value", "prev", "next", "owner") - def __init__(self, value, owner): - self.value = value - self.prev = None - self.next = None + def __init__(self, value: T, owner: LinkedList[T]): + self.value: T = value + self.prev: Optional[Node[T]] = None + self.next: Optional[Node[T]] = None # this links a node to a specific list # prevents attempt to remove a node from a list # it does not belong to. - self.owner = owner + self.owner: Optional[LinkedList[T]] = owner -class LinkedList: - def __init__(self): - self.head = None - self.tail = None +class LinkedList(Generic[T]): + def __init__(self) -> None: + self.head: Optional[Node[T]] = None + self.tail: Optional[Node[T]] = None - def push_head(self, value): + def push_head(self, value: T) -> Node[T]: node = Node(value, self) # makes this list the owner node.next = self.head @@ -31,16 +37,16 @@ def push_head(self, value): # returns the node so it can be removed by the remove method return node - def pop_tail(self): + def pop_tail(self) -> T: if self.tail is None: raise IndexError("Cannot pop from empty list") - current_tail = self.tail + current_tail = self.tail # current_tail's type is inferred self.remove(current_tail) return current_tail.value - def remove(self, node): + def remove(self, node: Node[T]) -> None: # check if node is a removed node if node.owner is None: return