From 34cac9d9c33c0c030fc8cf3bd876827679a30132 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Mon, 20 Oct 2025 10:01:29 +0530 Subject: [PATCH 1/5] feat(searches): Add Floyd's Cycle-Finding Algorithm Signed-off-by: Aditya Kumar --- searches/floyds_cycle_finding.py | 80 ++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 searches/floyds_cycle_finding.py diff --git a/searches/floyds_cycle_finding.py b/searches/floyds_cycle_finding.py new file mode 100644 index 000000000000..c050a7df68eb --- /dev/null +++ b/searches/floyds_cycle_finding.py @@ -0,0 +1,80 @@ +""" +An implementation of Floyd's Cycle-Finding Algorithm. +Also known as the "tortoise and the hare" algorithm. + +This algorithm is used to detect a cycle in a sequence of iterated function values. +It can also find the starting index and length of the cycle. + +Wikipedia: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare +""" +from typing import Any, Callable, Optional, Tuple + + +def floyds_cycle_finding( + f: Callable[[Any], Any], x0: Any +) -> Optional[Tuple[int, int]]: + """ + Finds a cycle in the sequence of values generated by the function f. + + Args: + f: A function that takes a value and returns the next value in the sequence. + x0: The starting value of the sequence. + + Returns: + A tuple containing the index of the first element of the cycle (mu) + and the length of the cycle (lam), or None if no cycle is found. + + Doctest examples: + >>> # Example with a cycle + >>> f = lambda x: (2 * x + 3) % 17 + >>> floyds_cycle_finding(f, 0) + (0, 8) + + >>> # Example with a different starting point and cycle + >>> f = lambda x: [1, 2, 3, 4, 5, 3][x] + >>> floyds_cycle_finding(f, 0) + (2, 3) + + >>> # Example with no cycle (sequence terminates) + >>> nodes = {0: 1, 1: 2, 2: 3, 3: None} + >>> f = lambda x: nodes.get(x) + >>> floyds_cycle_finding(f, 0) + + """ + # Main phase of the algorithm: finding a repetition x_i = x_2i. + # The hare moves twice as fast as the tortoise. + tortoise = f(x0) + hare = f(f(x0)) + while tortoise != hare: + # If the hare reaches the end of the sequence, there is no cycle. + if hare is None or f(hare) is None: + return None + tortoise = f(tortoise) + hare = f(f(hare)) + + # At this point, the tortoise and hare have met. + # Now, find the position of the first repetition. + # The distance from the start to the cycle's beginning is mu. + mu = 0 + tortoise = x0 + while tortoise != hare: + tortoise = f(tortoise) + hare = f(hare) + mu += 1 + + # Finally, find the length of the cycle. + # The hare moves one step at a time while the tortoise stays put. + # The number of steps until they meet again is the cycle length (lam). + lam = 1 + hare = f(tortoise) + while tortoise != hare: + hare = f(hare) + lam += 1 + + return mu, lam + + +if __name__ == "__main__": + import doctest + + doctest.testmod() \ No newline at end of file From a44a5fb454a58a34a093f476e48ad7c11a98a903 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 05:18:03 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- searches/floyds_cycle_finding.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/searches/floyds_cycle_finding.py b/searches/floyds_cycle_finding.py index c050a7df68eb..c49c9832dbe2 100644 --- a/searches/floyds_cycle_finding.py +++ b/searches/floyds_cycle_finding.py @@ -7,12 +7,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare """ + from typing import Any, Callable, Optional, Tuple -def floyds_cycle_finding( - f: Callable[[Any], Any], x0: Any -) -> Optional[Tuple[int, int]]: +def floyds_cycle_finding(f: Callable[[Any], Any], x0: Any) -> Optional[Tuple[int, int]]: """ Finds a cycle in the sequence of values generated by the function f. @@ -77,4 +76,4 @@ def floyds_cycle_finding( if __name__ == "__main__": import doctest - doctest.testmod() \ No newline at end of file + doctest.testmod() From 0dd682101dc5de68a77daff8aaa02458f4c14caf Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Mon, 20 Oct 2025 10:55:05 +0530 Subject: [PATCH 3/5] refactor(searches): Improve readability of Floyd's Cycle-Finding Algorithm Signed-off-by: Aditya Kumar --- searches/floyds_cycle_finding.py | 63 +++++++++++++++----------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/searches/floyds_cycle_finding.py b/searches/floyds_cycle_finding.py index c49c9832dbe2..26dbbd8e3355 100644 --- a/searches/floyds_cycle_finding.py +++ b/searches/floyds_cycle_finding.py @@ -7,67 +7,64 @@ Wikipedia: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare """ +from collections.abc import Callable +from typing import Any -from typing import Any, Callable, Optional, Tuple - -def floyds_cycle_finding(f: Callable[[Any], Any], x0: Any) -> Optional[Tuple[int, int]]: +def floyds_cycle_finding( + successor_function: Callable[[Any], Any], start_value: Any +) -> tuple[int, int] | None: """ - Finds a cycle in the sequence of values generated by the function f. + Finds a cycle in the sequence of values generated by the successor function. Args: - f: A function that takes a value and returns the next value in the sequence. - x0: The starting value of the sequence. + successor_function: A function that takes a value and returns the next value. + start_value: The starting value of the sequence. Returns: A tuple containing the index of the first element of the cycle (mu) and the length of the cycle (lam), or None if no cycle is found. Doctest examples: - >>> # Example with a cycle - >>> f = lambda x: (2 * x + 3) % 17 - >>> floyds_cycle_finding(f, 0) + >>> # Example with a mathematical sequence + >>> sequence_func = lambda x: (2 * x + 3) % 17 + >>> floyds_cycle_finding(sequence_func, 0) (0, 8) - >>> # Example with a different starting point and cycle - >>> f = lambda x: [1, 2, 3, 4, 5, 3][x] - >>> floyds_cycle_finding(f, 0) + >>> # Example with a list acting as a sequence + >>> get_next_item = lambda x: [1, 2, 3, 4, 5, 3][x] + >>> floyds_cycle_finding(get_next_item, 0) (2, 3) - >>> # Example with no cycle (sequence terminates) - >>> nodes = {0: 1, 1: 2, 2: 3, 3: None} - >>> f = lambda x: nodes.get(x) - >>> floyds_cycle_finding(f, 0) + >>> # Example with a graph-like structure (no cycle) + >>> get_next_node = lambda x: {0: 1, 1: 2, 2: 3, 3: None}.get(x) + >>> floyds_cycle_finding(get_next_node, 0) """ - # Main phase of the algorithm: finding a repetition x_i = x_2i. + # Main phase: finding a repetition x_i = x_2i. # The hare moves twice as fast as the tortoise. - tortoise = f(x0) - hare = f(f(x0)) + tortoise = successor_function(start_value) + hare = successor_function(successor_function(start_value)) while tortoise != hare: # If the hare reaches the end of the sequence, there is no cycle. - if hare is None or f(hare) is None: + if hare is None or successor_function(hare) is None: return None - tortoise = f(tortoise) - hare = f(f(hare)) + tortoise = successor_function(tortoise) + hare = successor_function(successor_function(hare)) - # At this point, the tortoise and hare have met. - # Now, find the position of the first repetition. - # The distance from the start to the cycle's beginning is mu. + # Phase 2: find the position of the first repetition (mu). mu = 0 - tortoise = x0 + tortoise = start_value while tortoise != hare: - tortoise = f(tortoise) - hare = f(hare) + tortoise = successor_function(tortoise) + hare = successor_function(hare) mu += 1 - # Finally, find the length of the cycle. - # The hare moves one step at a time while the tortoise stays put. - # The number of steps until they meet again is the cycle length (lam). + # Phase 3: find the length of the cycle (lam). lam = 1 - hare = f(tortoise) + hare = successor_function(tortoise) while tortoise != hare: - hare = f(hare) + hare = successor_function(hare) lam += 1 return mu, lam From 141ed1c5ffdf93078685e868150b763aa40bb862 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 05:38:15 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- searches/floyds_cycle_finding.py | 1 + 1 file changed, 1 insertion(+) diff --git a/searches/floyds_cycle_finding.py b/searches/floyds_cycle_finding.py index 26dbbd8e3355..7278e3c7c331 100644 --- a/searches/floyds_cycle_finding.py +++ b/searches/floyds_cycle_finding.py @@ -7,6 +7,7 @@ Wikipedia: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare """ + from collections.abc import Callable from typing import Any From 19aca57c836bd2a9d99f463fb0437eb106557939 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Wed, 22 Oct 2025 09:21:53 +0530 Subject: [PATCH 5/5] refactor(searches): Addressed failing build issue Signed-off-by: Aditya Kumar --- searches/floyds_cycle_finding.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/searches/floyds_cycle_finding.py b/searches/floyds_cycle_finding.py index 7278e3c7c331..f9c62ef6d488 100644 --- a/searches/floyds_cycle_finding.py +++ b/searches/floyds_cycle_finding.py @@ -42,18 +42,30 @@ def floyds_cycle_finding( >>> floyds_cycle_finding(get_next_node, 0) """ - # Main phase: finding a repetition x_i = x_2i. + # Phase 1: Find a repetition x_i = x_2i. + # The tortoise moves one step at a time, and the hare moves two. + tortoise = start_value + hare = successor_function(start_value) + # The hare moves twice as fast as the tortoise. - tortoise = successor_function(start_value) - hare = successor_function(successor_function(start_value)) + # The loop continues as long as they are not at the same value + # and the hare has not reached the end of the sequence. while tortoise != hare: - # If the hare reaches the end of the sequence, there is no cycle. - if hare is None or successor_function(hare) is None: + if hare is None: return None tortoise = successor_function(tortoise) - hare = successor_function(successor_function(hare)) - # Phase 2: find the position of the first repetition (mu). + # Move hare two steps, with a check after each step. + hare = successor_function(hare) + if hare is None: + return None + hare = successor_function(hare) + + # If the loop exits, a cycle was found. + + # Phase 2: Find the position of the first repetition (mu). + # Reset tortoise to the start and move both one step at a time until they + # meet again. mu = 0 tortoise = start_value while tortoise != hare: @@ -61,7 +73,9 @@ def floyds_cycle_finding( hare = successor_function(hare) mu += 1 - # Phase 3: find the length of the cycle (lam). + # Phase 3: Find the length of the cycle (lam). + # Fix the tortoise at the start of the cycle and move the hare + # until it returns to the tortoise. lam = 1 hare = successor_function(tortoise) while tortoise != hare: