Skip to content
Merged
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
81 changes: 60 additions & 21 deletions ds/linked_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


##########################
Expand All @@ -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_

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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
Expand Down Expand Up @@ -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 ###
Expand All @@ -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_
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
"""
Expand All @@ -469,11 +489,11 @@ def __setitem__(self, index: int, val: int) -> None:
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
Expand Down Expand Up @@ -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
62 changes: 47 additions & 15 deletions tests/test_data_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)):
Expand Down Expand Up @@ -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():
"""
Expand All @@ -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()
Expand All @@ -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)):
Expand Down Expand Up @@ -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():
"""
Expand Down Expand Up @@ -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()
Expand Down