From 3a2c647d70e7bbc62e950de826b9afe7ce16e7ac Mon Sep 17 00:00:00 2001 From: Aqib Javid Bhat Date: Mon, 23 Oct 2023 14:45:21 +0530 Subject: [PATCH 1/4] Add Floyd's Cycle Detection Algorithm --- .../linked_list/floyds_cycle_detection.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 data_structures/linked_list/floyds_cycle_detection.py diff --git a/data_structures/linked_list/floyds_cycle_detection.py b/data_structures/linked_list/floyds_cycle_detection.py new file mode 100644 index 000000000000..18ca43b912ae --- /dev/null +++ b/data_structures/linked_list/floyds_cycle_detection.py @@ -0,0 +1,136 @@ +""" +Floyd's cycle detection algorithm is a popular algorithm used to detect cycles +in a linked list. It uses two pointers, a slow pointer and a fast pointer, +to traverse the linked list. The slow pointer moves one node at a time while the fast +pointer moves two nodes at a time. If there is a cycle in the linked list, +the fast pointer will eventually catch up to the slow pointer and they will +meet at the same node. If there is no cycle, the fast pointer will reach the end of +the linked list and the algorithm will terminate. + +For more information: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare +""" + +from collections.abc import Iterator +from dataclasses import dataclass +from typing import Any, Self + + +@dataclass +class Node: + """ + A class representing a node in a singly linked list. + """ + + data: Any + next_node: Self | None = None + + +@dataclass +class LinkedList: + """ + A class representing a singly linked list. + """ + + head: Node | None = None + + def __iter__(self) -> Iterator: + """ + Iterates through the linked list. + + Returns: + Iterator: An iterator over the linked list. + + Examples: + >>> linked_list = LinkedList() + >>> list(linked_list) + [] + >>> linked_list.add_node(1) + >>> tuple(linked_list) + (1,) + """ + node = self.head + while node: + yield node.data + node = node.next_node + + def add_node(self, data: Any) -> None: + """ + Adds a new node to the end of the linked list. + + Args: + data (Any): The data to be stored in the new node. + """ + new_node = Node(data) + + if self.head is None: + self.head = new_node + return + + current_node = self.head + while current_node.next_node is not None: + current_node = current_node.next_node + + current_node.next_node = new_node + + def detect_cycle(self) -> bool: + """ + Detects if there is a cycle in the linked list using + Floyd's cycle detection algorithm. + + Returns: + bool: True if there is a cycle, False otherwise. + + Examples: + >>> linked_list = LinkedList() + >>> linked_list.add_node(1) + >>> linked_list.add_node(2) + >>> linked_list.add_node(3) + >>> linked_list.add_node(4) + + >>> linked_list.detect_cycle() + False + + # Create a cycle in the linked list + >>> linked_list.head.next_node.next_node.next_node = linked_list.head.next_node + + >>> linked_list.detect_cycle() + True + """ + if self.head is None: + return False + + slow_pointer: Node | None = self.head + fast_pointer: Node | None = self.head + + while fast_pointer is not None and fast_pointer.next_node is not None: + slow_pointer = slow_pointer.next_node if slow_pointer else None + fast_pointer = fast_pointer.next_node.next_node + if slow_pointer == fast_pointer: + return True + + return False + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + linked_list = LinkedList() + linked_list.add_node(1) + linked_list.add_node(2) + linked_list.add_node(3) + linked_list.add_node(4) + + # Create a cycle in the linked list + # It first checks if the head, next_node, and next_node.next_node attributes of the + # linked list are not None to avoid any potential type errors. + if ( + linked_list.head + and linked_list.head.next_node + and linked_list.head.next_node.next_node + ): + linked_list.head.next_node.next_node.next_node = linked_list.head.next_node + + has_cycle = linked_list.detect_cycle() + print(has_cycle) # Output: True From b5648b8fe2159e06b07b995f9513621dadd4a86c Mon Sep 17 00:00:00 2001 From: Aqib Javid Bhat Date: Mon, 23 Oct 2023 20:15:44 +0530 Subject: [PATCH 2/4] Add tests for add_node function --- .../linked_list/floyds_cycle_detection.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data_structures/linked_list/floyds_cycle_detection.py b/data_structures/linked_list/floyds_cycle_detection.py index 18ca43b912ae..6c3f13760260 100644 --- a/data_structures/linked_list/floyds_cycle_detection.py +++ b/data_structures/linked_list/floyds_cycle_detection.py @@ -48,8 +48,13 @@ def __iter__(self) -> Iterator: >>> tuple(linked_list) (1,) """ + visited = [] node = self.head while node: + # Avoid infinite loop in there's a cycle + if node in visited: + return + visited.append(node) yield node.data node = node.next_node @@ -59,6 +64,15 @@ def add_node(self, data: Any) -> None: Args: data (Any): The data to be stored in the new node. + + Examples: + >>> linked_list = LinkedList() + >>> linked_list.add_node(1) + >>> linked_list.add_node(2) + >>> linked_list.add_node(3) + >>> linked_list.add_node(4) + >>> tuple(linked_list) + (1, 2, 3, 4) """ new_node = Node(data) From b5e0cf86edc956297e75af2bd7ffd4bc0b0f4809 Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 23 Oct 2023 13:48:25 -0400 Subject: [PATCH 3/4] Apply suggestions from code review --- data_structures/linked_list/floyds_cycle_detection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/linked_list/floyds_cycle_detection.py b/data_structures/linked_list/floyds_cycle_detection.py index 6c3f13760260..8f842098a77a 100644 --- a/data_structures/linked_list/floyds_cycle_detection.py +++ b/data_structures/linked_list/floyds_cycle_detection.py @@ -48,13 +48,13 @@ def __iter__(self) -> Iterator: >>> tuple(linked_list) (1,) """ - visited = [] + visited = set() node = self.head while node: # Avoid infinite loop in there's a cycle if node in visited: return - visited.append(node) + visited.add(node) yield node.data node = node.next_node From 53973b67a0a43eb51a5e283595fb313f5360219f Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Mon, 23 Oct 2023 13:52:08 -0400 Subject: [PATCH 4/4] Update floyds_cycle_detection.py --- data_structures/linked_list/floyds_cycle_detection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_structures/linked_list/floyds_cycle_detection.py b/data_structures/linked_list/floyds_cycle_detection.py index 8f842098a77a..6c3f13760260 100644 --- a/data_structures/linked_list/floyds_cycle_detection.py +++ b/data_structures/linked_list/floyds_cycle_detection.py @@ -48,13 +48,13 @@ def __iter__(self) -> Iterator: >>> tuple(linked_list) (1,) """ - visited = set() + visited = [] node = self.head while node: # Avoid infinite loop in there's a cycle if node in visited: return - visited.add(node) + visited.append(node) yield node.data node = node.next_node