Jan 05 2021 02:03 PM - edited Jan 05 2021 08:22 PM
In this post, I would like to explain how I have used Lambda to create a function to generate a Fibonacci series array.
This example can also be used to understand how to create an array where the value of an element in the array depends on previous elements in the same array.
This function has been created using three function in two layers. The inner layer functions include the following:
InFib: This function generates the Nth Fibonacci number
InFibSer: This function generates the entire Fibonacci series up to the Nth number
The two functions mentioned above require arguments that are complicated and less intuitive. Therefore, we use an outer layer function (FibSeries) that generates the necessary argument and calls the two functions to generate the series.
=InFIB
'This function returns nth Fibonacci using tail recursion
'It takes three arguments n, a=0 , b=1 as argument and returns the nth Fibonacci value
=LAMBDA(n,a,b,IF(n=1,a,IF(n=2,b,Infib(n-1,b,a+b))))
/* The value for the arguments a and b should be passed as 0 and 1, respectively. */
Example:
=InFIB(10,0,1) would generate 34 as the value
Our objective, here, is not to obtain a single value but to generate an entire array. However, here is where we have a challenge: While it is easier to manipulate an existing array, it is far more to difficult to create a new array (other than sequence array) or extend an existing array.
Therefore, in order to create a Fibonacci series, we are going to pass a dummy array of n values. This array will have 0 as the first element and 1 as the value for all the other elements.
The following image portrays the recursive addition process that converts that the initial array into a Fibonacci series.
The following code does carries out the process recursively till the maximum value of the array equals the nth Fibonacci value.
=INFIBSER
'This inner function takes three arguments and returns a Fibonacci series
'The three arguments are nth Fibonacci value, the initial array and a sequence array (sequential numbers from 1 to n)
'Note, the sequence array can instead be generated inside the function but it may inefficient
=Lambda(NthFib, Initval, Sqn,
Let(
Maxval,Max(Initval),
If(Maxval = NthFib,
Initval,
Let(
NewVal,If(Maxval=1,Initval+1*(Sqn>=4),
Let(
StPos,Match(Maxval,Initval,0)+1,
adval,Large(unique(Initval),2),
Initval+adval*(Sqn>=StPos)
)
),
InFibser(NthFib,NewVal,Sqn)
)
)
)
)
In the first iteration, the above function adds one to all the values from the fourth position onwards. For the subsequent iterations it adds the second largest unique value to all values that all 2 places after the position of such second largest value. The above function uses the match function to identify the position pointer.
Although the above function can generate the Fibonacci series, as mentioned earlier, it requires arguments that are less intuitive and which requires some set up.
Therefore, here we have an outer layer function that requires only one argument, n. It creates the necessary argument to be passed to the inner layer function and calls them.
=FIBSERIES
'This outermost function generates an initial array, a sequence array, and nth fibonnaci value
'It then calls the inner functions with these parameters
=Lambda(n,
Let(
Sqn,sequence(n),
initval,sign(Sqn-1),
Nthfib,Infib(n,0,1),
InFibser(Nthfib, Initval, Sqn)
)
)
This final function can be used by the user to generate the Fibonacci series by merely passing the number of values required as argument.
Hope you found this useful. I would love to know if you think there is a way to improve the algorithm, here. I am especially looking for suggestions that could replace the match / xmatch in the InFibSer function to get the position pointer. Since the positions move sequentially, I believe a mathematical solution may exist.
Thank you
Apr 15 2023 08:34 AM
Oops, It took time to understand the formula, still don't sure if I took it correctly or not. Fibonacci and efficiency don't matter, approach is great.
Apr 15 2023 11:48 AM - edited Apr 15 2023 12:04 PM
@lori_m this is an interesting idea, I was trying to test it, we need to treat the special case of n=1, because it provides the wrong result {1;1}, but it is an easy fix:
Fib=LAMBDA(n,IF(n=1, 1,REDUCE(1,SEQUENCE(n-1),
LAMBDA(b,_,VSTACK(b,SUM(TAKE(b,-2)))))));
I don't fully understand the underscore notation (_) I google it, but I was not able to find any reference. Would you elaborate it more or share some link? thanks. My intuition, from other languages, it is a dummy variable, or unnamed variable, because it is not really used for other purpose than to define the number of iterations, but their values are not really used.
I modified it to use BIGADD. I incorporated @mtarler's fix:
BigAdd = LAMBDA(A, B, [c], let(cc, if(isomitted(c),0,c),
if((len(a)>14)+(len(b)>14),
let(lowadd, right(a,14)+right(b,14)+cc,
ccc, if(len(lowadd)>14,1,0),
aaa, if(len(a)>14,left(a,len(a)-14),0),
bbb, if(len(b)>14,left(b,len(b)-14),0),
bigadd(aaa,bbb,ccc)
& text(right(lowadd,14),"00000000000000")),
(a+b+cc)&"")));
Here is a possible solution for large Fibonacci numbers, I think we need to separate the cases n=1, n=2 from the iteration process to make it works and then to generate the sequence from n-2:
FIBO_SERIE_STR_LM2=LAMBDA(n,IF(n<3, SEQUENCE(n)^0&"",
REDUCE({"1";"1"},SEQUENCE(n-2),
LAMBDA(b,_,VSTACK(b,BIGADd(@TAKE(b,-1),
@CHOOSEROWS(b,-2)))))));
It works as expected. In terms of performance about the same as your previous formula using recursion. Which is expected since this approach is similar to mine and similar to @mtarler, but your is more concise and invokes less functions, I have an extra HSTACK and INDEX for example, so I would expect that for larger dataset your approach will show some performance improvements.
David
Apr 15 2023 12:57 PM
Maybe you understand @lori_m 's formula but I am still struggling! I even tried pulling the formula apart using local names but, though the formula still works, there has not been a huge leap forward in terms of its transparency.
= LET(
identityλ, LAMBDA(i,i),
k, SEQUENCE(10),
fibArrλ, SCAN(identityλ, k,
LAMBDA(fnλ,_, LAMBDA(i,
i*fnλ(0)+fnλ(1)
))
),
MAP(fibArrλ, LAMBDA(fibλ,fibλ(0))
)
)
Apr 15 2023 01:14 PM
@davidleal In response to the points you raise...
Underscore _:
This is used to specify an unused variable in Python, I don't know if this convention is more widespread. See Fibonacci code further down this link
https://realpython.com/python-itertools/#recurrence-relations
underscore is used both within accumulate (equivalent to SCAN) and within the for loop
Case n=1:
If one decides to follow a strict convention then yes it makes sense to add n=1 case. On the other hand if Fibonacci is considered more as a playground of ideas (as per Sergei's comment) such details are of less significance and inserting extra conditions may distract from the essence of a chosen approach. In any case since the sequence requires a pair of initial values, either (0,1) or (1,1). any of 0, 1 or #error may be considered possible conventions for n=1.
Apr 15 2023 02:30 PM
I think the idea of replacing arrays by functions arose after attempting the FFT challenge you posted for general radix. One can't hold real and imaginary 2D arrays simultaneously, but instead one can add a lambda parameter to choose between the two arrays - a bit like adding a third dimension.
In the formula, the function argument of SCAN is given by:
LAMBDA(f,_, LAMBDA(i,i*f(0)+f(1)))
The idea here is to rewrite the pair (x, y), say, as a function: f(i) i=0,1. i.e.
f(i) <- i*f(0)+f(1), i = 0,1
or more explicitly
f(0),f(1) <- f(1),f(0)+f(1)
Similarly if we set as initial value:
f = LAMBDA(i,i), i = 0,1
then
f(0),f(1) <- 0,1
The initial value of i needs to be input which was a problem for an array of lambdas so I ended up passing to MAP instead,. No doubt there's numerous improvements to be made - I'd be interested in other ideas to so this kind of thing.
Apr 15 2023 03:51 PM
Hi Lori, by the time you got to the third formula there was the start of something familiar! I have no idea how you managed to dream that up as a strategy but I am at least in with a chance of following it!
On a different matter, I asked the the AI search engine in Edge what is significant about the sum of the squares of two adjacent Fibonacci numbers and it came back with a formula and the statement: "if you square two consecutive Fibonacci numbers and add them, you get another Fibonacci number that is twice as far in the sequence". Search engine technology seems to be progressing!
Apr 21 2023 09:53 AM
I wasn't aware of that sum of squares identity! After some reflection, I think it can be derived from the 2x2 matrix representation which also provides a fast calc of larger Fibonacci numbers,
=INDEX(REDUCE({1,1;1,0},SEQUENCE(10),LAMBDA(a,_,MMULT(a,a))),2,1)
This matches @davidleal's FIBO_SERIE_STR_LM2(1024) last entry to 15sf as well as this alternative:
Fib(n)
=TEXTBEFORE(
SCAN(
"0,1",
SEQUENCE(n),
LAMBDA(a, _,
TEXTAFTER(a, ",") & "," &
BigAdd(TEXTBEFORE(a, ","), TEXTAFTER(a, ","))
)
),
","
)
Apr 21 2023 01:13 PM
I started by looking at
= F(m)*F(n) + F(m+1)*F(n+1)
setting m=1, and applying the basic recursion formula to F(n+1). As usual, once one has finished you know what to search for and can find the answer.
Apr 21 2023 09:19 PM - edited Apr 22 2023 11:00 AM
@lori_m just a minor correction to your first formula using matrix exponentiation, in order to make it works:
FIBO_MAT=LAMBDA(n, LET(a, {1,1;1,0}, IF(n=1,1,
INDEX(REDUCE(a,SEQUENCE(n-1),LAMBDA(ac,_,MMULT(ac,a))),2,1))))
Rather than the exponential matrix of the accumulator (ac), it is from the initial matrix (a), so I created a name for that. I haven't done any performance testing, but anyway I have seen this matrix multiplication can be simplified, calculating directly each element, so there is no need to do a matrix multiplication (resulting in O(log n)). I would say this would improve the performance. For example the following python code, uses this idea, but I don't know if it has an easy conversion to Excel (Fast Exponentiation)
def fib(n):
v1, v2, v3 = 1, 1, 0 # initialise a matrix [[1,1],[1,0]]
for rec in bin(n)[3:]: # perform fast exponentiation of the matrix (quickly raise it to the nth power)
calc = v2*v2
v1, v2, v3 = v1*v1+calc, (v1+v3)*v2, calc+v3*v3
if rec=='1': v1, v2, v3 = v1+v2, v1, v2
return v2
I guess the logic is based on this mathematical explanation (video) and also here another python implementation using fast exponentiation with the mathematical justification.
Regards,
David
Apr 22 2023 12:44 AM
Yes, the wikipedia entry was quite long and I hadn't noticed it before. Regarding the SCAN version above, I recall seeing some examples from you applying string concatenation in a similar way, this one compared favourably against the other REDUCE/VSTACK approach (0.2s vs 0.7s for 1024 elements)
If there were an efficient BigMul function available maybe we could make use of that kind of approach in Excel but otherwise i think we are limited by the precision.
Apr 22 2023 02:14 PM
@davidleal so I did not read up on any of this but do have 2 questions:
a) Why are you using a [2x2] * [2x2] instead of [1x2] * [2x2] ? Basically aren't you using the matrix multiplication to hold your prior value so [a b] * [ 1,1; 1,0] => [a+b, a] and you don't need the second row?
b) Why are you then using INDEX( .... ,2, 1) which is essentially the value 1 calculation behind instead of SEQUENCE(n-2) and INDEX(... 1,1) or in my suggestion just INDEX(..., 1)
I don't see any time performance improvements using my crude timer but it just makes sense to me:
FIBO_MAT=LAMBDA(n, LET(a, {1,1;1,0},
IF(n>2,
INDEX(REDUCE({1,1},SEQUENCE(n-2),
LAMBDA(ac,_,MMULT(ac,a))),1),
1)))
Apr 22 2023 11:55 PM - edited Apr 22 2023 11:56 PM
I'm glad you raised this point as I think there was a misunderstanding in the "corrected" version - probably due to being insufficiently clearly expressed on my part. The REDUCE example was actually designed to show that Fib(1024) could be calculated in just 10 steps by successively squaring the 2x2 matrix. The video David linked to extends this method to calculate arbitrary fibonacci numbers faster by using the binary representation of a number to decompose into powers of 2. Iterating the 2x2 matrix multiplication misses the point somewhat.
Apr 24 2023 12:43 PM - edited Apr 24 2023 12:44 PM
I realize this solution is not the most efficient but it was fun to create and it's a bit different (Inspired by Pascal's Triangle).
'DiagonalSum
=LAMBDA(a,v,VSTACK(
a,
IF(v < 3, 1, SUM(IFERROR(COMBIN(SEQUENCE(v - 1, , v - 1, -1), SEQUENCE(v - 1, , 0)), 0)))
))
'Fib
=LAMBDA(elements,REDUCE(0, SEQUENCE(elements), DiagonalSum))
Apr 24 2023 03:16 PM
I think I am becoming conditioned! I only have to read the words Pascal's triangle and I find myself writing
=REDUCE(1, SEQUENCE(n),
LAMBDA(triangle, k,
LET(
previous, TAKE(triangle, -1),
left, HSTACK(previous, 0),
right, HSTACK(0, previous),
IFNA(VSTACK(triangle, left + right), "")
)
)
)
Apr 24 2023 03:43 PM
Does that create a pyramid-like array?
I went with a left-justified version:
'Pascal
=Lambda(n,DROP(
REDUCE(
"",
SEQUENCE(, n),
LAMBDA(a, v, VSTACK(a, EXPAND(COMBIN(v - 1, SEQUENCE(, v, 0)), , n, "")))
),
1
))
Apr 25 2023 01:56 AM
No, like yours it is left stacked. Far more effort would be required for they layout than was needed for the calculation! The main difference is that mine is more primitive in that it uses the recurrence relation from row to row rather that the functional form of the result.
Since you mention it though, how about this?
= REDUCE(1, SEQUENCE(n),
LAMBDA(triangle,k,
LET(
previous, TAKE(triangle, -1),
left, HSTACK(previous, {0,0}),
right, HSTACK({0,0}, previous),
triangle, VSTACK(HSTACK(0,triangle), left + right),
IFERROR(IF(triangle,triangle,""),"")
)
)
)
Apr 25 2023 05:50 AM
@Peter Bartholomew OK all this inspired me to bring it back to this topic and create the "classic" fib spiral:
=LET(n,10, LET(a, {1,1;1,0}, IF(n>2,
DROP(REDUCE({1,1;1,1},SEQUENCE(n-2,,0),LAMBDA(ac,n,
LET(fibNs,TAKE(ac,1,2),
fibSp,DROP(ac,1),
fibNextPair,MMULT(fibNs,a),
fibNext, INDEX(fibNextPair,1),
fibNextBlock, SEQUENCE(fibNext,fibNext,fibNext,0),
fibSpiral, CHOOSE(MOD(n,4)+1,VSTACK(fibSp,fibNextBlock), HSTACK(fibSp,fibNextBlock), VSTACK(fibNextBlock,fibSp),HSTACK(fibNextBlock,fibSp)),
VSTACK(fibNextPair,fibSpiral) ))),1),
"n must > 2")))
Apr 25 2023 05:56 AM
Apr 25 2023 07:27 AM
I didn't even think of trying that! How about a bit of conditional formatting?
Every workbook should have one?
May 11 2023 03:30 PM
Following up on the earlier BigMul idea, it seems one could apply convolution to the digits, e.g. 123*321 could be translated to
=CONVOLVE({1,2,3},{3,2,1})
giving {3,8,14,8,3} then carry tens to return 39483. Combining with a simplified version of the @davidleal python method above (which appears to come from here ),
def fib(n):
a,b = 0,1 # initialise matrix [[a+b,b] ,[b,a]]]
for rec in bin(n)[3:]:
a,b = a*a+b*b,b*(2*a+b) # square of matrix
if rec=='1' : a,b = b,a+b # multiply by initial matrix
return b
returned values for fib(10000) in agreement to several thousand digits. The FFT convolution method in the attachment is from this gist courtesy of @aaltomani