77
88Wikipedia: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
99"""
10+ from collections .abc import Callable
11+ from typing import Any
1012
11- from typing import Any , Callable , Optional , Tuple
1213
13-
14- def floyds_cycle_finding (f : Callable [[Any ], Any ], x0 : Any ) -> Optional [Tuple [int , int ]]:
14+ def floyds_cycle_finding (
15+ successor_function : Callable [[Any ], Any ], start_value : Any
16+ ) -> tuple [int , int ] | None :
1517 """
16- Finds a cycle in the sequence of values generated by the function f .
18+ Finds a cycle in the sequence of values generated by the successor function .
1719
1820 Args:
19- f : A function that takes a value and returns the next value in the sequence .
20- x0 : The starting value of the sequence.
21+ successor_function : A function that takes a value and returns the next value.
22+ start_value : The starting value of the sequence.
2123
2224 Returns:
2325 A tuple containing the index of the first element of the cycle (mu)
2426 and the length of the cycle (lam), or None if no cycle is found.
2527
2628 Doctest examples:
27- >>> # Example with a cycle
28- >>> f = lambda x: (2 * x + 3) % 17
29- >>> floyds_cycle_finding(f , 0)
29+ >>> # Example with a mathematical sequence
30+ >>> sequence_func = lambda x: (2 * x + 3) % 17
31+ >>> floyds_cycle_finding(sequence_func , 0)
3032 (0, 8)
3133
32- >>> # Example with a different starting point and cycle
33- >>> f = lambda x: [1, 2, 3, 4, 5, 3][x]
34- >>> floyds_cycle_finding(f , 0)
34+ >>> # Example with a list acting as a sequence
35+ >>> get_next_item = lambda x: [1, 2, 3, 4, 5, 3][x]
36+ >>> floyds_cycle_finding(get_next_item , 0)
3537 (2, 3)
3638
37- >>> # Example with no cycle (sequence terminates)
38- >>> nodes = {0: 1, 1: 2, 2: 3, 3: None}
39- >>> f = lambda x: nodes.get(x)
40- >>> floyds_cycle_finding(f, 0)
39+ >>> # Example with a graph-like structure (no cycle)
40+ >>> get_next_node = lambda x: {0: 1, 1: 2, 2: 3, 3: None}.get(x)
41+ >>> floyds_cycle_finding(get_next_node, 0)
4142
4243 """
43- # Main phase of the algorithm : finding a repetition x_i = x_2i.
44+ # Main phase: finding a repetition x_i = x_2i.
4445 # The hare moves twice as fast as the tortoise.
45- tortoise = f ( x0 )
46- hare = f ( f ( x0 ))
46+ tortoise = successor_function ( start_value )
47+ hare = successor_function ( successor_function ( start_value ))
4748 while tortoise != hare :
4849 # If the hare reaches the end of the sequence, there is no cycle.
49- if hare is None or f (hare ) is None :
50+ if hare is None or successor_function (hare ) is None :
5051 return None
51- tortoise = f (tortoise )
52- hare = f ( f (hare ))
52+ tortoise = successor_function (tortoise )
53+ hare = successor_function ( successor_function (hare ))
5354
54- # At this point, the tortoise and hare have met.
55- # Now, find the position of the first repetition.
56- # The distance from the start to the cycle's beginning is mu.
55+ # Phase 2: find the position of the first repetition (mu).
5756 mu = 0
58- tortoise = x0
57+ tortoise = start_value
5958 while tortoise != hare :
60- tortoise = f (tortoise )
61- hare = f (hare )
59+ tortoise = successor_function (tortoise )
60+ hare = successor_function (hare )
6261 mu += 1
6362
64- # Finally, find the length of the cycle.
65- # The hare moves one step at a time while the tortoise stays put.
66- # The number of steps until they meet again is the cycle length (lam).
63+ # Phase 3: find the length of the cycle (lam).
6764 lam = 1
68- hare = f (tortoise )
65+ hare = successor_function (tortoise )
6966 while tortoise != hare :
70- hare = f (hare )
67+ hare = successor_function (hare )
7168 lam += 1
7269
7370 return mu , lam
@@ -77,3 +74,4 @@ def floyds_cycle_finding(f: Callable[[Any], Any], x0: Any) -> Optional[Tuple[int
7774 import doctest
7875
7976 doctest .testmod ()
77+
0 commit comments