Skip to content

Commit 2a8abbd

Browse files
committed
Add Floyd's algorithm illustration
1 parent 701432a commit 2a8abbd

2 files changed

Lines changed: 369 additions & 1 deletion

File tree

source_code/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ certain subtle points.
4343
errors.
4444
1. `iterative_binary_trees.ipynb`: using iteration rather than recursion to traverse
4545
binary trees.
46-
1. `quad_tree.ipynb`: implementation of a quad tree and comparison to naive approach.
46+
1. `quad_tree.ipynb`: implementation of a quad tree and comparison to naive
47+
approach.
48+
1. `cycles.ipynb`: find cycles in periodic functions. Naive vs. approach using
49+
dictionaries, vs. Floyd's algorithm.

source_code/cycles.ipynb

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 11,
6+
"id": "72050593-cd1e-4b99-bf85-7a748b813694",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"def find_cycles_naive(func, start_value, max_iters):\n",
11+
" history = [start_value]\n",
12+
" for generation in range(1, 1 + max_iters):\n",
13+
" value = func(history[-1])\n",
14+
" if value in history:\n",
15+
" offset = history.index(value)\n",
16+
" return offset, generation - offset + 1\n",
17+
" else:\n",
18+
" history.append(value)\n",
19+
" return max_iters, None"
20+
]
21+
},
22+
{
23+
"cell_type": "code",
24+
"execution_count": 43,
25+
"id": "1973e2d5-f59d-439c-9bd3-b6381ac13299",
26+
"metadata": {},
27+
"outputs": [],
28+
"source": [
29+
"def collatz_conjecture(n):\n",
30+
" return n//2 if n % 2 == 0 else 3*n + 1"
31+
]
32+
},
33+
{
34+
"cell_type": "code",
35+
"execution_count": 49,
36+
"id": "8cf319fb-cc1a-49b7-a63e-97972ce768c7",
37+
"metadata": {},
38+
"outputs": [
39+
{
40+
"data": {
41+
"text/plain": [
42+
"(60, 3)"
43+
]
44+
},
45+
"execution_count": 49,
46+
"metadata": {},
47+
"output_type": "execute_result"
48+
}
49+
],
50+
"source": [
51+
"find_cycles_naive(collatz_conjecture, 1023, 100)"
52+
]
53+
},
54+
{
55+
"cell_type": "code",
56+
"execution_count": 12,
57+
"id": "92990251-8f03-46cb-9206-e71274ec87ba",
58+
"metadata": {},
59+
"outputs": [],
60+
"source": [
61+
"def find_cycles_dict(func, start_value, max_iters):\n",
62+
" history = {start_value: 0}\n",
63+
" value = start_value\n",
64+
" for generation in range(1, 1 + max_iters):\n",
65+
" value = func(value)\n",
66+
" if value in history:\n",
67+
" offset = history[value]\n",
68+
" return offset, generation - offset\n",
69+
" else:\n",
70+
" history[value] = generation\n",
71+
" return max_iters, None"
72+
]
73+
},
74+
{
75+
"cell_type": "code",
76+
"execution_count": 61,
77+
"id": "a7e3de97-3031-4aff-b799-fec419ba6173",
78+
"metadata": {
79+
"scrolled": true
80+
},
81+
"outputs": [
82+
{
83+
"data": {
84+
"text/plain": [
85+
"(60, 3)"
86+
]
87+
},
88+
"execution_count": 61,
89+
"metadata": {},
90+
"output_type": "execute_result"
91+
}
92+
],
93+
"source": [
94+
"find_cycles_dict(collatz_conjecture, 1023, 100)"
95+
]
96+
},
97+
{
98+
"cell_type": "code",
99+
"execution_count": 13,
100+
"id": "aa224337-7847-41da-8859-d285c994c528",
101+
"metadata": {},
102+
"outputs": [],
103+
"source": [
104+
"def find_cycles_floyd(func, start_value, max_iters):\n",
105+
" tortoise = func(start_value)\n",
106+
" hare = func(tortoise)\n",
107+
" while tortoise != hare:\n",
108+
" tortoise = func(tortoise)\n",
109+
" hare = func(func(hare))\n",
110+
" offset = 0\n",
111+
" tortoise = start_value\n",
112+
" while tortoise != hare:\n",
113+
" tortoise = func(tortoise)\n",
114+
" hare = func(hare)\n",
115+
" offset += 1\n",
116+
" length = 1\n",
117+
" hare = func(tortoise)\n",
118+
" while tortoise != hare:\n",
119+
" hare = func(hare)\n",
120+
" length += 1\n",
121+
" return offset, length"
122+
]
123+
},
124+
{
125+
"cell_type": "code",
126+
"execution_count": 67,
127+
"id": "3639aefe-2e2b-4342-aebb-7736c64c8645",
128+
"metadata": {},
129+
"outputs": [
130+
{
131+
"data": {
132+
"text/plain": [
133+
"(60, 3)"
134+
]
135+
},
136+
"execution_count": 67,
137+
"metadata": {},
138+
"output_type": "execute_result"
139+
}
140+
],
141+
"source": [
142+
"find_cycles_floyd(collatz_conjecture, 1023, 100)"
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": 71,
148+
"id": "42eed8d2-3443-4636-840e-6731350a686b",
149+
"metadata": {},
150+
"outputs": [],
151+
"source": [
152+
"for n in range(1024):\n",
153+
" naive_cycle = find_cycles_naive(collatz_conjecture, n, 100_000)\n",
154+
" dict_cycle = find_cycles_dict(collatz_conjecture, n, 100_000)\n",
155+
" floyd_cycle = find_cycles_floyd(collatz_conjecture, n, 100_000)\n",
156+
" assert naive_cycle == dict_cycle\n",
157+
" assert dict_cycle == floyd_cycle"
158+
]
159+
},
160+
{
161+
"cell_type": "code",
162+
"execution_count": 69,
163+
"id": "7e4d214c-ba72-4007-98ba-dd7b61ed18e1",
164+
"metadata": {},
165+
"outputs": [],
166+
"source": [
167+
"def show_series(func, start_value, max_iters):\n",
168+
" value = start_value\n",
169+
" for _ in range(max_iters):\n",
170+
" print(value)\n",
171+
" value = func(value)"
172+
]
173+
},
174+
{
175+
"cell_type": "code",
176+
"execution_count": 76,
177+
"id": "ee8d5f5c-1257-420e-9e5c-e78900da3c1b",
178+
"metadata": {},
179+
"outputs": [
180+
{
181+
"name": "stdout",
182+
"output_type": "stream",
183+
"text": [
184+
"17.2 µs ± 248 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n"
185+
]
186+
}
187+
],
188+
"source": [
189+
"%timeit find_cycles_naive(collatz_conjecture, 1023, 100_000)"
190+
]
191+
},
192+
{
193+
"cell_type": "code",
194+
"execution_count": 77,
195+
"id": "eec36b86-2c86-4eda-8700-066ce1d011de",
196+
"metadata": {},
197+
"outputs": [
198+
{
199+
"name": "stdout",
200+
"output_type": "stream",
201+
"text": [
202+
"6.46 µs ± 136 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n"
203+
]
204+
}
205+
],
206+
"source": [
207+
"%timeit find_cycles_dict(collatz_conjecture, 1023, 100_000)"
208+
]
209+
},
210+
{
211+
"cell_type": "code",
212+
"execution_count": 78,
213+
"id": "511e6192-4dde-4bd8-9ade-d7838b5eccff",
214+
"metadata": {},
215+
"outputs": [
216+
{
217+
"name": "stdout",
218+
"output_type": "stream",
219+
"text": [
220+
"15.7 µs ± 289 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n"
221+
]
222+
}
223+
],
224+
"source": [
225+
"%timeit find_cycles_floyd(collatz_conjecture, 1023, 100_000)"
226+
]
227+
},
228+
{
229+
"cell_type": "code",
230+
"execution_count": 80,
231+
"id": "ada23862-9d69-4ccb-9309-5ddfc1d07645",
232+
"metadata": {},
233+
"outputs": [],
234+
"source": [
235+
"offsets = [find_cycles_dict(collatz_conjecture, n, 100_000)[0] for n in range(2**20 - 1)]"
236+
]
237+
},
238+
{
239+
"cell_type": "code",
240+
"execution_count": 50,
241+
"id": "2a28f9ba-a284-441d-8c0f-8e7c7365af6f",
242+
"metadata": {},
243+
"outputs": [],
244+
"source": [
245+
"def generate_periodic_function(offset, length):\n",
246+
" def internal(n):\n",
247+
" if n < offset - 1:\n",
248+
" return n + 1\n",
249+
" return offset + ((n + 1 - offset) % length)\n",
250+
" return internal"
251+
]
252+
},
253+
{
254+
"cell_type": "code",
255+
"execution_count": 51,
256+
"id": "b34cb35a-9949-4187-94be-420196511c8c",
257+
"metadata": {},
258+
"outputs": [],
259+
"source": [
260+
"f = generate_periodic_function(5, 3)"
261+
]
262+
},
263+
{
264+
"cell_type": "code",
265+
"execution_count": 52,
266+
"id": "2a7e97c2-92bb-4ef7-9771-b21f9f791fa5",
267+
"metadata": {},
268+
"outputs": [
269+
{
270+
"name": "stdout",
271+
"output_type": "stream",
272+
"text": [
273+
"1\n",
274+
"2\n",
275+
"3\n",
276+
"4\n",
277+
"5\n",
278+
"6\n",
279+
"7\n",
280+
"5\n",
281+
"6\n",
282+
"7\n",
283+
"5\n",
284+
"6\n",
285+
"7\n",
286+
"5\n",
287+
"6\n",
288+
"7\n",
289+
"5\n",
290+
"6\n",
291+
"7\n",
292+
"5\n"
293+
]
294+
}
295+
],
296+
"source": [
297+
"for n in range(20):\n",
298+
" print(f(n))"
299+
]
300+
},
301+
{
302+
"cell_type": "code",
303+
"execution_count": 53,
304+
"id": "fd92a91b-d1e7-4bf1-a7c9-7fb3b3d60f20",
305+
"metadata": {},
306+
"outputs": [
307+
{
308+
"data": {
309+
"text/plain": [
310+
"(5, 3)"
311+
]
312+
},
313+
"execution_count": 53,
314+
"metadata": {},
315+
"output_type": "execute_result"
316+
}
317+
],
318+
"source": [
319+
"find_cycles_dict(f, 0, 100)"
320+
]
321+
},
322+
{
323+
"cell_type": "code",
324+
"execution_count": 54,
325+
"id": "8be3dec2-736c-4617-b7cd-e016770e28fd",
326+
"metadata": {},
327+
"outputs": [],
328+
"source": [
329+
"for offset in range(10):\n",
330+
" for length in range(1, 5):\n",
331+
" f = generate_periodic_function(offset, length)\n",
332+
" assert (offset, length) == find_cycles_floyd(f, 0, 0)"
333+
]
334+
},
335+
{
336+
"cell_type": "code",
337+
"execution_count": null,
338+
"id": "a7e10960-0a9c-447d-8ba2-edfe1900bc19",
339+
"metadata": {},
340+
"outputs": [],
341+
"source": []
342+
}
343+
],
344+
"metadata": {
345+
"kernelspec": {
346+
"display_name": "Python 3 (ipykernel)",
347+
"language": "python",
348+
"name": "python3"
349+
},
350+
"language_info": {
351+
"codemirror_mode": {
352+
"name": "ipython",
353+
"version": 3
354+
},
355+
"file_extension": ".py",
356+
"mimetype": "text/x-python",
357+
"name": "python",
358+
"nbconvert_exporter": "python",
359+
"pygments_lexer": "ipython3",
360+
"version": "3.12.3"
361+
}
362+
},
363+
"nbformat": 4,
364+
"nbformat_minor": 5
365+
}

0 commit comments

Comments
 (0)