From db6cff6f2becbf7f30062f70c23a19b290d3fb64 Mon Sep 17 00:00:00 2001 From: EH225 Date: Wed, 20 Aug 2025 15:26:01 -0400 Subject: [PATCH 1/2] add __add__ and __iadd__ dunder methods to linked list data structures --- ds/linked_list.py | 83 +++++++++++++++++++++++++---------- tests/test_data_structures.py | 62 +++++++++++++++++++------- 2 files changed, 108 insertions(+), 37 deletions(-) diff --git a/ds/linked_list.py b/ds/linked_list.py index 1adfe97..0e6abd0 100644 --- a/ds/linked_list.py +++ b/ds/linked_list.py @@ -4,7 +4,8 @@ details. """ -from typing import Union, Optional, List, Tuple, Iterable +from __future__ import annotations +from typing import Union, Optional, List, Tuple, Iterable, Any ########################## @@ -16,7 +17,7 @@ class ListNode: A singly-linked list node. """ - def __init__(self, val: int, next_=None): + def __init__(self, val: Any, next_: ListNode = None): self.val = val self.next_ = next_ @@ -67,7 +68,7 @@ def get(self, index: int, return_value: bool = True) -> Optional[Union[ListNode, else: return node.val if return_value is True else node - def insert(self, index: int = None, val: int = None) -> None: + def insert(self, index: int = None, val: Any = None) -> None: """ In-place method for adding a new node with a value of val at a given index in the linked list. @@ -107,7 +108,7 @@ def insert(self, index: int = None, val: int = None) -> None: self.n += 1 # Update length of list counter - def append(self, val: int) -> None: + def append(self, val: Any) -> None: """ In-place method for appending a new value to the end of the linked list. This method is the same as using obj.insert(len(obj), val). @@ -152,7 +153,7 @@ def pop(self, index: int = None) -> int: self.n -= 1 # Update length of list counter return ans - def index(self, val: int) -> int: + def index(self, val: Any) -> int: """ Returns the first index where a given input value occurs in the linked list. If the provided value cannot be found, an index error is raised. @@ -209,7 +210,7 @@ def __getitem__(self, index: int) -> ListNode: else: raise IndexError(f"Index={index} is out of range") - def __setitem__(self, index: int, val: int) -> None: + def __setitem__(self, index: int, val: Any) -> None: """ Supports obj[index] = val updates to existing nodes in the linked list. """ @@ -222,11 +223,11 @@ def __setitem__(self, index: int, val: int) -> None: def __iter__(self) -> Optional[ListNode]: """ Add support to allow for iteration e.g. - a = LinkedList() - a.addAtTail(1) - a.addAtTail(2) - a.addAtTail(3) - for node in a: + x = LinkedList() + x.append(1) + x.append(2) + x.append(3) + for node in x: print(node.val) """ current_node = self.head @@ -255,6 +256,25 @@ def __str__(self) -> str: """ return self.__repr__() + def __add__(self, x: LinkedList) -> LinkedList: + """ + Handles the addition operator i.e. list_1 + list_2, creates and returns a new list object. + """ + output_list = LinkedList() # Create a new linked list object + for node in self: # Iterate over all the nodes of this list and add their values + output_list.append(node.val) + for node in x: # Iterate over all the nodes of the other list and add thier values + output_list.append(node.val) + return output_list + + def __iadd__(self, x: LinkedList) -> None: + """ + Handles in-place addition i.e. list_1 += list_2, alters the existing list object. + """ + for node in x: # Iterate over the nodes of the second list + self.append(node.val) # Append the values of those nodes to this list + return self + ########################## ### Doubly Linked List ### @@ -265,7 +285,7 @@ class DoublyListNode: Doubly-linked list node. """ - def __init__(self, val, prev_=None, next_=None): + def __init__(self, val: Any, prev_: DoublyListNode = None, next_: DoublyListNode = None): self.val = val self.prev_ = prev_ self.next_ = next_ @@ -319,7 +339,7 @@ def get(self, index: int, return_value: bool = True): else: return node.val if return_value is True else node - def insert(self, index: int = None, val: int = None) -> None: + def insert(self, index: int = None, val: Any = None) -> None: """ In-place method for adding a new node with a value of val at a given index in the linked list. @@ -363,7 +383,7 @@ def insert(self, index: int = None, val: int = None) -> None: self.n += 1 # Update length of list counter - def append(self, val: int) -> None: + def append(self, val: Any) -> None: """ In-place method for appending a new value to the end of the linked list. This method is the same as using obj.insert(len(obj), val). @@ -410,7 +430,7 @@ def pop(self, index: int = None) -> int: self.n -= 1 # Update length of list counter return ans - def index(self, val: int) -> int: + def index(self, val: Any) -> int: """ Returns the first index where a given input value occurs in the linked list. If the provided value cannot be found, an index error is raised. @@ -456,7 +476,7 @@ def __getitem__(self, index: int) -> Optional[DoublyListNode]: else: raise IndexError(f"Index {index} is out of range") - def __setitem__(self, index: int, val: int) -> None: + def __setitem__(self, index: int, val: Any) -> None: """ Supports obj[index] = val updates to existing nodes in the linked list. """ @@ -465,15 +485,15 @@ def __setitem__(self, index: int, val: int) -> None: raise IndexError(f"Index={index} out of range") else: # Update the value associated with this node node.val = val - +git def __iter__(self) -> Optional[DoublyListNode]: """ Add support to allow for iteration e.g. - a = DoublyLinkedList() - a.addAtTail(1) - a.addAtTail(2) - a.addAtTail(3) - for node in a: + x = DoublyLinkedList() + x.append(1) + x.append(2) + x.append(3) + for node in x: print(node.val) """ current_node = self.head @@ -501,3 +521,22 @@ def __str__(self) -> str: Returns a string representation of the linked list. """ return self.__repr__() + + def __add__(self, x: DoublyLinkedList) -> DoublyLinkedList: + """ + Handles the addition operator i.e. list_1 + list_2, creates and returns a new list object. + """ + output_list = DoublyLinkedList() # Create a new linked list object + for node in self: # Iterate over all the nodes of this list and add their values + output_list.append(node.val) + for node in x: # Iterate over all the nodes of the other list and add thier values + output_list.append(node.val) + return output_list + + def __iadd__(self, x: DoublyLinkedList) -> None: + """ + Handles in-place addition i.e. list_1 += list_2, alters the existing list object. + """ + for node in x: # Iterate over the nodes of the second list + self.append(node.val) # Append the values of those nodes to this list + return self diff --git a/tests/test_data_structures.py b/tests/test_data_structures.py index b5b18f2..5e75eb9 100644 --- a/tests/test_data_structures.py +++ b/tests/test_data_structures.py @@ -22,10 +22,10 @@ def test_LinkedList(): obj.insert(5, 20) with pytest.raises(ValueError): # Test inserting without providing a value - obj.insert(5) + obj.insert(5) with pytest.raises(IndexError): # Test updating a node at a non-existant index - obj[10] = 5 + obj[10] = 5 with pytest.raises(IndexError): # Test popping from an empty list obj.pop() @@ -52,7 +52,7 @@ def test_LinkedList(): assert a == b.val assert obj.index(a) == idx - obj.reverse() # Reverse the order of the elements and run tests + obj.reverse() # Reverse the order of the elements and run tests assert obj.head.val == test_data[-1], "Test for obj.head failed" assert obj.tail.val == test_data[0], "Test for obj.tail failed" for idx, (a, b) in enumerate(zip(test_data[::-1], obj)): @@ -96,6 +96,23 @@ def test_LinkedList(): obj[1] = 5 assert obj[1].val == 5, "Test for set item failed" + a = LinkedList() + a_vals = [1, 2, 3] + for x in a_vals: + a.append(x) + b = LinkedList() + b_vals = [9, 10, 12] + for x in b_vals: + b.append(x) + c = a + b + assert isinstance(c, LinkedList) + for node, check_val in zip(c, a_vals + b_vals): + assert node.val == check_val + a += b + assert isinstance(a, LinkedList) + for node, check_val in zip(c, a_vals + b_vals): + assert node.val == check_val + def test_DoublyLinkedList(): """ @@ -111,10 +128,10 @@ def test_DoublyLinkedList(): obj.insert(5, 20) with pytest.raises(ValueError): # Test inserting without providing a value - obj.insert(5) + obj.insert(5) with pytest.raises(IndexError): # Test updating a node at a non-existant index - obj[10] = 5 + obj[10] = 5 with pytest.raises(IndexError): # Test popping from an empty list obj.pop() @@ -141,7 +158,7 @@ def test_DoublyLinkedList(): assert a == b.val assert obj.index(a) == idx - obj.reverse() # Reverse the order of the elements and run tests + obj.reverse() # Reverse the order of the elements and run tests assert obj.head.val == test_data[-1], "Test for obj.head failed" assert obj.tail.val == test_data[0], "Test for obj.tail failed" for idx, (a, b) in enumerate(zip(test_data[::-1], obj)): @@ -187,6 +204,23 @@ def test_DoublyLinkedList(): obj[1] = 5 assert obj[1].val == 5, "Test for set item failed" + a = DoublyLinkedList() + a_vals = [1, 2, 3] + for x in a_vals: + a.append(x) + b = DoublyLinkedList() + b_vals = [9, 10, 12] + for x in b_vals: + b.append(x) + c = a + b + assert isinstance(c, DoublyLinkedList) + for node, check_val in zip(c, a_vals + b_vals): + assert node.val == check_val + a += b + assert isinstance(a, DoublyLinkedList) + for node, check_val in zip(c, a_vals + b_vals): + assert node.val == check_val + def test_BinaryIndexTree(): """ @@ -322,30 +356,28 @@ def test_BinarySearchTree(): assert obj.find_first_ge(obj.root.val) == obj.root.val, "find_first_ge check failed" obj.print_tree() # Should run without crashing - - obj = BinarySearchTree() assert obj.preOrderTraversal() == [], "Failed empty traversal test" assert obj.inOrderTraversal() == [], "Failed empty traversal test" assert obj.postOrderTraversal() == [], "Failed empty traversal test" assert obj.levelOrderTraversal() == [], "Failed empty traversal test" assert obj.isValidBST() is True, "Failed isValidBST empty tree test" - obj.delete(5) # Delete from a blank tree + obj.delete(5) # Delete from a blank tree assert obj.n == 0 - obj.insert(5) # Add 1 element, then delete it - obj.delete(5) # Delete the root node - assert obj.n == 0 # Check that this ran and the size is recorded as 0 + obj.insert(5) # Add 1 element, then delete it + obj.delete(5) # Delete the root node + assert obj.n == 0 # Check that this ran and the size is recorded as 0 - obj.insert(5) # Add the same element multiple times and make sure the valid BST check still passes + obj.insert(5) # Add the same element multiple times and make sure the valid BST check still passes obj.insert(5) obj.insert(4) obj.insert(4) obj.insert(0) assert obj.isValidBST() is True, "Insertion of duplicate elements validation check failed" - obj.root.val = 50 # This will make the BST no longer valid + obj.root.val = 50 # This will make the BST no longer valid assert obj.isValidBST() is False, "Validation check failed to catch invalid BST" - obj.root.val = -50 # This will make the BST no longer valid + obj.root.val = -50 # This will make the BST no longer valid assert obj.isValidBST() is False, "Validation check failed to catch invalid BST" obj = BinarySearchTree() From 740585e7c011addc8eb92294b289f9616b7b851c Mon Sep 17 00:00:00 2001 From: EH225 Date: Wed, 20 Aug 2025 15:26:35 -0400 Subject: [PATCH 2/2] correct typo --- ds/linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ds/linked_list.py b/ds/linked_list.py index 0e6abd0..3fbb9bf 100644 --- a/ds/linked_list.py +++ b/ds/linked_list.py @@ -485,7 +485,7 @@ def __setitem__(self, index: int, val: Any) -> None: raise IndexError(f"Index={index} out of range") else: # Update the value associated with this node node.val = val -git + def __iter__(self) -> Optional[DoublyListNode]: """ Add support to allow for iteration e.g.