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...And run your code on the Variant vArrayIf 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
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\nYou'll be able to adapt that for use with CallByName(), unless you're only ever going to do this once.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\nDim vArray As Variant \nDim varX \nDim varTypeTest As VbVarType
\n\n' Guard clauses. Lots of Guard Clauses.
\n\nIf IsError(varArray) Or IsError(varTest) Then \n IsMemberOf = False \n Exit Function \nEnd If
\n\nIf VarType(varArray) = vbError Then \n IsMemberOf = False \n Exit Function \nEnd If
\n\nIf IsArray(varTest) Then
\n\n\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
End If
\n\nIf IsEmpty(varArray) Then
\n\n\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
ElseIf IsNull(varArray) Then
\n\n\n\nIsMemberOf = False Exit Function
ElseIf TypeName(varArray) = "Range" Then
\n\n\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
Else
\n\n\n\nvArray = 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
End If
\n\nvarTypeTest = VarType(varTest)
\n\nFor Each varX In vArray
\n\n\n\nIf 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
Next varX
\n\nEnd Function \n
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.