There are no docstrings. How are we supposed to know what this code does and how to call it?
There are no test cases. This kind of code is an ideal opportunity to use doctests.
UsingTo memoize a function, the same piece of code for multiple purposes makes your code hardermost convenient way to read, harderdo it is to testuse a decorator. This keeps the memoization logic separate from the code being memoized, and less maintainablealso makes the memoization re-usable for other functions. Here you are usingIn particular, there is a single class for three things: (i) computing Fibonacci numbers;@memoized
decorator in the (ii) memoizing a recursive computationPython Decorator Library, and (iii) summingso there is no need to reinvent the resultswheel. Using this decorator, you could write:
@memoized
def F(n):
"""Return the 'n'th Fibonacci number.
>>> [F(i) for i in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
"""
if n <= 2:
return (0, 1, 1)[n]
else:
return F(n - 1) + F(n - 2)
It would be better to split these three parts of the functionality into different functions or classes.
To memoize a function, the most convenient way to do it is to use a decorator. In particular, there is a @memoized
decorator in the Python Decorator Library, so there is no need to reinvent the wheel. Using this decorator, you could write:
@memoized
def F(n):
"""Return the 'n'th Fibonacci number.
>>> [F(i) for i in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
"""
if n <= 2:
return (0, 1, 1)[n]
else:
return F(n - 1) + F(n - 2)
- Once you split the memoization into its own function or class, there will no longer be any persistent state in your program (the only use of
self
at present is self.cached_result
). So there is no need to use a class here. Once you split the memoization into its own function or class, there will no longer be any persistent state in your program (the only use of self
at present is self.cached_result
). So there is no need to use a class here.
2. A mathematicalsimpler approach
A nice feature of Project EulerThis is that the problems benefit from a bit of mathematical thinking. In some of the early problems you can bludgeon through them by looping over all the values (as you do here), but later on this becomes impossible: it would take far too long, so you have to use some mathematics to reduce the amountkind of computation.
Let's practiceproblem that here. Start by having a look at the Fibonacci numbers and seeing which ones are even:
0 1 1 2 3 5 8 13 21 34 55 89 144 ...
You can see that every third Fibonacci number is even (exercise: prove this)be solved very simply. NowNo need for classes, each Fibonacci number is the sum of the previous two Fibonacci numbersmemoization, so the sum ofor multiple functions; all the even Fibonacci numbers up to F3nthat's needed is exactly half of sum of all the Fibonacci numbers up to F3n. (For example, taking n = 2, we find that 0 + 2 + 8 = 10, and 0 + 1 + 1 + 2 + 3 + 5 + 8 = 20.)
Can we find a quick way to sum the Fibonacci numbers? Let's look at the partial sums and see if there's a pattern:
0 1 2 4 7 12 20 33 54 88 143
It should be clear from this that the sum of the Fibonacci numbers up to Fk is Fk+2 − 1 (exercise: prove this by induction).
So if you can find the largest even Fibonacci number below four million (call it F3N) then the sum of all the Fibonacci numbers below four million will be F3N+2 − 1. How can we quickly find N? Well, a quick peek at Wikipedia's Fibonacci numbers article reveals that
3N = floor(logφ(F3N·√5 + ½))
(where φ = (1 + √5) / 2). Here, F3N ≤ 4,000,000, and log and floor are monotonic, so
3N ≤ floor(logφ(4,000,000 · √5 + ½))
Hence
N ≤ floor(logφ(4,000,000 · √5 + ½)) / 3
and since we want the largest such N, the value we want is
N = floor(floor(logφ(4,000,000·√5 + ½)) / 3)
Which you can quickly compute in Python:
>>> from math import floor, log, sqrt
>>> phi = (1 + sqrt(5)) / 2
>>> int(floor(log(4000000 * sqrt(5) + 0.5, phi) / 3))
11
and so the answer we want is F35 − 1, and again peeking at Wikipedia we find that
Fn = round(φn / √5)
So putting this all togethersimple loop:
def sum_even_fibonacci(limit):
"""Return the sum of the even Fibonacci numbers up to 'limit'.
>>> # See <http://oeis.org/A000071>A099919>
>>> [sum_even_fibonacci(10**i4 ** i) for i in range(81, 11)]
[0[2, 2010, 8844, 1596188, 6764798, 1213923382, 217830814328, 9227464]60696, 257114, 1089154]
"""
#total See= <http://en.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding>0
from matha, importb floor,= log0, sqrt1
sqrt5while =b sqrt(5)<= limit:
phi = (1 + sqrt5)if /b % 2 == 0:
N = int(floor(log(limit * sqrt5 + 0.5, phi) /total 3))+= b
return int(round(pow(phi a, 3b *= Nb, a + 2)b
/ sqrt5)) - 1return total
Note that this code uses floating-point numbers, so it won't work for arbitrarily large values of limit
. But if you needed to, you could re-code it using the decimal
module.