diff --git a/DIRECTORY.md b/DIRECTORY.md index 03044e01084b..f08c31794b1e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -25,7 +25,9 @@ * [Sum Of Subsets](https://github.com/TheAlgorithms/Python/blob/master/backtracking/sum_of_subsets.py) ## Bit Manipulation + * [Binary And Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_and_operator.py) * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) + * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) ## Blockchain * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) diff --git a/data_structures/linked_list/singly_linked_list.py b/data_structures/linked_list/singly_linked_list.py index 1f3e03a31b7e..39b14c520521 100644 --- a/data_structures/linked_list/singly_linked_list.py +++ b/data_structures/linked_list/singly_linked_list.py @@ -9,50 +9,110 @@ def __repr__(self): class LinkedList: def __init__(self): - self.head = None # initialize head to None + self.head = None + + def __iter__(self): + node = self.head + while node: + yield node.data + node = node.next + + def __len__(self) -> int: + """ + Return length of linked list i.e. number of nodes + >>> linked_list = LinkedList() + >>> len(linked_list) + 0 + >>> linked_list.insert_tail("head") + >>> len(linked_list) + 1 + >>> linked_list.insert_head("head") + >>> len(linked_list) + 2 + >>> _ = linked_list.delete_tail() + >>> len(linked_list) + 1 + >>> _ = linked_list.delete_head() + >>> len(linked_list) + 0 + """ + return len(tuple(iter(self))) + + def __repr__(self): + """ + String representation/visualization of a Linked Lists + """ + return "->".join([str(item) for item in self]) + + def __getitem__(self, index): + """ + Indexing Support. Used to get a node at particular position + """ + if index < 0: + raise ValueError("Negative indexes are not yet supported") + for i, node in enumerate(self): + if i == index: + return node.data + + # Used to change the data of a particular node + def __setitem__(self, index, data): + current = self.head + # If list is empty + if current is None: + raise IndexError("The Linked List is empty") + for i in range(index): + if current.next is None: + raise IndexError("list index out of range") + current = current.next + current.data = data def insert_tail(self, data) -> None: + self.insert_nth(len(self), data) + + def insert_head(self, data) -> None: + self.insert_nth(0, data) + + def insert_nth(self, index: int, data) -> None: + if not 0 <= index <= len(self): + raise IndexError("list index out of range") + new_node = Node(data) if self.head is None: - self.insert_head(data) # if this is first node, call insert_head + self.head = new_node + elif index == 0: + new_node.next = self.head # link new_node to head + self.head = new_node else: temp = self.head - while temp.next: # traverse to last node + for _ in range(index - 1): temp = temp.next - temp.next = Node(data) # create node & link to tail - - def insert_head(self, data) -> None: - new_node = Node(data) # create a new node - if self.head: - new_node.next = self.head # link new_node to head - self.head = new_node # make NewNode as head + new_node.next = temp.next + temp.next = new_node def print_list(self) -> None: # print every node data - temp = self.head - while temp: - print(temp.data) - temp = temp.next - - def delete_head(self): # delete from head - temp = self.head - if self.head: - self.head = self.head.next - temp.next = None - return temp + print(self) + + def delete_head(self): + return self.delete_nth(0) def delete_tail(self): # delete from tail - temp = self.head - if self.head: - if self.head.next is None: # if head is the only Node in the Linked List - self.head = None - else: - while temp.next.next: # find the 2nd last element - temp = temp.next - # (2nd last element).next = None and temp = last element - temp.next, temp = None, temp.next - return temp + return self.delete_nth(len(self) - 1) + + def delete_nth(self, index: int = 0): + if not 0 <= index <= len(self) - 1: # test if index is valid + raise IndexError("list index out of range") + delete_node = self.head # default first node + if index == 0: + self.head = self.head.next + else: + temp = self.head + for _ in range(index - 1): + temp = temp.next + delete_node = temp.next + temp.next = temp.next.next + return delete_node.data def is_empty(self) -> bool: - return self.head is None # return True if head is none + return self.head is None def reverse(self): prev = None @@ -70,101 +130,74 @@ def reverse(self): # Return prev in order to put the head at the end self.head = prev - def __repr__(self): # String representation/visualization of a Linked Lists - current = self.head - string_repr = "" - while current: - string_repr += f"{current} --> " - current = current.next - # END represents end of the LinkedList - return string_repr + "END" - # Indexing Support. Used to get a node at particular position - def __getitem__(self, index): - current = self.head +def test_singly_linked_list() -> None: + """ + >>> test_singly_linked_list() + """ + linked_list = LinkedList() + assert linked_list.is_empty() is True + assert str(linked_list) == "" - # If LinkedList is empty - if current is None: - raise IndexError("The Linked List is empty") + try: + linked_list.delete_head() + assert False # This should not happen. + except IndexError: + assert True # This should happen. - # Move Forward 'index' times - for _ in range(index): - # If the LinkedList ends before reaching specified node - if current.next is None: - raise IndexError("Index out of range.") - current = current.next - return current + try: + linked_list.delete_tail() + assert False # This should not happen. + except IndexError: + assert True # This should happen. - # Used to change the data of a particular node - def __setitem__(self, index, data): - current = self.head - # If list is empty - if current is None: - raise IndexError("The Linked List is empty") - for i in range(index): - if current.next is None: - raise IndexError("Index out of range.") - current = current.next - current.data = data + for i in range(10): + assert len(linked_list) == i + linked_list.insert_nth(i, i + 1) + assert str(linked_list) == "->".join(str(i) for i in range(1, 11)) - def __len__(self): - """ - Return length of linked list i.e. number of nodes - >>> linked_list = LinkedList() - >>> len(linked_list) - 0 - >>> linked_list.insert_tail("head") - >>> len(linked_list) - 1 - >>> linked_list.insert_head("head") - >>> len(linked_list) - 2 - >>> _ = linked_list.delete_tail() - >>> len(linked_list) - 1 - >>> _ = linked_list.delete_head() - >>> len(linked_list) - 0 - """ - if not self.head: - return 0 + linked_list.insert_head(0) + linked_list.insert_tail(11) + assert str(linked_list) == "->".join(str(i) for i in range(0, 12)) - count = 0 - cur_node = self.head - while cur_node.next: - count += 1 - cur_node = cur_node.next - return count + 1 + assert linked_list.delete_head() == 0 + assert linked_list.delete_nth(9) == 10 + assert linked_list.delete_tail() == 11 + assert str(linked_list) == "->".join(str(i) for i in range(1, 10)) def main(): - A = LinkedList() - A.insert_head(input("Inserting 1st at head ").strip()) - A.insert_head(input("Inserting 2nd at head ").strip()) + from doctest import testmod + + testmod() + + linked_list = LinkedList() + linked_list.insert_head(input("Inserting 1st at head ").strip()) + linked_list.insert_head(input("Inserting 2nd at head ").strip()) print("\nPrint list:") - A.print_list() - A.insert_tail(input("\nInserting 1st at tail ").strip()) - A.insert_tail(input("Inserting 2nd at tail ").strip()) + linked_list.print_list() + linked_list.insert_tail(input("\nInserting 1st at tail ").strip()) + linked_list.insert_tail(input("Inserting 2nd at tail ").strip()) print("\nPrint list:") - A.print_list() + linked_list.print_list() print("\nDelete head") - A.delete_head() + linked_list.delete_head() print("Delete tail") - A.delete_tail() + linked_list.delete_tail() print("\nPrint list:") - A.print_list() + linked_list.print_list() print("\nReverse linked list") - A.reverse() + linked_list.reverse() print("\nPrint list:") - A.print_list() + linked_list.print_list() print("\nString representation of linked list:") - print(A) + print(linked_list) print("\nReading/changing Node data using indexing:") - print(f"Element at Position 1: {A[1]}") - A[1] = input("Enter New Value: ").strip() + print(f"Element at Position 1: {linked_list[1]}") + linked_list[1] = input("Enter New Value: ").strip() print("New list:") - print(A) - print(f"length of A is : {len(A)}") + print(linked_list) + print(f"length of linked_list is : {len(linked_list)}") if __name__ == "__main__":