Excel's LAMBDA with a "kind of" composite function - excel

Ever since I learnt that Excel is now Turing-complete, I understood that I can now "program" Excel using exclusively formulas, therefore excluding any use of VBA whatsoever.
I do not know if my conclusion is right or wrong. In reality, I do not mind.
However, to my satisfaction, I have been able to "program" the two most basic structures of program flow inside formulas: 1- branching the control flow (using an IF function has no secrets in excel) and 2- loops (FOR, WHILE, UNTIL loops).
Let me explain a little more in detail my findings. (Remark: because I am using a Spanish version of Excel 365, the field separator in formulas is the semicolon (";") instead of the comma (",").
A- Acumulator in a FOR loop
B- Factorial (using product)
C- WHILE loop
D-UNTIL loop
E- The notion of INTERNAL/EXTERNAL SCOPE
And now, the time of my question has arrived:
I want to use a formula that is really an array of formulas
I want to use an accumulator for the first number in the "tuple" whereas I want a factorial for the second number in the tuple. And all this using a single excel formula. I think I am not very far away from succeeding.
The REDUCE function accepts a LET function that contains 2 LAMBDAS instead of a single LAMBDA function. Until here, everything is perfect. However, the LET function seems to return only a "single" function instead of a tuple of functions
I can return (in the picture) function "x" or function "y" but not the tuple (x,y).
I have tried to use HSTACK(x,y), but it does not seem to work.
I am aware that this is a complex question, but I've done my best to make myself understood.
Can anybody give me any clues as to how I could solve my problem?

Very nice question.
I noticed that in your attempts you have given REDUCE() a single constant value in the 1st parameter. Funny enough, the documentation nowhere states you can't give values in array-format. Hence you could use the 1st parameter to give all the constants in (your case; horizontal) array-format, and while you loop through the array of the 2nd parameter you can apply the different types of logic using CHOOSE():
=REDUCE({0,1},SEQUENCE(5),LAMBDA(a,b,CHOOSE({1,2},a+b,a*b)))
This way you have a single REDUCE() function which internal processes will update the given constants from the 1st parameter in array-form. You can now start stacking multiple functions horizontally and input an array of constants, for example:
=REDUCE({0,1,100},SEQUENCE(5),LAMBDA(a,b,CHOOSE({1,2,3},a+b,a*b,a/b)))
I suppose you'd have to use {0\1} and {1\2} like I'd have to in my Dutch version of Excel.
Given your accumulator:
Formula in A1:
=REDUCE(F1:G1,SEQUENCE(F3),LAMBDA(a,b,CHOOSE({1,2},a+b,a*b)))

Related

Calculate the minimum value of each column in a matrix in EXCEL

Alright this should be a simple one.
I apologize in case it has been already solved, but I can only find posts related to solving this issue with programming languages and not specifically to EXCEL.
Furthermore, I could find posts that address a sub-problem of my question (e.g. regarding limitation of certain EXCEL functions) and should solve/invalidate my request but maybe, just maybe, there is a workaround.
Problem statement:
I want to calculate the minimum value for each column in an EXCEL matrix. Simply enough, I want to input a 2D array (mxn matrix) in a function and output an array with dimension 1xm where each item is the minimum value MIN(nj) of each nj column.
However, I want to solve this with specific constraints:
Avoid using VBA and other non-function scripting: that I could devise myself;
All in one function: what I want to achieve here is to have one and one function only, not split the problem into multiple passages (such as for example copypasting a MIN() function below each column, that wouldn't do it);
The result should be a transposable array (which is already ok, I assume);
Where I am stranded with my solution so far:
The main issue here is that any function I am trying to use takes the entire matrix as a single array input and would calculate the MIN() of the entire matrix, not each column. My current (not working) function for an exemplary 4x4 matrix in range A1:D4 would be as below (the part in bold is where it is clearly not working):
=MIN(INDEX(A1:D4,SEQUENCE(4,4,1,1)))
which ofc does not work, because INDEX() does probably not "understand" SEQUENCE() as an array of items to take into account. Another, not working, way of solving this is to input a series of ranges (A1:A4;B1:B4;C1:C4;D1:D4) so that INDEX() "understands" the ranges as single columns, but ofc does not know and I do not know sincerely how to formulate that. I could use INDIRECT() in some way to reference the array of ranges, but do not know how and could find a way by searching online.
Fundamental question is: can a function, which works with single arrays, also work with multiple arrays? Basically, I do not know how to communicate an EXCEL array formula, that each batch of data I am inputting is a single array and must be evaluated separately (this is very easily solved with for() cycles, I know).
Many thanks for any suggestion and any workaround, any function and solution works as longs as it fits in the constrains defined above (maybe a LAMBA() function? don't know).
This is ofc a simplification of a way more complex problem (I am trying to calculate the annual mean temperature evolution for a specific location by finding the value - for each year from 1950 to 2021 - that is associated to the lat/lon coordinates that are the nearest to the one of the location inputted, given a netCDF-imported grid of time-arrayed data; the MIN() function is used to selected the nearest location, which is then used, via INDEX() to find temp data). I need to do this in one hit (meaning just pasting the function, which evaluates a matrix of data that is referenced by a fixed range), so that I can just use it modularly for other data sets. I already have a working solution, which is "elegant"* enough, but not "elegant"* as the one I could develop solving this issue.
*where "elegant"= it saves me one click every time for 1000+ datasets when applying the function.
If I understand your problem correct then this should solve it:
=BYCOL(A1:D4,LAMBDA(d,MIN(d)))

Excel - Row-wise calculation without helpers

Recently I answered a question about how to retrieve the MEDIAN() of each MEDIAN() in a 2-column matrix without helpers, e.g:
The row-wise calculation without helpers wasn't too hard because the median with only two values is always the average. Therefor a simple formula was all it took:
=MEDIAN((A1:A3+B1:B3)/2)
But for curiosity sakes however, wat if I would have at least a 3-column matrix?
The median will actually need to be calculated. Here the medians are {8,2,2}.
I can't seem to find a way to to get a row-wise calculation for 3+columns. In this case it's about MEDIAN() but I can imagine there could be other functionalities. Since this could be simplified data I don't want to resort to something like =MEDIAN(MEDIAN(A1:C1),MEDIAN(......
I tried to fiddle around with OFFSET(), though not a fan of volatile functions I was hoping it would either work directly with an array, or would be triggered correctly through using MEDIAN(LET(X,SEQUENCE(ROWS(A1:A3)),MEDIAN(OFFSET(A1:C1,X-1,0)))). I then moved on to combinations of either MMULT() or LARGE(), however none of my attempt were succesfull.
Question
So the question ultimately is; how do we return the result (array) of an row-wise calculation without helpers. And if not possible, that's also a perfectly fine answer so I can rest my head =)
New Answer
With the new BYROW() function one could use:
=MEDIAN(BYROW(A1:C3,LAMBDA(a,MEDIAN(a))))
The nested LAMBDA() in the 2nd parameter makes it a piece o' cake to loop all rows in a dynamic array (not a range per se).
Previous Answer (Pre-BYROW())
So. After a long thought, as far as my understanding goes this is not possible through current formulae. However, currently in BETA, Excel365 will feature the new LAMBDA() function which makes it possible to create your own function without VBA and even recursively call itself. It isn't the prettiest of solutions but I thought I would share what I did here:
Formula in E3:
=MED(A1:C3,"",ROWS(A1:A3))
Where MED() is our own LAMBDA() function created at the "name manager" menu. It reads:
=LAMBDA(rng,txt,rws,IF(rws=0,MEDIAN(FILTERXML("<t><s>"&txt&"</s></t>","//s")),MED(rng,TEXTJOIN("</s><s>",,txt,MEDIAN(INDEX(rng,rws;0))),rws-1)))
As can be seen there are 4 main parameters of which 3 variables:
rng - The range to be examined.
txt - A reserved variable to be used in FILTERXML().
rws - A counter.
The 4th parameter is a nested IF() which if the counter is as 0 will return the median of all medians. This is done through FILTERXML() which I will not get into detail right now.
If the counter is not yet at 0 it will recursively call the LAMBDA() function untill it is, and what it does is using the same three parameters but we can alter them right there and then. Therefor we leave rng intact, we concatenate the MEDIAN() of the row (current counter) through TEXTJOIN() to create a valid xml construct. And last but not least we need to lower the counter.
It's a struggle, but with LAMBDA() it will now be possible to do a rowwise calculation.
Note, if you are interested in the FILTERXML() construct, you might like this post where I now also included a LAMBDA() version of a SPLIT() function.

Renaming CUBEVALUE function to something shorter?

I've been using a rather long embedded CUBEVALUE() function, which is a pain to work with. It looks something like:
=IFERROR(VALUE(CUBEVALUE(arg1;arg2;arg3));CUBEVALUE(arg1;arg2;arg3))
Due to the CUBEVALUE function and its arguments, it's becoming a REALLY long function and thus not easy to work with. Since there are only 3 arguments, which are written in different cells, I'd like to create something like
=MyFunction(A1,A2,A3)
and use A1, A2 and A3 as "arg1, arg2, arg3" in the function mentioned first. This way its possible to "pull" the function so it would calculate using the input in B1:B3 and C1:C3 etc. as well.
The function works fine and can be pulled through and such, but my question is how to rename this loooong function into something more user-friendly, as it requires only 3 cells as an input and the rest of the text in the function just makes it hard to use for end-users.
Using UDF is not an option because CUBEVALUE can't be called through VBA... and any attempt to stich strings together and using the final result with INDIRECT also seems to fail..
In a similar question on this site, someone refers to using "asynchronous UDF's", but no further information was given (and what I could find seemed irrelevant).
You shouldn't really have several long cube functions. Allocate some space in hidden rows/columns or in header rows/columns to add your cubemember functions. Then throughout most of your report, you should just have cubevalue functions that reference other cells with error handling around them. Proper use of absolute and relative references are your friend.
Peter Meyers has some great tips for this here, slides 20 - 24. I have an example Excel file with cube functions on my blog here.

Array evaluation of find_text argument in SEARCH() function

Say I have the following:
Entering the following formulas in cell C1 and then clicking Evaluate Formula->Evaluate produces very different results:
Formula 1: B$1:B$5 evaluates as non-array
{=SEARCH(B$1:B$5,A1)}
Formula 2: B$1:B$5 evaluates as an array
{=IF(SEARCH(B$1:B$5,A1),"")}
Why, exactly, is this? What is the cause of this behavior? If possible, please provide other examples using other Excel functions to illustrate what is happening here.
Parenthetically:
My question came about while experimenting with the accepted answer to this question.
In general, an array of values will only be returned by a worksheet function given that the following two conditions are satisfied:
1) The formula in question is either in itself capable of returning an array of values, or else is contained within a larger set-up of several functions, one or more of those which precede the function in question (and therefore act upon it) having that property. Whether that capability is something which requires coercion (i.e. via array-entry (CSE)) or is an in-built feature of the function is not important in terms of the answer you are seeking.
2) The array generated must be passed to a further function for processing. Excel is more teleological than you think: it has no great belief in returning an array of values as an end in itself.
As for your example, it's not that SEARCH, when array-entered, isn't capable of processing arrays (it is). It's more that there is no further function incited which is to act upon that array. In the IF version, there is precisely that, though again, if you process that one more time you'll find that your current array is reduced to just the first element in that array. Wrap a further function around the IF, e.g. SUM, and you'll be able to go one step further, and so on and so on.
And here is a major difference between evaluating formulas via the Evaluate Formula tool, and repeated "evaluation" via selecting various parts of the function in the formula bar and pressing F9.
The latter will always return an array of values, whether the above two conditions are satisfied or not. However - and not many people realise this - the "evaluation" so obtained can, ultimately, lead to incorrect results, and so should only be used providing one is aware of its limitations.
Take the following example, for instance:
With A1:A10 empty, the formula:
=SUMPRODUCT(0+(A1:A10=""))
correctly returns 10.
Now select just the part A1:A10 in the formula bar and press F9. Excel, being forced to "evaluate" the range, returns:
=SUMPRODUCT(0+({0;0;0;0;0;0;0;0;0;0}=""))
which, on further processing, results (correctly, it would seem) in the quite different result of 0.
Regards

Excel Named Function Parameters or UDF without Macros

It seems like a bit of an omission that there's no easy way to create a user-defined declarative function in Excel without defining a macro. I can't use XSLM with the uphill battle that will entail in the Enterprise, but I want to be able to define a function with intent thus.
I want to do this;
=BreakEven(C1:C20)
But I can't use a macro, although I can use a "named formula". The trouble is how to pass parameters to that? I've seen a couple of tricks (kludgy workarounds) but not for xslx.
I'd like to be able to define a Breakeven() function in another tab and reference it here passing in MORE THAN one parameter, two ranges in fact. I'm sure there's some way using string parsing but I can't see it.
I don't mind if the function doesn't look exactly like that, as long as it evaluates within the cell and I can parse it for 'intent'. For instance, this example (http://www.jkp-ads.com/articles/ExcelNames09.asp) which I was unable to get to work in xlsx uses this syntax;
=IF(ROW(D3),CellColor)
Where "cellcolor" is the name of the function and D3 is the range parameter. The other solution I'm toying with is to define a function in column format with a variable argument list (this is two rows of an excel spreadsheet);
[Value][function][parameter1][parameter2][parameter3]
24050 BreakEven C1:C20 A1:A20
It's not pretty, but the benefit of the latter is that it describes the function to an external reader. We know it's a breakeven function, whereas if we put the actual formula "OFFSET,INDIRECT,SUM()()()()etc" it would not be readable/parseable. Of course, in that case, I'd have to construct the value field by parsing the cells to the right in Excel, which would make the Value formula messy but at least it would be a self-describing row.
Can anyone suggest a better method?
Poor-man's UDF
So I think what we're going to have to do is this;
A B C D E
1 [Value][function][parameter1][parameter2][parameter3]
2 24050 BreakEven C1:C20 A1:A20
3 111 mySum 1 10 100
Where "BreakEven" is a "named function". Here's the formula for "mySum";
=sum(C1:E1)
To evaluate functions listed in B, we just put this in column A (transposing the same value for all rows in column A;
=value(B)
This works because A2 and A3 both evaluate column B as a value, which causes BreakEven and Sum to run (as poor-man's UDFs) in the context of A2 and A3. The range (C1:E1) is relative of course.
So in effect, we can write any function name in column B (as long as there's a corresponding named function defined in the workbook which can be as complex as you like). Columns C, D and E act as the parameters for the function on the same row.
I would have loved to just be able to write the following in column A instead;
=mySum(1,10,100)
But in the absence of that support, the mechanism above serves to provide a readable parameterised function that would be understandable by a user, that's also machine readable (works in CSV too) and allows us to offload our re-usable functions to a library sheet somewhere in the workbook for maintenance.
Not perfect, but an acceptable compromise, unless anyone has a clever way of doing this in a single cell?
Not really an answer, but easier to illustrate here than in a comment. Although you can't rename formulas in a simplistic way - I like your suggestion actually I've never thought about that before; but then I've never worked in a non-macro environment so this has never occurred- you can add notes into the actual formula explaining what it does. For example:
=N("This is a really complex BreakEven Formula")+SUM(3,4,5)
Is a perfectly valid formula. As I said, not really an answer, but could potentially add clarity to a complex formula
You can do this with a small trick
For example to create effectively a cuberoot UDF that emulates =cuberoot(x) then name a variable as cuberoot with a 'value' like this.
=(RC[-1])^(1/3)
Now you can either do this using a temporary switch to RC mode, or put the cursor in say cell E5 and type the name value as =(D5)^(1/3)
Now whenever you need a cuberoot you can put the argument in any cell and put =cuberoot in the cell to its right. It really works and follows true Excel rules.
I use it for multiparameter models that have the single 'argument' Time as a dependent variable. I then define the term Model as the model equation eg =a+bTime+cTime^2
where a,b,c are already named locations holding unique parameter values -
and then define Time as =RC[-1]
My sheets are filled with cells simply saying =Model and have the required time value to the left (ie their argument). It is simple to extend to multi arg functions using multiple cells. It usually fits in well with spreadsheet layouts. Change the definition of your model once in the define name box and all places change simultaneously.
I have a function called ToDMS which takes the decimal degree value in the preceding cell and converts it to a deg Min and Sec string - very tidy.
You need the degrees to be in a single cell but want it in the alt. form in another cell
elegant, simple and it works
Bob Jordan

Resources