Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Sprint-2/implement_linked_list/CHANGES-MADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Changes Made: Doubly Linked List Implementation

## 1. Core Logic (`linked_list.py`)
- **Node Class**: Created a `Node` class to act as the building block of the list. Each node stores a `value` and has pointers for `next` and `previous`, allowing for bi-directional traversal.
- **LinkedList Class**:
- Implemented `push_head(value)`: Adds a new node to the beginning of the list in **O(1)** time.
- Implemented `pop_tail()`: Removes and returns the value from the end of the list in **O(1)** time. Includes logic for empty lists and single-node lists.
- Implemented `remove(node)`: Removes a specific node from the list and re-wires the surrounding nodes' pointers. Handles edge cases where the node is the current `head` or `tail`.

## 2. Testing & Verification (`linked_list_test.py`)
- **Middle Removal**: Added `test_remove_middle` to verify that when a node in the center of the list is removed, the nodes ahead and behind it correctly link to each other.
- **Edge Case - Empty Pop**: Added `test_pop_empty_list` to ensure the program returns `None` rather than crashing when attempting to remove from an empty list.
- **Edge Case - Null Removal**: Added `test_remove_none` to verify the `remove` method handles `None` inputs gracefully.

## 3. Technical Trade-offs
- **Space vs. Time**: Used a Doubly Linked List structure, trading slightly more memory (for the `previous` pointer) to achieve faster removal and tail-access times **`O(1)`**.
69 changes: 69 additions & 0 deletions Sprint-2/implement_linked_list/linked_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from typing import Any, Optional

class Node:
"""
Represents a single person in line.
Stores a value and links to the people ahead and behind.
"""
__slots__ = ['value', 'next', 'previous']

def __init__(self, value: Any):
self.value = value
self.next: Optional['Node'] = None
self.previous: Optional['Node'] = None


class LinkedList:
"""
Manages the line by keeping track of the Head and the Tail.
"""
def __init__(self):
self.head: Optional[Node] = None
self.tail: Optional[Node] = None

def push_head(self, value: Any) -> Node:
"""Adds a new node to the front of the list."""
new_node = Node(value)

if self.head is None:
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]:
"""Removes the last node and returns its value."""

if self.tail is None:
return None

value_to_return = self.tail.value

self.remove(self.tail)

return value_to_return

def remove(self, node: Optional[Node]) -> None:
"""Removes a specific node from anywhere in the list."""
if node is None:
return

if node == self.head:
self.head = node.next

if node == self.tail:
self.tail = node.previous

if node.previous is not None:
node.previous.next = node.next

if node.next is not None:
node.next.previous = node.previous

node.next = None
node.previous = None
28 changes: 28 additions & 0 deletions Sprint-2/implement_linked_list/linked_list_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ def test_remove_tail(self):
self.assertIsNone(b.next)
self.assertIsNone(b.previous)

def test_remove_middle(self):
l = LinkedList()
l.push_head("tail_node")
middle = l.push_head("middle_node")
l.push_head("head_node")

l.remove(middle)

self.assertEqual(l.head.value, "head_node")
self.assertEqual(l.tail.value, "tail_node")
self.assertEqual(l.head.next.value, "tail_node")
self.assertEqual(l.tail.previous.value, "head_node")

def test_pop_empty_list(self):
l = LinkedList()
# If the list is empty, popping the tail should return None
# instead of crashing the program with an error.
result = l.pop_tail()

self.assertIsNone(result)

def test_remove_none(self):
l = LinkedList()
l.push_head("a")
# This should just do nothing and not crash
l.remove(None)

self.assertEqual(l.pop_tail(), "a")

if __name__ == "__main__":
unittest.main()
Loading