1

So, I saw someone using the reduce method to calculate the Fibonacci sequence. Here is his idea: (1,0) , (1,1) , (2,1) , (3,2) , (5,3) corresponds to 1, 1, 2, 3, 5, 8, 13, 21 .......

and the code looks like this

def fib_reduce(n):
    initial =(1,0)
    dummy = range(n)
    fib_n = reduce(lambda prev ,b : (prev[0] + prev[1], prev[0]),
                   dummy,
                   initial)
    
    return fib_n[0]

I understand the (prev[0] + prev[1] , prev[0]) which is like a, b = b, b + a. However, I don't understand what this b stands for ?

May someone please explain this b?

2
  • 1
    I don't see any recursion here. Commented Nov 18, 2020 at 6:59
  • 1
    Note that this is not a recursive algorithm. While reduce can be implemented recursively, in Python it is effectively a for loop (pure Python implementation, C implementation). Commented Nov 18, 2020 at 9:23

1 Answer 1

2

Repeatedly applying a function using reduce

This answer suggests writing up your own function repeated to repeatedly apply a function, rather than calling reduce with a dummy second argument.

We still use reduce, but in a more functional manner and with itertools.repeat.

from itertools import repeat
from functools import reduce

def repeated(func, n):
    def apply(x, f):
        return f(x)
    def ret(x):
        return reduce(apply, repeat(func, n), x)
    return ret

def fibonacci(n):
  get_next_pair = lambda p: (sum(p), p[0])
  first_pair = (1, 0)
  return repeated(get_next_pair, n)(first_pair)[1]

print(fibonacci(0), fibonacci(1), fibonacci(11))
# 0 1 89

Repeatedly apply a linear function using linear algebra

The function lambda a,b: b,a+b which you want to apply happens to be a linear function. It can be represented by a 2*2 matrix. Repeatedly applying the function to a two-element tuple is the same as repeatedly multiplying a two-element vector by the matrix.

This is cool, because taking the power of a matrix is much, much faster than repeatedly applying a function.

import numpy as np

def fibonacci(n):
  return np.linalg.matrix_power(np.array([[0, 1],[1,1]]), n).dot(np.array([0,1]))[0]

print(fibonacci(0), fibonacci(1), fibonacci(11))
# 0 1 89

If you don't like one-liners, here is the same function decomposed on a few lines with more explicit variable names:

import numpy as np

def fibonacci(n):
  next_pair_matrix = np.array([[0, 1],[1,1]])
  matrix_to_the_nth = np.linalg.matrix_power(next_pair_matrix, n)
  first_pair_vector = np.array([0,1])
  nth_pair_vector = matrix_to_the_nth.dot(first_pair_vector)
  return nth_pair_vector[0]

print(fibonacci(0), fibonacci(1), fibonacci(11))
# 0 1 89
Sign up to request clarification or add additional context in comments.

3 Comments

Comparing my reduce version with the for-loop version from your answer by running t0 = time.perf_counter(); y = fibonacci(10000); t1 = time.perf_counter() ten times and taking the average, I get 1.56ms for the for-loop; 3.72ms for the reduce; and 0.125ms for the matrix.
With fibonacci(100000) I get: forloop 74.4ms; reduce 153.4ms; matrix 0.14ms.
Oops, you can forget the time of the matrix version. Apparently the for-loop and reduce versions both use python's big ints, but the matrix version doesn't and returns an incorrect result for fib(100000).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.