I'm trying to write the SWITCH function in VBA for my coworker who has Excel 2013. I feel that my VBA is strong enough to code this function once I set up all my function parameters. However, I'm not sure how to have an unlimited number of optional parameters in a function (similar to *args in Python). How can I set up a function so that it may have an unlimited number of optional arguments?
You need to use ParamArray, e.g.
Public Function TestSum(ParamArray a())
Dim i As Long
For i = LBound(a) To UBound(a)
TestSum = TestSum + a(i)
Next i
End Function
An interesting question, here's my attempt at replicating the Switch functionality.
You will need to use a ParamArray argument:
Optional. Used only as the last argument in arglist to indicate that the final argument is an Optional array of Variant elements. The ParamArray keyword allows you to provide an arbitrary number of arguments. It may not be used with ByVal, ByRef, or Optional. (source)
Revised, thanks to the comments with #TinMan, we no longer use a Dictionary so this will be compatible with Mac OS without further tweaks.
Function FSwitch2(ValueToMatch As Variant, ParamArray ValuesToMatchAndReturn())
' example of replicating the Switch function available in Office 365, etc.
' https://support.office.com/en-us/article/switch-function-47ab33c0-28ce-4530-8a45-d532ec4aa25e
Dim i As Integer
Dim retVal As Variant
Dim default As Variant
If (UBound(ValuesToMatchAndReturn) + 1) Mod 2 <> 0 Then
' if the array is not evenly sized, assume the last argument is the default value.
default = ValuesToMatchAndReturn(UBound(ValuesToMatchAndReturn))
Else
' Otherwise, default to #N/A error if no match.
default = CVErr(2042)
End If
For i = LBound(ValuesToMatchAndReturn) To UBound(ValuesToMatchAndReturn) Step 2
If ValueToMatch = ValuesToMatchAndReturn(i) Then
retVal = ValuesToMatchAndReturn(i + 1)
Exit For
End If
Next
FSwitch2 = IIf(IsEmpty(retVal), default, retVal)
End Function
Related
I want to include a ParamArray in my function, but my efforts have failed so far.
Let's say I want to build a function to calculate mean for discrete distribution.
The function has the following form:
Function Dist_Discrete(X1, P1, X2, P2, X3, P3, etc.)
There are two solutions currently come to my mind:
Using optional arguments multiple of times.
Using ParamArray
The second one is preferred. But here is some problem when I try to define
Function Dist_Discrete(ParamArray XVal() As Variant, ParamArray Prob() As Variant)
And an error message comes up - "Compile error"
I can find a way to go around this by setting even as probability and odd as value. But I think this might be a good temporary solution
Your posted example shows the inputs as cells in a contiguous column like X1, X2. etc. If this is the case, the just use Range objects as the inputs:
Function Dist_Discrete(X as Range, P as Range) as Double
then in a worksheet cell:
=DistDiscrete(X1:X30, P1:P30)
ParamArray specifies that a procedure parameter takes an optional array of elements of the specified type. ParamArray can be used only on the last parameter of a parameter list. Thus 2 param arrays are not possible. (learn.microsoft.com)
However, you may think about a little trick in Excel. E.g. lets consider the following Function:
Function SumAndMultiply(valuesToSum(), valuesToMultiply()) as Double
which has the idea to sum the values of the valuesToSum() array and to multiply the result with each of the valuesToMultiply().
In VBA, if the first one is presented as a range, then it would work quite nicely:
Public Function SumAndMultiply(valuesToSum As Range, _
ParamArray valuesToMultiply() As Variant) As Double
Dim myCell As Range
Dim sum As Double
For Each myCell In valuesToSum
sum = sum + myCell
Next myCell
Dim param As Variant
SumAndMultiply = sum
For Each param In valuesToMultiply
SumAndMultiply = SumAndMultiply * param
Next
End Function
I am using windows 7, Excel 2010, VBA. I am getting an error
"ByRef Argument Type Mismatch". I am assuming it is a problem with my variable type. I found lots of questions like mine but I can't find anything that has helped me figure out my problem.
Variable Declarations
'Force explicit variable declaration
Option Explicit
Private dptData(8) As String
Private TSdata(8) As String
Private fiscalYear(8) As String
Calling Function
parseUserData fiscalYear, dptData, TSdata
Called function Prototype
Function parseUserData(fiscalYear As String, dptDataAs String, TSdata As String)
You're passing an array to a String. Change the function's signature to accept a Variant instead.
Public Function parseUserData(fiscalYear As Variant, dptDataAs Variant, TSdata As Variant)
Debug.Assert IsArray(fiscalYear) And IsArray(dptDataAs) And IsArray(TSdata)
A String parameter can only ever accept a String argument1. Variant on the other hand, can accept anything - including an array - but then you'll want to Assert that you're dealing with an array, so that you halt execution (and prevent a bug) if that's not the case.
Why use a Variant over a typed array?
Using a typed array would work, but a typed array can't be coerced from a Variant parameter - which means this:
Public Sub DoSomething(ByRef args() As String)
...can't be invoked with this otherwise perfectly valid array of strings:
DoSomething Array("string1", "string2", "string3") ' can't pass a variant array!
Changing the signature to DoSomething(ByRef args As Variant) makes it work. All you need to do is to use a meaningful, descriptive, pluralized name to your variant array parameters, so that when IntelliSense is shown when you invoke that procedure, the name tells you everything you need to know.
But... 'Variant' is evil!
No different than many other languages, type safety in VBA is essentially smokes and mirrors. Variant is a very powerful tool and does have its uses - avoid it when you can doesn't mean unlearn its existence. Using it to pass array references around between procedures doesn't hurt the code's readability, maintainability, or stability. Variant enables duck typing and late-binding, and is a legitimate COM type.
It's a hammer. Just make sure not everything becomes a nail, and you'll do great.
1VBA will implicitly convert other value types to a String, but an array can't be coerced into a string, implicitly or explicitly.
It seems you wanted a string array after all but it may be worth mentioning that you can declare a fixed width string var.
Dim dptData As String * 8
dptData = "abc"
Debug.Print Len(dptData) & "|" & dptData & "|"
'result from Immediate window
'8|abc |
dptData = "abcdefghijk"
Debug.Print Len(dptData) & "|" & dptData & "|"
'result from Immediate window
'8|abcdefgh|
I'm new to excel custom functions and trying to create a custom Excel function that outputs a "Y" or "N" and takes four arguments. The error I'm getting on the other end is #VALUE! with no further explanation. I tried looking at the Range object in the MSDN docs and can't understand why my function isn't reading the values.
My function takes four arguments and the datatypes in the cells are median: Double, base: Double, hours: Integer, and exemption: String (it takes a letter "E" or "N"). I get those arguments from four separate cells in my worksheet.
Function ToIncrease(median As Range, base As Range, hours As Range, exemption As Range)
ToIncrease = "N"
exempt = exemption.Value
If exempt.Equals("N") Then
baseSal = base.Value * hours.Value
medianSal = median.Value * hours.Value
If medianSal > baseSal Then
ToIncrease = "Y"
End If
End If
If exempt.Equals("E") Then
If median.Value > base.Value Then
ToIncrease = "Y"
End If
End If
End Function
I first tried exemption.Value.Equals("N") and it didn't work, and then I tried to declare the exempt variable as Dim exempt As String = exemption.Value and got an error about expecting the end of a statement.
First replace lines like:
If exempt.Equals("N") Then
with
If exempt = "N" Then
(there may be other problems as well)
It is MUCH easier to debug VBA code as a Sub rather than a Function() because the error messages are much better.
I am trying to evaluate a logical expression by concatenating the operands and operator. Is there a formula to convert from text to logical in Excel 2016, similar to how VALUE() converts from text to number? I'm looking to a solution for this so I can dynamically change the condition without changing the actual Excel formula. I've searched and read through the Excel function descriptions, but nothing is jumping out as a solution.
'The operands and operator
A1: 1
A2: >
A3: 0
'Concatenation
B4: =CONCAT(A1:A3) 'This evaluates to 1>0
B5: =A1&A2&A3 'This also evaluates to 1>0
'Some checks
C4: =ISTEXT(B4) 'This evaluates to TRUE.
C5: =ISTEXT(B5) 'This also evaluates to TRUE
D4: =ISLOGICAL(B4) 'This evaluates to FALSE
D5: =ISLOGICAL(B5) 'This also evaluates to FALSE
'Vain attempts
E4: =AND(B4,TRUE) 'This ALWAYS is TRUE, even when my desired output is FALSE
E5: =OR(B5) 'This spits out a #VALUE! error
Since I'm looking for something dynamic, I want to avoid a solution such as
=IF(A2=">",A1>A3,FALSE). I also would prefer to avoid a UDF but am willing to go that route if no built in function exists to convert a logical expression in text and evaluate it as logical.
To write a UDF calling Evaluate, that can handle either a Range or Variant/String input, try the following:
Function L(exp As Variant) As Variant
Dim vExp As Variant
vExp = exp
L = Application.Evaluate(vExp)
End Function
Why does it work?
The line vExp = exp does the magic. If exp is a Range then the assigment uses the default .Value property and copies as a Variant/String (because we are not using Set). If it's a Variant/String, then it's a straight copy.
It does have the downside of using Application.Evaluate (downside as explained
here )
A version to also handle the possibility of being called from VBA, try
Function L(exp As Variant) As Variant
If TypeName(exp) = "Range" Then
L = exp.Worksheet.Evaluate(exp.Value)
Else
If IsError(Application.Caller) Then
'Calls from VBA are hanbled here.
' Note: Best to fully qualify any range references in exp
' to avoid unexpected references to the active sheet
L = ActiveSheet.Evaluate(exp)
Else
L = Application.Caller.Worksheet.Evaluate(exp)
End If
End If
End Function
Based on the comments that no built-in function takes care of this, I made a user-defined function ("UDF") called L() for logical, taking after the built-in functions N() and T() which are for numbers and text.
Function L(exp As Variant) As Variant
Dim limit As Integer
Dim counter As Integer
'Set an upper limit to how many cycles the loop may run
limit = 1000
'Assuming the possibility of nested functions, loop until the expression resolves to logical or to an error or until the loop limit has been reached
Do
exp = [exp] 'This avoids Error 2015 if exp is a Range reference. Comment if there's a better way!
exp = Application.Evaluate(exp) 'Evaluate the expression
counter = counter + 1 'Increment the loop counter
Loop Until Application.IsLogical(exp) Or Application.IsError(exp) Or counter >= limit
'Return the evaluated expression
L = exp
End Function
This function works even when I throw some silly things its way like =L(TRUE) or =L(CONCAT(A1:A2)&A3) or even =l(CONCAT(A1:A2)&"""foo"""). But it does not throw errors in cases where it probably should, such as =l(123) or =l("123"). In these cases, thank goodness for the counter limit because 123 and "123" will never evaluate to a logical or an error.
Sorry in advance, I'm a self-taught VBA programmer, and I'm not sure how to phrase my question!
I've declared constants which have similar names, i.e.
Public Const BP1Enable = "something1something:someotherthing1someotherthing"
public const BP2Enable = "something2something:someotherthing2someotherthing"
etc.
I have 10 of these constants. I have a sub with these constants as arguments:
Sub FieldEnable (ByVal enableconst)
Now I want to call the FieldEnable sub in a loop using i as counter:
For i = 1 To 10
BPfieldname = "BP" & i & "Enable"
FieldEnable enableconst:=BPfieldname
Next i
This does not work, what is happening is that the "value" assigned to enableconst in the sub FieldEnable, is "BP1Enable" instead of the value of the constant BP1Enable namely "something1something:someotherthing1someotherthing".
How can I use the variable BPfieldname when calling the sub FieldEnable?
I hope this makes sense. Any help appreciated.
Transform your variables into a single Array.
See this http://msdn.microsoft.com/en-us/library/wak0wfyt.aspx
EDIT: as #sina has correctly pointed out, VBA does not allow constant arrays,
so instead of trying this
Dim BPEnable = {
"something1something:someotherthing1someotherthing",
"something2something:someotherthing2someotherthing",
....
}
you should try this
Dim BPEnable
BPEnable = Array( _
"something1something:someotherthing1someotherthing", _
"something2something:someotherthing2someotherthing", _
"..."
)
For i = 0 To UBound(BPEnable)
BPfieldname = BPEnable(i)
Next i
The best guess would be to use a constant array, but constant arrays are not supported by VBA.
Therefore you could go with building an array out of your constants before you start your loop:
Dim BPEnable
BPEnable = Array(BP1Enable, BP2Enable)
For i = 0 To UBound(BPEnable)
FieldEnable enableconst:=BPEnable(i)
Next i
Another option would be to declare all constants as one long string with some defined separator and use a split function on that string to generate the array.
Also, if you are going to be using this "constant" in more than one place, more than one function, you can effectively make an array constant, that can even be called that way.
Public Sub Test()
For i = LBound(BPEnable) To UBound(BPEnable)
Debug.Print BPEnable(i)
Next i
End Sub
Public Function BPEnable() As Variant
BPEnable = Array("One", "Two", "Three", "Pi", "Four")
End Function
Immediate Window:
One
Two
Three
Pi
Four