Excel Named Function Parameters or UDF without Macros - excel

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

Related

Returning a column reference from MATCH to avoid using INDIRECT with a Named Range

TL;DR: I'm basically trying to obtain a column range such as 'Sheet 1'!$A:$A where the A is obtained by matching the contents of a given cell to a 1:1 range within a sheet referenced by another given cell, for use in a dynamic range.
In the highly probable case where that made zero sense, here's an illustration:
PARAMETERS: A2 = "LIST" | C2 = "FirstName" | Desired result: 'LIST'!$A:$A
And I've obtained that, BUT, I can't use that output ('LIST'!$A:$A) within formulas (namely to create a dynamic range). For instance, here 'LIST'!$A:$A contains 101 cells with values in them:
V3 = NamedFormula = 'LIST'!$A:$A
COUNTA(INDIRECT(V3)) = 101
COUNTA(INDIRECT(NamedFormula)) = 1 because it evaluates to #VALUE and that is a singular result.
Before delving into the topic of using INDIRECT with a Named Range (which I've read about and am still getting over my confused grief), I'm realizing my Names are getting a bit out of hand. I tend to use Excel like a mad scientist. So, in case there's a much simpler solution to what I'm trying to do, here's my actual mission:
0. I'm building a tool to simplify a process where email addresses are built from different data, which needs to run without any scripts, only formulas.
1. A tab with no imposed name would contain a user database with minimally (firstname and lastname OR IDs) AND (potentially other data columns) in no specific order. Tool users would import that tab from wherever the data got to them depending on the client, and would only need to copy-paste relevant headers to the main tab without changing anything else here for data integrity.
2. The main tab would have specific input fields where tool users would paste in the name of the imported tab as well as the labels of the columns they need (for instance, the labels in the first row of the columns containing the first name and the last name), and an input field for the domain name to use to build those email addresses.
3. A Data tab is referenced for cleaning and preparing strings for email address formats.
4. The Export tab would spew out a list of clean email addresses that can be exported to CSV.
The Data tab is just 2 columns to use with SUBSTITUTE so that for instance apostrophes are removed but accented letters are normalized (é -> e). I've used LAMBDA within Names to get there. The problem is to tie everything in - to get those Named ranges into the final formula.
The Names I'm using so far (I'd like to use fewer but testing specific parts extended beyond simple usage I fear):
ALPH ={"A";"B";"C";"D";"E";"F";"G";"H";"I";"J";"K";"L";"M";"N";"O";"P";"Q";"R";"S";"T";"U";"V";"W";"X";"Y";"Z"}
LABELS =LAMBDA(labelname,ADDRESS(2,MATCH(labelname,INDIRECT("'"&PARAMETERS!$A$2&"'!$1:$1"),0),1,1,PARAMETERS!$A$2))
RANGECOL =LAMBDA(labelname,COLUMN(INDIRECT(LABELS(labelname))))
RNCOL =LAMBDA(label,"'"&PARAMETERS!$A$2&"'!$"&INDEX(ALPH,RANGECOL(label))&":$"&INDEX(ALPH,RANGECOL(label)))
I haven't tied everything in the Data tab yet - I'm still trying to automate my main tab before pushing further and using the Data tab substitutions on top of everything. That will be the next step, not my current focus. But, for the curious and interested, on the Data tab I'm using something something I found on ablebits which works wonders =]
So, now if I use the offset range with a static LIST!A:A it works:
=IF($C$2<>"",LOWER(INDEX(OFFSET(INDIRECT(ADDRESS(2,MATCH($C$2,INDIRECT("'"&$A$2&"'!$1:$1"),0),1,1,$A$2)),0,0,COUNTA(LIST!A:A)-1,1),ROW())),"") &IF($C$3<>"","."&LOWER(INDEX(OFFSET(INDIRECT(ADDRESS(2,MATCH($C$3,INDIRECT("'"&$A$2&"'!$1:$1"),0),1,1,$A$2)),0,0,COUNTA(LIST!A:A)-1,1),ROW())),"") &"#"&$C$4
But when I try to use the dynamic RNCOL($C$3) it does not:
=IF($C$2<>"",LOWER(INDEX(OFFSET(INDIRECT(LABELS($C$2)),0,0,COUNTA(INDIRECT(RNCOL($C$2)))-1,1),ROW())),"") &IF($C$3<>"","."&LOWER(INDEX(OFFSET(INDIRECT(LABELS($C$3)),0,0,COUNTA(INDIRECT(RNCOL($C$3)))-1,1),ROW())),"") &"#"&$C$4
This just gives #REF, and evaluating shows the digression starting at INDIRECT(RNCOL($C$3)) equating to #VALUE.
I'm starting to see double here but my undying and completely normal love for Excel prevents me from going home from work as I'm way too far down the rabbit hole to let my obsession die here.
Any pointers as to how this can work?
Note - all of the names in the supplied sheet were generated by an online fake name generator, nothing in here is actual user data #GDPR
Thanks in advance! <3
Test sheet is available via Google Drive.
Your current set-up is not good for many reasons, and in my opinion would require a complete overhaul, the scope of which lies beyond a response on this website.
As to a 'quick fix' to your current issue, the reason your formula in E1 is currently returning an error is due to the fact that, as you can see via stepping through with the Evaluate Formula tool, the part
COUNTA(INDIRECT(RNCOL($C$2)))-1
is resolving to
COUNTA(INDIRECT({"'LIST'!$A:$A"}))-1
and this is not the same as
COUNTA(INDIRECT("'LIST'!$A:$A"))-1
in that the value being passed to INDIRECT is an array in the former though not in the latter. Although INDIRECT can accept arrays, it is only within certain constructions in conjunction with other suitable functions; here it will simply error.
And the reason that it is returning an array is due to the fact that RNCOL($C$2) is returning an array, and that is because that function is defined as
=LAMBDA(label,"'"&PARAMETERS!$A$2&"'!$"&INDEX(ALPH,RANGECOL(label))&":$"&INDEX(ALPH,RANGECOL(label)))
and, since RANGECOL($C$2) resolves to 1 here, the above is equivalent to
"'PARAMETERS!$A$2'!$"&INDEX(ALPH,1)&":$"&INDEX(ALPH,1)
Here, because you are omitting the column_num parameter from INDEX, the part
INDEX(ALPH,1)
is resolving to
{"A"}
which is an array (albeit one comprising a single value) and technically different from
"A"
In most circumstances, this is not an issue. As such, it is almost always unnecessary to pass both a row_num and column_num parameter to INDEX when indexing a one-dimensional array. Here, however, it matters.
You can resolve this by explicitly including a column_num parameter, i.e. redefine RNCOL as
=LAMBDA(label,"'"&PARAMETERS!$A$2&"'!$"&INDEX(ALPH,RANGECOL(label),1)&":$"&INDEX(ALPH,RANGECOL(label),1))

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

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)))

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 Serial If statements

