passing arrays or ranges to VBA udf - arrays

I am trying to pass an array to a UDF to avoid massive duplication of code. As a simple example:
function USERFUNC1(inp as variant)
Dim array_size As Integer
dim i as integer
dim values as double
array_size = WorksheetFunction.CountA(inp)
for i = 1 to array_size
values = values + inp(i)
Next i
USERFUNC1 = values
End function
function USERFUNC2(input1 as variant, input2 as variant)
Dim array_size As Integer
dim i as integer
dim values as double
array_size = WorksheetFunction.CountA(input1)
redim nested_array(array_size)
for i = 1 to array_size
nested_array(i) = input1(i)+input2(i)
Next i
USERFUNC2= USERFUNC1(nested_array)
End function
In the example I have create I have the nested array that I am passing internally to the UDF. However when run this results in a by ref error. I am sure that this can be done but I appear to be missing something
EDIT
It seems what I wrote caused confusion, essentially I have a number of functions, the above is just to demonstrate the idea.
On some of the cells I am calculating a value using a function (call it fugacity) that picks up values into an array a range from the worksheet. In another function (phase equilibrium) I need to perform the same calculation (fugacity) within the second function (phase equilibrium) using values calculated within the second function. This requires me to pass the array from the second function into the first or write out the entire first function again within the second.
I could do that however it make the 2nd function much more difficult to debug as I can no longer be certain that the nested calculation is performing the right calculation, rather I need to check the whole thing. So far I have about 250 lines of code in the first and 300 line the second and within the second (phase equilibrium) I need to perform the first (fugacity) 4 times.

If you want USERFUNC1 to return an array of the values in the inp range, then merely:
function USERFUNC1(inp as Range) as Variant
USERFUNC1 = inp
End function
This will be a 1-based two dimensional array where the first dimension represents the rows, and the second dimension the columns in the original range inp.
I'm not sure what your end goal is, but you may not even need USERFUNC1

Related

How do I return a value from 2 overloaded functions with different return types?

I created two functions to import (make) an array from a text file. They have the same function names but different number of parameters. They also have return values which are different since one importArray function is returning a 1D array and the other is returning a 2D array.
Overloads Function importArray(fileName As String) As Array
Overloads Function importArray(fileName As String, splitter As Char) As Array
Sub Main()
Dim getArray As New MakeArray
Dim printArray() As String = getArray.importArray("array.txt")
For i = 0 To printArray.Length - 1
'printArray
Next
Console.ReadKey()
End Sub
I can't seem to wrap my head around this. I can enter 2 parameters when calling the function or 1 then it's fine, but I don't know how I could specify which function to call, because when I am printing the array I don't know whether to use the 1D or 2D array. I can't do 2 for loops since using one dimension or 2 throws an error "Expression is not a method" so I'm not sure how I can get around this.
Is there a way I could determine whether I am using a 1D or 2D array by reading the text file? I wanted to keep the code as efficient as possible.
Thank you!
Firstly, don't use the Array type that way. If the method return String array, that should be the return type. If it returns a 2D String array then that should be the return type.
Overloads Function ImportArray(fileName As String) As String()
Overloads Function ImportArray(fileName As String, splitter As Char) As String(,)
When you call one of the functions, you assign it to a variable of the appropriate type for the method you're calling. Just think of them as two different methods. Then, you either use a single loop of two nested loops to traverse the data.
Dim arr1 As String() = getArray.ImportArray(fileName)
For i = 0 To arr1.GetUpperBound(0)
'...
Next
Dim arr2 As String(,) = getArray.ImportArray(fileName, splitter)
For i = 0 To arr2.GetUpperBound(0)
For j = 0 To arr2.GetUpperBound(1)
'...
Next
Next

Is there a way to use a 2-dimensional array as the Y-argument in Excel's FORECAST functions?

