Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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