Forum Discussion
Lambda Example: Generate Fibonacci series
I rarely use recursion these days, preferring instead to use Lambda helper functions. Occasionally it might be a built-in SCAN or BYROW but, more often, it is a mocked-up version built from REDUCE/VSTACK since it is unusual to require an array of scalars; an array of arrays is far more useful and more frequently encountered.
For the Fibonacci series, one needs to take an array of 2 values forward so
Fibonacciλ(priorTerms, [currentData])
= LET(
F₁, INDEX(priorTerms, 1),
F₂, INDEX(priorTerms, 2),
HSTACK(F₁ + F₂, F₁)
)
The vertical SCAN is achieved by using
SCANV(initialValues, scannedArray, Fnλ)
= REDUCE(
initialValues,
SEQUENCE(1, ROWS(scannedArray)),
LAMBDA(accumulation, parameter,
LET(
priorResult, CHOOSEROWS(accumulation, -1),
currentData, CHOOSEROWS(scannedArray, parameter),
newResult, Fnλ(priorResult, currentData),
VSTACK(accumulation, newResult)
)
)
)
In this instance, the scanned array does not figure in the calculation so the function could be simplified, but at the cost of generality. For the Fibonacci series only one column of the results is needed, so
= LET(
k, SEQUENCE(n-1),
f, SCANV({1,1}, k, Fibonacciλ),
TAKE(f,,-1)
)
returns the first 1,000 Fibonacci numbers in 100 ms. I couldn't extend the test that much without hitting #NUM! overflow errors.
Out of interest, I had lifted the SCANV function directly from a workbook that calculated the motion of a container slung from an overhead crane, by tenths of a second for 20 secs. That required 4 variables at each step, namely the cart position, the pendulum angle subtended by the container and the corresponding velocities. The name of the game was to provide force inputs that led to a final state in which the container was hanging vertically with zero velocity since placing a swinging cargo on a truck is tricky.
The parameters were optimised by using Solver to reduce the terminal energy of the system, though lori_m has written optimisation routines as Lambda functions!
= SCANV(X₀, t, RK4Stepλ(Dλ))
PeterBartholomew1 I don't know if at the time you replied you haven't seen the update I did to my post in the UPDATE section. I don't think any other approach can beat this one FIBO using the https://math.libretexts.org/Bookshelves/Applied_Mathematics/Book%3A_College_Mathematics_for_Everyday_Life_(Inigo_et_al)/10%3A_Geometric_Symmetry_and_the_Golden_Ratio/10.04%3A_Fibonacci_Numbers_and_the_Golden_Ratio:
=LAMBDA(n,ROUND(POWER((1+SQRT(5))/2,n)/SQRT(5),0))
I tested it for FIBO(SEQUENCE(10000)) and in the worst-case scenario, it took 20ms, so it serves to get a single Fibonacci number or a series. Short, no recursive, and low computation effort. It reminds me of this heuristic rule that I heard once, :-):
T+S+H=C
Thinking (T) plus Software (S) plus Hardware(H) is equal to Constant(C). This is what the Binet formula does. Simplified the mathematical formulation, then you can minimize the Software and Hardware effort/investment, in our case Software is Excel and Hardware is the computer where it runs.
Thanks for your replay, similar to my approach (formula 2 in my post) using REDUCE to avoid the recursion.
- PeterBartholomew1Apr 13, 2023Silver Contributor
Please accept my apologies. I hadn't noticed your use of the Binet formula. I simply revisited the problem as an exercise, since the original Viz post seemed to predate the Lambda helper functions. Somewhat oddly, I did use the Binet formula to determine why my pseudo-SCAN failed with #NUM! errors long before I reached the 10,000th term you mentioned. I hit overflow errors somewhere before the 1,500th term which in turn meant that my formula returned nothing but the error.
The main takeaway for me from the exercise is that the inbuilt Excel functions SCAN, MAP, BYCOL, BYROW are all inadequately scoped since the nested array should be the basic building block of the functionality, not an error. As it stands, the use Lambda helper functions in formulas can be more restrictive than relying on the underlying array methods they support, since the lifting and broadcasting behaviours of the array formula are not generalised as one might hope.
- davidlealApr 13, 2023Iron Contributor
PeterBartholomew1 not a problem at all, indeed your approach is very interesting too. I realized that the problem of numerical precision is affecting not just the Binet formula, but also the initial approach I provided in my post (formula 2). Check my reply to lori_m. Using the REDUCE approach starts to fail for getting the 74th Fibonacci number, even it involves just additions. Have you tested your formula for this number or greater than this one?, unless the Python algorithm is also wrong, but it involves just additions too.
- PeterBartholomew1Apr 13, 2023Silver Contributor
I worked on the assumption that the integer calculation was exact until I reached 15 significant digits after which Excel does not report the desired precision so the numbers returned are only approximations. The approximation will become larger as one proceeds.
- lori_mApr 13, 2023Iron Contributor
Indeed, the same Binet formula was mentioned in the original reply to Viz 's comment further down this post,
https://techcommunity.microsoft.com/t5/excel/lambda-examples-distance-between-two-cities/m-p/2035287/highlight/true#M85173One reason to consider recursive alternatives to the Binet formula is numerical accuracy, see e.g.
https://bosker.wordpress.com/2011/07/27/computing-fibonacci-numbers-using-binet%E2%80%99s-formula/
Additionally the Fibonacci sequence provides a nice testing ground for different conceptual approaches to functional programming, Peter provides a mechanical example of this above. Another approach could be to use SCAN with an accumulator such as LAMBDA(i,IF(i,a,b)) as a means of overcoming array truncation even if this may be not as efficient, I'll try and post an example soon..- davidlealApr 13, 2023Iron Contributor
lori_m thanks for your feedback, the first link refers to another post not to any specific answer in this post that is why I was not aware of it. About the numerical accuracy, I was aware that for Python the Binet approach provides a wrong result starting from 72 fibonacci number. I checked that for this number Excel provides the correct result using Binet formula, but now looking at your link I tested more numbers and I realized that starting from 74, the result is different. I verified it https://www.programiz.com/python-programming/online-compiler/ using the following python approach that is really fast:
import math def fibo(n): a, b = 0, 1 for i in range(n): a, b = b, a + b return a print(fibo(74))
But the problem is not the numerical precision of the approximate method. I tested the second formula of my post, which doesn't involve a numerical calculation other than addition:
FIBO = LAMBDA(n,REDUCE({1;1},SEQUENCE(IF(n<=2,1,n-2)), LAMBDA(ac,a, IF(n<=2,1,LET(x_1,TAKE(ac,1),x_2,DROP(ac,1), IF(a=(n-2),x_1+x_2, VSTACK(x_2, x_1+x_2)))))))
and for 74 and returns: 1304969544928660, but the Python formula returns 1304969544928657 and we are not close to the https://support.microsoft.com/en-us/office/data-types-in-data-models-e2388f62-6122-4e2b-bcad-053e3da9ba90 number 9223372036854780000
Thanks,