From 7552c4ec48048d841a85eda5b39cb2f66304d199 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 17:10:53 +0530 Subject: [PATCH 1/3] Add cycle detection and removal in linked list --- .../cycle_detection_and_removal.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 data_structures/linked_list/cycle_detection_and_removal.py diff --git a/data_structures/linked_list/cycle_detection_and_removal.py b/data_structures/linked_list/cycle_detection_and_removal.py new file mode 100644 index 000000000000..d980291676bc --- /dev/null +++ b/data_structures/linked_list/cycle_detection_and_removal.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +from typing import Any + + +class ContainsLoopError(Exception): + pass + + +class Node: + def __init__(self, data: Any) -> None: + self.data: Any = data + self.next_node: Node | None = None + + def __iter__(self): + node = self + visited = [] + while node: + if node in visited: + raise ContainsLoopError + visited.append(node) + yield node.data + node = node.next_node + + @property + def has_loop(self) -> bool: + """ + A loop is when the exact same Node appears more than once in a linked list. + >>> root_node = Node(1) + >>> root_node.next_node = Node(2) + >>> root_node.next_node.next_node = Node(3) + >>> root_node.next_node.next_node.next_node = Node(4) + >>> root_node.has_loop + False + >>> root_node.next_node.next_node.next_node = root_node.next_node + >>> root_node.has_loop + True + """ + try: + list(self) + return False + except ContainsLoopError: + return True + + def remove_loop(self) -> None: + """ + Removes the loop in the linked list if one exists. + + >>> root_node = Node(1) + >>> root_node.next_node = Node(2) + >>> root_node.next_node.next_node = Node(3) + >>> root_node.next_node.next_node.next_node = Node(4) + >>> root_node.remove_loop() + >>> root_node.has_loop + False + + >>> root_node = Node(1) + >>> root_node.next_node = Node(2) + >>> root_node.next_node.next_node = Node(3) + >>> root_node.next_node.next_node.next_node = Node(4) + >>> root_node.next_node.next_node.next_node = root_node.next_node + >>> root_node.remove_loop() + >>> root_node.has_loop + False + + >>> root_node = Node(5) + >>> root_node.next_node = Node(6) + >>> root_node.next_node.next_node = Node(5) + >>> root_node.next_node.next_node.next_node = Node(6) + >>> root_node.remove_loop() + >>> root_node.has_loop + False + """ + if self.has_loop: + slow_ptr = self + fast_ptr = self + while slow_ptr and fast_ptr and fast_ptr.next_node: + slow_ptr = slow_ptr.next_node + fast_ptr = fast_ptr.next_node.next_node + if slow_ptr == fast_ptr: + break + + # Move one pointer to the head of the linked list + slow_ptr = self + while slow_ptr.next_node != fast_ptr.next_node: + slow_ptr = slow_ptr.next_node + fast_ptr = fast_ptr.next_node + + # Remove the loop + fast_ptr.next_node = None + + +if __name__ == "__main__": + root_node = Node(1) + root_node.next_node = Node(2) + root_node.next_node.next_node = Node(3) + root_node.next_node.next_node.next_node = Node(4) + print(root_node.has_loop) # False + root_node.next_node.next_node.next_node = root_node.next_node + print(root_node.has_loop) # True + + # Remove the loop + root_node.remove_loop() + print(root_node.has_loop) # False + + root_node = Node(5) + root_node.next_node = Node(6) + root_node.next_node.next_node = Node(5) + root_node.next_node.next_node.next_node = Node(6) + print(root_node.has_loop) # False + + # Remove the loop + root_node.remove_loop() + print(root_node.has_loop) # False + + root_node = Node(1) + print(root_node.has_loop) # False From d435d805c0eedee2986d4c3b447e090a3f5bd9ab Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 17:13:25 +0530 Subject: [PATCH 2/3] Add doctest --- data_structures/linked_list/cycle_detection_and_removal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/data_structures/linked_list/cycle_detection_and_removal.py b/data_structures/linked_list/cycle_detection_and_removal.py index d980291676bc..a94aeb736bc5 100644 --- a/data_structures/linked_list/cycle_detection_and_removal.py +++ b/data_structures/linked_list/cycle_detection_and_removal.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import Any +import doctest class ContainsLoopError(Exception): @@ -91,6 +92,7 @@ def remove_loop(self) -> None: if __name__ == "__main__": + doctest.testmod() root_node = Node(1) root_node.next_node = Node(2) root_node.next_node.next_node = Node(3) From 32d98cfed900d80bc08b8698691c388bbf7c43e4 Mon Sep 17 00:00:00 2001 From: Hardik Pawar Date: Sun, 1 Oct 2023 17:14:38 +0530 Subject: [PATCH 3/3] Add wiki url --- data_structures/linked_list/cycle_detection_and_removal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data_structures/linked_list/cycle_detection_and_removal.py b/data_structures/linked_list/cycle_detection_and_removal.py index a94aeb736bc5..afb10d2c2f39 100644 --- a/data_structures/linked_list/cycle_detection_and_removal.py +++ b/data_structures/linked_list/cycle_detection_and_removal.py @@ -46,6 +46,7 @@ def has_loop(self) -> bool: def remove_loop(self) -> None: """ Removes the loop in the linked list if one exists. + Wikipedia Reference: [Linked list](https://en.wikipedia.org/wiki/Linked_list) >>> root_node = Node(1) >>> root_node.next_node = Node(2)