|
| 1 | +""" |
| 2 | +The Collatz conjecture is a famous unsolved problem in mathematics. Given a starting |
| 3 | +positive integer, define the following sequence: |
| 4 | +- If the current term n is even, then the next term is n/2. |
| 5 | +- If the current term n is odd, then the next term is 3n + 1. |
| 6 | +The conjecture claims that this sequence will always reach 1 for any starting number. |
| 7 | +
|
| 8 | +Other names for this problem include the 3n + 1 problem, the Ulam conjecture, Kakutani's |
| 9 | +problem, the Thwaites conjecture, Hasse's algorithm, the Syracuse problem, and the |
| 10 | +hailstone sequence. |
| 11 | +
|
| 12 | +Reference: https://en.wikipedia.org/wiki/Collatz_conjecture |
| 13 | +""" |
| 14 | + |
1 | 15 | from __future__ import annotations
|
2 | 16 |
|
| 17 | +from collections.abc import Generator |
3 | 18 |
|
4 |
| -def collatz_sequence(n: int) -> list[int]: |
| 19 | + |
| 20 | +def collatz_sequence(n: int) -> Generator[int, None, None]: |
5 | 21 | """
|
6 |
| - Collatz conjecture: start with any positive integer n. The next term is |
7 |
| - obtained as follows: |
8 |
| - If n term is even, the next term is: n / 2 . |
9 |
| - If n is odd, the next term is: 3 * n + 1. |
10 |
| -
|
11 |
| - The conjecture states the sequence will always reach 1 for any starting value n. |
12 |
| - Example: |
13 |
| - >>> collatz_sequence(2.1) |
| 22 | + Generate the Collatz sequence starting at n. |
| 23 | + >>> tuple(collatz_sequence(2.1)) |
14 | 24 | Traceback (most recent call last):
|
15 | 25 | ...
|
16 |
| - Exception: Sequence only defined for natural numbers |
17 |
| - >>> collatz_sequence(0) |
| 26 | + Exception: Sequence only defined for positive integers |
| 27 | + >>> tuple(collatz_sequence(0)) |
18 | 28 | Traceback (most recent call last):
|
19 | 29 | ...
|
20 |
| - Exception: Sequence only defined for natural numbers |
21 |
| - >>> collatz_sequence(43) # doctest: +NORMALIZE_WHITESPACE |
22 |
| - [43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, |
23 |
| - 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] |
| 30 | + Exception: Sequence only defined for positive integers |
| 31 | + >>> tuple(collatz_sequence(4)) |
| 32 | + (4, 2, 1) |
| 33 | + >>> tuple(collatz_sequence(11)) |
| 34 | + (11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1) |
| 35 | + >>> tuple(collatz_sequence(31)) # doctest: +NORMALIZE_WHITESPACE |
| 36 | + (31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137, |
| 37 | + 412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593, |
| 38 | + 1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425, |
| 39 | + 1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644, |
| 40 | + 1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732, |
| 41 | + 866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53, |
| 42 | + 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1) |
| 43 | + >>> tuple(collatz_sequence(43)) # doctest: +NORMALIZE_WHITESPACE |
| 44 | + (43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26, |
| 45 | + 13, 40, 20, 10, 5, 16, 8, 4, 2, 1) |
24 | 46 | """
|
25 |
| - |
26 | 47 | if not isinstance(n, int) or n < 1:
|
27 |
| - raise Exception("Sequence only defined for natural numbers") |
| 48 | + raise Exception("Sequence only defined for positive integers") |
28 | 49 |
|
29 |
| - sequence = [n] |
| 50 | + yield n |
30 | 51 | while n != 1:
|
31 |
| - n = 3 * n + 1 if n & 1 else n // 2 |
32 |
| - sequence.append(n) |
33 |
| - return sequence |
| 52 | + if n % 2 == 0: |
| 53 | + n //= 2 |
| 54 | + else: |
| 55 | + n = 3 * n + 1 |
| 56 | + yield n |
34 | 57 |
|
35 | 58 |
|
36 | 59 | def main():
|
37 | 60 | n = 43
|
38 |
| - sequence = collatz_sequence(n) |
| 61 | + sequence = tuple(collatz_sequence(n)) |
39 | 62 | print(sequence)
|
40 |
| - print(f"collatz sequence from {n} took {len(sequence)} steps.") |
| 63 | + print(f"Collatz sequence from {n} took {len(sequence)} steps.") |
41 | 64 |
|
42 | 65 |
|
43 | 66 | if __name__ == "__main__":
|
|
0 commit comments