Is there a way in Excel VBA to use Application.WorksheetFunction.Sumproduct to get the sumproduct of 2 array columns? For example, if A and B are two arrays each with 3 rows and 3 columns (VBA arrays, not Excel arrays), is there an easy way to get the sumproduct of the 3rd column of A with the 2nd column of B? If so, what is the syntax?
Thanks
While it might be tempting to try and use WorksheetFunction.SumProduct to do this, looping over a VBA array will be much faster than using worksheet functions. In a small test I got about a x40 performance improvement over the other posted answer.
This is a simple example of how you might do it. You should add validity checks on the inputs and error handling.
Function ArraySumProduct(aA As Variant, aB As Variant, col1 As Long, col2 As Long) As Variant
Dim i As Long
Dim dSumProduct
For i = LBound(aA) To UBound(aA)
dSumProduct = dSumProduct + aA(i, col1) * aB(i, col2)
Next
ArraySumProduct = dSumProduct
End Function
OK, seems as if WorksheetFunction.Index works with arrays of arrays also. So you can achieve this with a combination of WorksheetFunction.Index to get the 3rd and 2nd columns and WorksheetFunction.Transpose to get 1D-arrays of the columns and then WorksheetFunction.SumProduct.
Sub test()
aA = [{1,2, 3;4,5, 6;7,8, 9}]
aB = [{10, 11,12;13, 14,15;16, 17,18}]
'aA = Array(Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9))
'aB = Array(Array(10, 11, 12), Array(13, 14, 15), Array(16, 17, 18))
aCol3of_aA = WorksheetFunction.Index(aA, 0, 3)
aCol2of_aB = WorksheetFunction.Index(aB, 0, 2)
aArr1 = WorksheetFunction.Transpose(aCol3of_aA)
aArr2 = WorksheetFunction.Transpose(aCol2of_aB)
dSumProduct = WorksheetFunction.SumProduct(aArr1, aArr2)
'= 3*11 + 6*14 + 9*17 = 270
MsgBox dSumProduct
End Sub
Related
I am stuck. I am trying to copy some selected rows from one column of a 2-D array to a particular column of another 2-D Array. I have tried all my knowledge, but to no avail.
Sub TestIndxPst()
Dim varArray As Variant
ReDim varArray(1 To 4, 1 To 3)
Dim vaOut As Variant
ReDim vaOut(1 To 4, 1 To 3)
RowNumArray = Evaluate("transpose(Row(1" & ":" & 5 & "))")
varArray = ThisWorkbook.Worksheets("Sheet1").Range("G2:I7")
Application.Index([G9:I12], , 2) = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2))
vaOut = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2))
Application.Index(vaOut, , 2) = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2))
For i = LBound(vaOut) To UBound(vaOut)
Debug.Print vaOut(i, 1)
Next i
End Sub
You can see that I can do this while writing to an excel range using Application.Index([G9:I12], , 2) = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2))
I can also do that when I output it to the first column using vaOut = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2))
But as soon as I am trying to put the result in column 2 of out array here, Application.Index(vaOut, , 2) = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2)). I get application-defined or object defined error
Any help is highly appreciated.
Application.Index(vaOut, , 2) = .. - Any help is highly appreciated
ReDim varOut & ReDim vaOut get overruled by any eventual datafield assignment.
If you want to stick to the Index function by all means writing the rearranged values to a target range, I'd prefer an intermediate assignment
something like
Dim newArr
newArr = Application.Transpose(Application.Index(varArray, Array(2, 3, 5, 6), 2))
followed by
Sheet1.Range("H9").Resize(Ubound(newArr),ubound(newArr,2)) = newArr
Application.Index([G9:I12], , 2) = .. builds no virtual array,
it describes just the receiving target range which simply is H9:H12 then.
Main issue
If, however you want to enter the newArr values to the same or another array column,
this is simply not possible in one go by a
construction like
Application.Index(vaOut, , 2) = .. (though ressembling to your previous target range code) - you'll have to loop (e.g. through newArr and enter values to another predefined array) as #TimWilliams suggested.
I was wondering if there is a possibility to use countif on arrays.
Currently there are two arrays. One is the Array with the Range (RangeArray) and the other the Criteria array (CritArray) which comes from another workbookbut is saved in an array. I'm trying to use the countif method in VBA using arrays if and store the countif values in a cell. So I don't need to loop between workbooks all the time.
Dim RangeArray, CritArray as Variant
RangeArray = Array(1,2,3,4,2,4,2,5,7,1,7,1,2)
CritArray = Array(1,2)
For i = 1 To LastRow
Cells(i, 1).Value = WorksheetFunction.CountIf(RangeArray, CriteriaArray)
Next i
When I try to do something amongst these lines it keeps giving the error "object required".
Any help would be kindly appreciated!
Kind regards,
Sub test()
Dim RangeArray, CritArray As Variant
Dim Counts As New Collection
RangeArray = Array(1, 2, 3, 4, 2, 4, 2, 5, 7, 1, 7, 1, 2, 11)
CritArray = Array(1, 2)
For i = 0 To UBound(CritArray)
Count = 0
For j = 0 To UBound(RangeArray)
If CritArray(i) = RangeArray(j) Then
Count = Count + 1
End If
Next
Counts.Add Count
Next
For k = 1 To Counts.Count
Cells(k, 1) = Counts(k)
Next
End Sub
Suppose I have data in range A1:A100. I would like to split each cell in the range to multiple columns, by a fixed width, eg (0-10,10-15,15-37). I could use the Text-to-Columns function in both vba and excel itself.
My question is, if i pass the range to an array first in VBA:
Dim my Array as Variant
myArray = Range("A1:A100").value
How would i apply the following logic:
myNewArray = Array(myArray(0,10),myArray(10,15),myArray(15,37))
or maybe like this:
for i=1 to 100
myNewArray(i,1) = mid(myArray(i),0,10)
myNewArray(i,2) = mid(myArray(i),10,5)
myNewArray(i,3) = mid(myArray(i),15,22)
next
which would result in a new array of 100 rows by 3 columns, but having split the initial data at the specified points, like how a Text-to-Column approach would. But these approaches don't seem to work.
I have tried searching for answer to this but can't seem to find anything
Any help would be appreciated,
thanks
In addition to Scott 's correct hint in comment you could use one datafield array only (based on three columns) and do a reverse loop splitting the first "column":
Option Explicit
Sub Split_them()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("MySheet") ' << change to your sheet name
Dim i As Long, j As Long
Dim v
Dim a(): a = Array(0, 11, 16, 38) ' Fixed Widths (first item zero)
v = ws.Range("A1:C100")
For i = 1 To UBound(v, 1)
For j = 3 To 1 Step -1
v(i, j) = Mid(v(i, 1), a(j - 1) + 1, a(j) - a(j - 1))
Next j
Next i
' write back to sheet
ws.Range("A1:C100").Offset(0, 2) = v
End Sub
I need to create a 2D array which is not intended to be updated, only read.
Reading this question, I found this possibility using evaluate:
Dim varData As Variant
varData = [{1, 2, 3; 4, 5, 6; 7, 8, 9}]
In my case, the number of values requires to split the assignment on several lines, e.g.
varData = [{value1, value2; _
...; _
valueM, valueN}]
However when using _ to split this assignment, VBA complains at the first line with:
Compile Error:
Missing end bracket
I've tried to find the explanation, but all examples seems to use only a single line. What is wrong?
Note: I'm trying to populate an array, not cells in a sheet.
I was thinking of this.
Dim A as Variant
A = Array(Array(1, 2), _
Array(3, 4), _
Array(5, 6))
The Microsoft site suggests the following code should work:
Dim numbers = {{1, 2}, {3, 4}, {5, 6}}
However I get a complile error when I try to use it in an excel VBA module.
The following does work for a 1D array:
A = Array(1, 2, 3, 4, 5)
However I have not managed to find a way of doing the same for a 2D array.
Any ideas?
You can also use a shorthand format leveraging the Evaluate function and a static array. In the code below, varData is set where [] is the shorthand for the Evaluate function and the {...} expression indicates a static array. Each row is delimited with a ; and each field delimited with a ,. It gets you to the same end result as simoco's code, but with a syntax closer to your original question:
Sub ArrayShorthand()
Dim varData As Variant
Dim intCounter1 As Integer
Dim intCounter2 As Integer
' set the array
varData = [{1, 2, 3; 4, 5, 6; 7, 8, 9}]
' test
For intCounter1 = 1 To UBound(varData, 1)
For intCounter2 = 1 To UBound(varData, 2)
Debug.Print varData(intCounter1, intCounter2)
Next intCounter2
Next intCounter1
End Sub
The Microsoft site suggests...
This suggestion is for VB.NET but not VBA.
For VBA you were in the right direction. You can do this:
Dim A as Variant
A = Array(Array(1, 2), Array(3, 4), Array(5, 6))
Alternative via Application.Index()
Extending on Dmitriv Pavliv's use of a jagged array (and as alternative to Robin Mackenzie's short hand approach), you can go a step further by applying Application.Index() on this array of arrays (with identical number of elements each) - note the double zero arguments!:
Sub Get2DimArray()
Dim arr() As Variant
'a) build array of arrays (aka as jagged array)
arr = Array(Array(1, 2, 4), Array(4, 5, 6), Array(7, 8, 9))
'b) make it 2-dimensional
arr = Application.Index(arr, 0, 0)
End Sub
Results in
a 2-dim arr(1 To 3, 1 To 3), where
* Row 1 ~> 1|2|4
* Row 2 ~> 4|5|6
* Row 3 ~> 7|8|9
Related link
Further reading regarding Some pecularities of the Application.Index() function
So here you generate the array without anything on it, just by telling its dimensions.
Dimension is X+1 because 0 counts as a position in the array.
Dim MyArray(X, X) As Integer
Then you fill it by doing for exemple
MyArray (0,0) = 1
MyArray (0,1) = 2
MyArray (1,0) = 3
MyArray (1,1) = 4
...
And so on.
If you want a more convenient way of filling it you can use For Cycles if there is a inherent logic to the numbers you are filling it with.
In case the size is unknown until run time.
Dim nRows As Integer, nCols As Integer
...
Dim yourArray() As Integer
ReDim yourArray(1 to nRows, 1 to nCols) 'One base initialisation
'ReDim yourArray(0 to nRows - 1, 0 to nCols - 1) 'Zero base initialisation
Then you can initialise (or access) the grid as:
yourArray(1, 1) = ... 'set first cell