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
Related
The idea is passing the complete content of a listobject.databodyrange to an array to make operations in memory and not having to access the sheets cells values repeatedly which is very time consuming.
this is the code.
Dim theArray As Variant
theArray = mylistObject.DataBodyRange.value
MsgBox (theArray(1, 1)) '= column 1 row 1 = first element
It works, so far so good.
but!!! since theArray is dimensioned as Variant, their elements are NOT strings, So when passing every of the values of theArray into a function that requires a string an error appears.
what to do?
Note: I know I might change the data type of the function itself to variant, but this function is called from so many routines that i dont dare to touch it. I rather prefer try to look for the way to transform the content of that variant into a string
like theArray(i,j) to str(thearray(i,j)) (which does not work)
some help, some ideas?
EDIT 1:
this is the line of the error:
Dim theclaims As Variant
theclaims = rawClsTbl.DataBodyRange.value
For i = LBound(theclaims, 1) To UBound(theclaims, 1)
myText = deleteRefSigns(theclaims(i, 2))
etc
error: byref argument type missmatch
where:
Function deleteRefSigns(txT As String) As String
i will be trying the solutions proposed.
thx
Related questions:
I asked in overflow myself this question some time ago:
Passing Listobject Range to array and getting error "out of range"
and read also this one
Excel VBA Type Mismatch Error passing range to array
and several others.
The following should work:
Dim MyStr As String
MyStr = CStr(TheArray(1, 1))
Note: Always declare it as a forced array not just as Variant …
Dim TheArray() As Variant 'Variant array (can only be an array)
Dim TheArray As Variant 'Variant (can be a value or array)
… to ensure it contains an array. Otherwise it will contain only a value if you do
TheArray = Range("A1").Value
which might easily fail if your range is dynamic.
If you read a range into an array like
Dim TheArray() As Variant
TheArray = Range("A1:C20").Value
then there is no possibility to declare the array as String it is forced to be Variant by design.
It looks like you don't want deleteRefSigns to modify the argument passed by the calling procedure. If so, you can pass the argument by value...
Function deleteRefSigns(ByVal txT As String) As String
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
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.
I wondering how I can use a variable to call another variable. For example Apple1, Apple2, Apple3, Apple4, Apple5, Apple6 and AppleNum.
Let us say AppleNum is 4. How can I use AppleNum to call for the code to use Apple4?
Also, What if Apple1, Apple2...ect are objects?
My first thought on how to solve this was by using some sort of Array?
Note: Using Select Case here will work but doesn't simply the code I have and would require each case to be written out individually (a lot of work)
Just out of interest, ts there a way to define AppleNum number of variables in the code?
If you can help, Thanks!
It is possible to have an array of variable size in VBA, using the following (for example):
Dim myArray() as Double
Dim AppleNum as Integer, arraySize as Integer
AppleNum = 4
arraySize = 6
ReDim myArray(1 to arraySize)
this would create an array myArray, and change its size to be 6 elements long (with index starting at 1). You can declare the entire array in one statement (without redimensioning) with
myArray = Array(Apple1, Apple2, Apple3, Apple4, Apple5, Apple6)
Assuming that Apple1 etc. have been previously declared.
If you declare myArray as Variant rather than Integer (or don't give a type - defaults to Variant) then your array can contain anything you want.
You can also use
ReDim Preserve myArray(1 to arraySize * 2)
if at a later point you want the array to be twice as large, but you don't want to lose the first six elements you had assigned.
I hope these things will get you moving with your problem.