Excel's FORECAST functions take a 1-dimensional array for both the 'known Xs' argument and the 'known Ys' argument, and then returns a single value as the answer.
I'd like to use a 2-dimensional array for the 'known Ys' argument, and return an array (1-dimensional) as the answer.
In other words, I want to return a set (array) of forecasts that correspond to a set (array) of Y-values, covering the same time-scale (X-values).
(There's a reason I need this...I need to multiply the result I get by a couple of other arrays.)
But if I take a formula that works fine, like
FORECAST.LINEAR($H$1,A2:G2,$A$1:$G$1)
and then change the 1-dimensional array to 2-dimensional (G7 instead of G2):
FORECAST.LINEAR($H$1,A2:G7,$A$1:$G$1)
and press Ctrl+Alt+Enter, I get an error (#N/A).
Same with the TREND function.
I know some Excel functions don't like taking an array as an argument--though sometimes there are ways around this (like here: Can Excel's INDEX function return array?). But I can't figure out if it's possible to 'dereference' things in my situation...I certainly haven't managed to incorporate this approach here.
Addendum in response to comment: The data below are representative (though the real data have a lot more rows and columns!). The top row represents the 'known X's (this is a time scale) and the subsequent rows are the data. The result I want to end up with is an array representing the forecasted Y-value corresponding to X=8...here, I believe that would be
11.71; 14.43; 177.71; 25.71; 16.71; 10.86;
So here is an attempt, I'm still not sure what you are after, but let me try.
I've went the UDF way and my assumption is that you feed the function with a range that has as many rows as columns.
Function GetResult(RNG1 As Range, RNG2 As Range) As Double
Dim X As Double, RNG3 As Range, ARR() As Variant
ReDim ARR(RNG2.Rows.Count - 1)
For X = 1 To RNG2.Rows.Count
Set RNG3 = Application.Intersect(RNG2, Rows(X + 1))
ARR(X - 1) = Application.WorksheetFunction.Forecast(RNG2.Columns.Count + 1, RNG3, RNG1)
Next X
GetResult = Application.WorksheetFunction.Forecast(RNG2.Columns.Count + 1, ARR, RNG1.Value)
End Function
On the sample data you give (with the extraction of column 7), it would look like this:
The function will create a forecast for each line and stores it in an 1-dimensional array. Then it will use all these forecasts to create a final forecast.
Is this close to what you expect?

Use of SumIf function with range object or arrays

I'm am trying to optimize a sub that uses Excel´s sumif function since it takes several time to finish.
The specific line (contained in to a for loop) is this one:
Cupones = Application.WorksheetFunction.SumIf(Range("Test_FecFinCup"), Arr_FecFlujos(i), Range("Test_MtoCup"))
Where the ranges are named ranges in the workbook, and Arr_FecFlujos() is an array of dates
That, code works fine, except for it takes to much time to finish.
I am trying this two approaches
Arrays:
Declare my arrays
With Test
Fluj = .Range(Range("Test_Emision").Cells(2, 1), Range("Test_Emision").Cells(2, 1).End(xlDown)).Rows.Count
Arr_FecFinCup = .Range("Test_FecFinCup")
Arr_MtoCup = .Range("Test_MtoCup")
End With
Cupones = Application.WorksheetFunction.SumIf(Arr_FecFinCup, Arr_FecFlujos(i), Arr_MtoCup)
Error tells me I need to work with Range Objects, so I changed to:
With Test
Set Rango1 = .Range("Test_FecIniCup")
Set Rango2 = .Range("Test_MtoCup")
End With
Cupones = Application.WorksheetFunction.SumIf(Rango1, Arr_FecFlujos(i), Rango2)
That one, doesn't shows any error messages, but the sum is incorrect.
Can anybody tell me what's working wrong with these methods and perhaps point me in the correct direction?
It seems that you try to sum a range of numbers using a range of criteria:
WorksheetFunction.SumIf(Arr_FecFinCup, Arr_FecFlujos(i), Arr_MtoCup)
As i know, if the criteria parameter is given a range, Excel don't iterate over that range but instead look for the one value in the criteria_range that coincides with the row of the cell that it is calculating.
For example
Range("D3") = WorksheetFunction.SumIf(Range("A1:A10"),Range("B1:B10"))
Excel will actually calculate as follow
Range("D3") = WorksheetFunction.SumIf(Range("A1:A10"),Range("B3"))
If there is no coincident, then the return is 0
For example
Range("D7") = WorksheetFunction.SumIf(Range("A1:A10"),Range("B1:B5"))
Then D7 is always 0 because looking for [B7] in [B1:B5] is out of range.
Therefore, to do a sum with multiple criterias, the correct way is using SUMIFS as suggested by #mrtiq.

Assigning values to a dynamically resized array in Excel VBA

I am trying to populate a two dimensional array of ranges. I don't know how big the array will need to be, so I am using the ReDim and Preserve functions to dynamically re-size the array as required.
I am encountering runtime error 91: "Object variable or With block variable not set" when I run the code.
I am not an experienced coder, but I have managed to isolate the error, and am sure it is coming from the pseudo code below.
Can anyone see any mistakes I have made that would produce the runtime error?
Dim ArrayName() as Range
Dim counter as Integer
If condition = True Then
counter = counter + 1
ReDim Preserve ArrayName(0, counter - 1)
ArrayName(0, counter - 1) = Cells(counter, counter) 'I get a runtime error here
End If
Thank you.
If you want to store ranges within your array you need to add Set before problem line in this way:
Set ArrayName(0, counter - 1) = Cells(counter, counter)
But if you want to store values of the cells you need to change declaration line to this:
Dim ArrayName() as Double 'or String or Variant depending on value type you need to keep in array

vba variant array bug

I am fairly new to excel vba and I can't seem to fix this problem with vbArrays. I created the function cumsum in vba just to make my life easier. However, I want to make the code flexible such that I can pass in both variants from a function and also a range. In my code, when I added the line vec=vec.value if I am passing in a range, it works perfectly fine but it doesn't work if I want it to work if I call the function and pass in a non range type. What I noticed was if I didn't have the line vec=vec.value in my code and I pass in a range, it has dimension 0 and I checked by writing my own function. Can someone please explain to me how I can fix this problem? Thanks.
Public Function cumsum(vec As Variant) As Variant
Dim temp() As Variant
MsgBox (getDimension(vec))
'works if i use vec=vec.value if vec is a range but has 0 if i do not vec = vec.values
ReDim temp(LBound(vec, 1) To UBound(vec, 1), 1 To 1) As Variant
Dim intCounter As Integer
For intCounter = LBound(vec) To UBound(vec)
If intCounter = LBound(vec) Then
temp(intCounter, 1) = vec(intCounter, 1)
Else
temp(intCounter, 1) = temp(intCounter - 1, 1) + vec(intCounter, 1)
End If
Next
cumsum = temp()
End Function
Function getDimension(var As Variant) As Integer
On Error GoTo Err:
Dim i As Integer
Dim tmp As Integer
i = 0
Do While True:
i = i + 1
tmp = UBound(var, i)
Loop
Err:
getDimension = i - 1
End Function
Why don't you just check the data type of vec by using VarType and TypeName then perform the necessary manipulation on vec
Public Function cumsum2(vec As Variant) As Variant
MsgBox TypeName(vec)
MsgBox VarType(vec)
cumsum2 = 0
End Function
The answers from #Jake and #chris are hints in the right direction, but I don't think they go far enough.
If you are absolutely sure that you'll only ever call this routine as a UDF (i.e. from formulas in your worksheets), then all you really need to do is add this:
If IsObject(vec) Then
Debug.Assert TypeOf vec Is Range
vec = vec.Value2
End If
to the start of your function. Called as a UDF, the only object type it should ever get passed is Range. Also, called as a UDF, you can rely on the fact that any arrays it gets passed will be indexed starting from 1.
I could pick out other problems with your routine, but they would be beside the point of your original question. Briefly: this will only work on column vectors, it will fail for single-cell ranges, etc.
Note that the reason your getDimension function is returning zero for Ranges because UBound is choking on the range. Your error handler happily catches an error (type mismatch) you didn't really expect to get and returning zero. (That method of finding "dimension" is assuming the error will be a subscript out range error.)
I wrote an answer a while back describing why, when working with Excel, I don't think the general getDimension approach is a good one:
https://stackoverflow.com/a/6904433/58845
Finally, the issue with VarType is that, when passed an object that has a default property, it will actually return the type of the property. So VarType(<range>) is going to tell you the type of the stuff in the range, not the code for object, because Range has a default property, Range.Value.
Modify your getDimension to include
If TypeName(var) = "Range" Then
var = var.Value
End If

Resources