简体   繁体   中英

How can I write a function that would take and return an array of any type in VBA for excel?

I've tried to create an array function that would compute x^2 + 1/3x + 5 for a one- (vertical or horizontal) or two-dimensional array and return an array with the same dimension as parameter. The outcomes should be in respective cells. But when I use it in Excel it exchanges rows and columns and it works only for a square matrix. Can you please help me, how the code should look like so it could be used for non-square matrices and also for one-dimensional array?

Here's my code

Option Base 1

Public Function FunctionValues(rng As Range) As Variant
 Dim i As Integer
 Dim j As Integer
 Dim NumCols As Integer
 Dim NumRws As Integer
 NumCols = rng.Columns.Count
 NumRws = rng.Rows.Count
 Dim FX() As Double
 ReDim FX(NumCols, NumRws)

    For i = 1 To NumCols
        For j = 1 To NumRws
     x = rng.Columns(i).Rows(j)
        FX(i, j) = x ^ 2 + 1 / 3 * x + 5
     Next j
 Next i

    FunctionValues = FX()

    End Function

Good news! Because there is a one-to-one correspondence between an input cell and an output cell, there is no need for an array UDF. Consider:

Public Function Polyn(Rin As Range) As Double
   Dim v As Variant
   v = Rin.Value
   Polyn = v ^ 2 + v / 3 + 5
End Function

Just copy it where you need it.

在此处输入图片说明

So we place the formula in cell E7 and then copy E7 to the range E7 through G11 . As you see, this is normally entered rather than array entered

Is it always going to receive data as the .Value property of an Excel range?

That means that your input will always be a 2-dimensional array, with dimensions (1 to Row.Count, 1 to Columns.Count) ...

...Except when it's a single cell, when the value of the range is a scalar variant, not an array, and all your carefully-typed code will raise type errors. Here's how you handle that:

Dim vArray As Variant

If TypeName(rng) = "Range" Then

If rng.Cells.Count = 1 Then
    Redim vArray(1 To 1, 1 To 1)
    vArray(1,1) = Array(rng.Value2)
Else
    vArray = varArray.Value2
End If

End If

...And run your code on the Variant vArray

Note how I've dimensioned that: (1 to Row.Count, 1 to Columns.Count) - almost every error in placing the returned results from VBA arrays can be traced to confusion between zero-based arrays and the 1-based .Value property of an Excel range.

Next: running a complex function on the contents of each cell. If you want a generic function, like the Lambda calculus of a proper 'Functional' language, I'm thorry to thay that you will be dithappointed. You can, however, pass in functions by name and use VBA.Interaction.CallByName()

There's nothing to stop you coding up your function as:

y = CallByName(ThisWorkbook, ParabolicPlotter, VbLet, x, a, b, c)

...in the Workbook module. This means that ParabolicPlotter is a method of the workbook object, which can be called by name as:

 y = CallByName(ThisWorkbook, ParabolicPlotter, VbLet, x, a, b, c) 

Or you can just hardcode the constants a, b and c as 1.0, 1/3 and 5 in the code, passing only the parameter 'x'.

Finally, there is a worst case, where you're forced to accept any variant array, and you don't know whether it's going to be a 2-dimensional array.

Note that single-column ranges (and a single-row range) from an Excel Spreadsheet will still express their values as 2-dimensional arrays: (1 to rng.Rows.Count, 1 To 1) or (1 To 1, 1 to rng.Columns.count) - but true 'Vector' arrays are dimensioned (1 to n): there isn't a second dimension, and no 'cast' to vArray(1 to n, 0 to 0) that allows you to read a vector with the same code you use for a true 2-D array. The only common interface is a For... Each iteration.

'Any Variant' may include nested arrays, and you'll get this 'even worse case' from third-party real-time market data providers. So here's a simple single-purpose function that deals with any and all incoming variants:

\nPublic Function IsMemberOf(varTest, varArray, _ \n                  Optional StringComparison As VbCompareMethod = vbBinaryCompare _ \n                   ) As Boolean \nApplication.Volatile False  

\n\n

On Error Resume Next

\n\n

' Returns true if varTest is in the collection or array 'VarArray'

\n\n

' Returns False if varTest is empty, null, nothing, or an array.

\n\n

' WARNING: Type-sensitive: the string "1" is not a member of array(1, 2, 3) \n' ...Unless there is an automatic cast in VBA: \n' int(1) is a member of array( 1.0, 2.0, 3.0 )

\n\n

' WARNING: This is case-sensitive wherever it makes a string comparison: \n' "SELL" is not in Array("Buy", "Sell")...

\n\n

' ...Unless you specify VbCompareMethod = vbTextCompare AND both \n' the test value and a value in varArray are strings. Note that \n' they must be explicitly declared as string variables: not as \n' variants or an object's default value that can cast to string

\n\n

Dim vArray As Variant \nDim varX \nDim varTypeTest As VbVarType

\n\n

' Guard clauses. Lots of Guard Clauses.

\n\n

If IsError(varArray) Or IsError(varTest) Then \n IsMemberOf = False \n Exit Function \nEnd If

\n\n

If VarType(varArray) = vbError Then \n IsMemberOf = False \n Exit Function \nEnd If

\n\n

If IsArray(varTest) Then

\n\n
 ' Enhancement: insert Err.Raise for this specific case ' Or code up an array equivalence test, if you have too much spare time IsMemberOf = False Exit Function 
\n\n

End If

\n\n

If IsEmpty(varArray) Then

\n\n
 ' VBA.Empty isn't a member of the empty set, nor is VBA.Empty() - the ' empty set has no members. Not even an 'empty' variant, value, or set. IsMemberOf = False Exit Function 
\n\n

ElseIf IsNull(varArray) Then

\n\n
 IsMemberOf = False Exit Function 
\n\n

ElseIf TypeName(varArray) = "Range" Then

\n\n
 ' Yes, I specified an array variant. If the parameter passed to the function ' is a Range object, VBA will ignore the convention of reading the object's ' default property into the variant, and pass the range object by reference. ' ...So your variant is now an *object* and you will get type errors. If varArray.Cells.Count = 1 Then vArray = Array(varArray.Value2) Else vArray = varArray.Value2 End If 
\n\n

Else

\n\n
 vArray = varArray ' Works for all arrays and (most of the time) for external objects which ' expose an iterator, but no 'variable' or value you can assign to a VBA ' variant: these iterators are only accessible using For... Each 
\n\n

End If

\n\n

varTypeTest = VarType(varTest)

\n\n

For Each varX In vArray

\n\n
 If IsArray(varX) Then ' Nested array: recursion If IsMemberOf(varTest, varX, StringComparison) Then IsMemberOf = True Exit For End If ElseIf VarType(varX) = vbString And varTypeTest = vbString Then ' Separate out the slower string comparison operation If VBA.Strings.StrComp(varX, varTest, vArray, StringComparison) = 0 Then IsMemberOf = True Exit For End If ElseIf varX = varTest Then ' all other tyes IsMemberOf = True Exit For End If 
\n\n

Next varX

\n\n

End Function \n

You'll be able to adapt that for use with CallByName(), unless you're only ever going to do this once.

There's one more case for you to consider: noncontiguous ranges. That'll need an iteration through rng.Areas() - and I would suggest that you wait a while before tackling those.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM