Skip to content
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,7 @@
* [Double Linear Search Recursion](searches/double_linear_search_recursion.py)
* [Exponential Search](searches/exponential_search.py)
* [Fibonacci Search](searches/fibonacci_search.py)
* [Floyds Cycle Finding](searches/floyds_cycle_finding.py)
* [Hill Climbing](searches/hill_climbing.py)
* [Interpolation Search](searches/interpolation_search.py)
* [Jump Search](searches/jump_search.py)
Expand Down
91 changes: 91 additions & 0 deletions searches/floyds_cycle_finding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
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 collections.abc import Callable
from typing import Any


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 successor function.

Args:
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 mathematical sequence
>>> sequence_func = lambda x: (2 * x + 3) % 17
>>> floyds_cycle_finding(sequence_func, 0)
(0, 8)

>>> # 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 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)

"""
# 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.
# 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 hare is None:
return None
tortoise = successor_function(tortoise)

# 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:
tortoise = successor_function(tortoise)
hare = successor_function(hare)
mu += 1

# 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:
hare = successor_function(hare)
lam += 1

return mu, lam


if __name__ == "__main__":
import doctest

doctest.testmod()
Loading