Skip to content

Commit 7acc653

Browse files
committed
Add Zeckendorf representation algorithm
Implements Zeckendorf's theorem: every positive integer can be written uniquely as the sum of one or more distinct, non-consecutive Fibonacci numbers. The representation is recovered with a greedy pass from the largest Fibonacci number downwards, which is optimal for this problem. Includes doctests for 0, 1, 2, 4, 10, 100 and 1000, plus TypeError for non-integers and ValueError for negative input. https://en.wikipedia.org/wiki/Zeckendorf%27s_theorem
1 parent 6e03a48 commit 7acc653

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

‎maths/zeckendorf.py‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
https://en.wikipedia.org/wiki/Zeckendorf%27s_theorem
3+
"""
4+
5+
6+
def _fibonacci_numbers_up_to(limit: int) -> list[int]:
7+
"""
8+
Return the Fibonacci numbers (1, 2, 3, 5, ...) that are <= limit.
9+
10+
The sequence starts at 1, 2 rather than 1, 1 so that every number has a
11+
unique representation and the value 1 is used at most once.
12+
13+
>>> _fibonacci_numbers_up_to(1)
14+
[1]
15+
>>> _fibonacci_numbers_up_to(10)
16+
[1, 2, 3, 5, 8]
17+
>>> _fibonacci_numbers_up_to(0)
18+
[]
19+
"""
20+
numbers: list[int] = [1, 2]
21+
while numbers[-1] + numbers[-2] <= limit:
22+
numbers.append(numbers[-1] + numbers[-2])
23+
return [number for number in numbers if number <= limit]
24+
25+
26+
def zeckendorf(number: int) -> list[int]:
27+
"""
28+
Return the Zeckendorf representation of a non-negative integer.
29+
30+
Zeckendorf's theorem states that every positive integer can be written
31+
uniquely as the sum of one or more distinct, non-consecutive Fibonacci
32+
numbers. This function recovers that representation with a greedy pass
33+
over the Fibonacci numbers from largest to smallest, which is always
34+
optimal for this problem.
35+
36+
>>> zeckendorf(0)
37+
[]
38+
>>> zeckendorf(1)
39+
[1]
40+
>>> zeckendorf(2)
41+
[2]
42+
>>> zeckendorf(4)
43+
[3, 1]
44+
>>> zeckendorf(10)
45+
[8, 2]
46+
>>> zeckendorf(100)
47+
[89, 8, 3]
48+
>>> zeckendorf(1000)
49+
[987, 13]
50+
>>> zeckendorf(-1)
51+
Traceback (most recent call last):
52+
...
53+
ValueError: zeckendorf() only accepts non-negative integers
54+
>>> zeckendorf(1.5)
55+
Traceback (most recent call last):
56+
...
57+
TypeError: zeckendorf() only accepts integers
58+
"""
59+
if isinstance(number, bool) or not isinstance(number, int):
60+
raise TypeError("zeckendorf() only accepts integers")
61+
if number < 0:
62+
raise ValueError("zeckendorf() only accepts non-negative integers")
63+
64+
representation: list[int] = []
65+
remainder = number
66+
for fibonacci_number in reversed(_fibonacci_numbers_up_to(number)):
67+
if fibonacci_number <= remainder:
68+
representation.append(fibonacci_number)
69+
remainder -= fibonacci_number
70+
return representation
71+
72+
73+
if __name__ == "__main__":
74+
import doctest
75+
76+
doctest.testmod()

0 commit comments

Comments
 (0)