I'm new to Excel, and I'm struggling with a formula. Essentially, what I'm looking for is to filter a cell through a set of procedures using a formula (this part isn't strict).
For example
Let's say I have a cell, A1. I'm trying to perform different calculations on this based on whether it is between a certain range of values. The problem is, it can be within several ranges.
Pseudo-Code representation
If(A1 > 187.5) {
// Run some code here.
}
If(A1 > 150) {
// Run some code here.
}
NOTE : The above example is only to illustrate the logic of sequential if statements.
Note that I Do not want a nested If statement. I'm just trying to run the same value through various checks. How do I do this in an Excel formula?
The best I can come up with is something like the following.
=(A1>187.5)*<some expression>+(A1>150)*<some expression>+....
The result of this will be some single value. (The straightforward way to get multiple values is to have the individual terms in separate cell.
If you want the result to reflect one among several mutually exclusive outcomes, then you would want to go with:
=(A1>187.5)*<some expression>+(A1>150)*(A1<=187.5)*...etc.
There are many ways to achieve this. One way is to use nested conditions like this:
=IF(Something, do something, IF(something else, do something, do something))
This is good if you want to condensate the formula a bit but arguably leads to more cluttered formulas. According to the FAST-Standard organization, those cases of nested conditions should be replaced by the use of flags. The most simple case would be, for example, where you would be looking for a rebate percentage according to a sales amount. In multiple cells you would have IF conditions evaluating to true only if the value matches that specific range. Then, your formula can be as simple as a SUMPRODUCT of your flags with your rebates percentages.
This is one example, but it can be applied to other cases very well too.
if there is a relationship between the numbers your checking for its very likely you can use
=CHOOSE(FLOOR(A1/160,1)+1,"<160",">160")
Which would put your 150 in the first and leave your 185 in the second

Resources