Of late I have become more and more convinced that Index is the best "value for money" Worksheet Function in Excel.
In worksheet function form the INDEX function can for example do the following (all context aside):
a) return single Values
note: here the above acts like INDIRECT in so far as if row 4 is deleted the above would still return contents of the "new" A4 and not error=INDEX($A:$A,4)
(we are for sake of simplicity assuming Col A is not deleted in above example)
b) return Array of Values for use in conjunction with other functions
c) be used to create Ranges, eg for use as Dynamic Named Ranges=SUMIF($A$2:$A$10,"Apple",INDEX($B$2:$Z$10,0,MATCH("values",$B$1:$Z$1,0)))
which all in all makes it incredibly flexible and it does all of this whilst being non-volatile (post XL97) unlike equivalent function types (eg OFFSET, INDIRECT etc...)Name: _PivotData RefersTo: =$A$1:INDEX($A:$IV,COUNTA($A:$A),COUNTA($1:$1))
However, further to it's "native" flexibility it's also very useful in VBA specifically when working with Arrays.
Let's assume we have a numeric 2d Array which for sake of "alternatives" is not sourced from a Range object.
Using a very basic (and admittedly unrealistic) set up along the lines of:
Let's now assume we want to Sum the contents of the 2nd "Column" of our 2d Array without having to iterate each "item", how can we do this ?Public Sub Example() Dim lngY As Long, lngX As Long Dim MyArray(1 To 50000, 1 To 4) As Double For lngY = LBound(MyArray, 1) To UBound(MyArray, 1) Step 1 For lngX = LBound(MyArray, 2) To UBound(MyArray, 2) Step 1 MyArray(lngY, lngX) = Rnd Next lngX Next lngY End Sub
INDEX offers us a very simple method:
The same can be applied for a given "row" - say the 10th row of our ArrayDebug.Print Application.Sum(Application.Index(MyArray,0,2))
This is very handy but what if we wanted to sum an area within our Array - say from "row" 50 to "row" 15000 (inclusive) whilst omitting "columns" 1 & 4 ?Debug.Print Application.Sum(Application.Index(MyArray,10,0))
In terms of the native function and a range this is relatively straightfoward, if we assume our 50000 x 4 matrix is A1:D50000 then:
but how can we reflect the same in VBA using INDEX ?=SUM(INDEX(A:D,50,2):INDEX(A:D,15000,3)) or =SUM(INDEX(B:B,50):INDEX(C:C,15000))
Previously I thought this was not possible... then along came our very own lecxe with this very clever trick:
note: you could use shorthand for the Evaluate if preferred - ie [row(50:15000)]Debug.Print Application.Sum(Application.Index(MyArray, Evaluate("ROW(50:15000)"), Array(2, 3)))
That's pretty darn clever !!!
Better yet this technique can handle non contiguous parts of the source array - say we wanted the same rows but to sum columns 1 & 4 rather than 2 & 3:
that's super clever !!! especially when you consider the "native" INDEX equivalent:Debug.Print Application.Sum(Application.Index(MyArray, Evaluate("ROW(50:15000)"), Array(1, 4)))
=SUM(INDEX(A:A,50):INDEX(A:A,15000),INDEX(D:D,50):INDEX(D:D,15000))
Better still (is this even possible?!) pending actions we could even revert the ordering of the "columns"
So let's now assume that we have a 5th Column in our Array which is populated with strings which may or may not repeat ("apple", "banana" etc)
We now want to match "apple" in between "rows" 50 and 15000 in our Array and return the value associated with that match from "Column" 3
Using the above technique we could create a right to left VLOOKUP using this sub array technique
Debug.Print Application.VLookup("apple", Application.Index(MyArray, Evaluate("ROW(50:15000)"), Array(5, 3)), 2, 0)
If you feel you must reply to the above (not expected) please note the following:
Following on from the above:a) the above is not a question
b) none of the techniques outlined above are being advocated as "best practice"
c) the set ups above are entirely hypothetical
though there are countless alternative methods for all of the above they it is not the intention to discuss them here - this post is merely to demonstrate lecxe's Evaluate w/INDEX trick.
The post in which this trick was first brought to my attention resides a) on another Excel Message Board and b) within a restricted forum of that Message Board so at this stage I can not provide a direct link unfortunately.
Last edited by DonkeyOte; 01-01-2011 at 04:08 PM. Reason: typo in c) formula
My Recommended Reading:
Volatility
Sumproduct & Arrays
Pivot Intro
Email from XL - VBA & Outlook VBA
Function Dictionary & Function Translations
Dynamic Named Ranges
Hi
This is how I got to the vba statement that DO posted to sum some values in an array:
(I used just 10 rows to make it easier to test in the worksheet)Debug.Print Application.Sum(Application.Index(MyArray, Evaluate("ROW(5:14)"), Array(1, 4)))
It sums elements in rows 5 to 14 in columns 1 and 4 of the array.
I was inspired by how the Index() function works in the worksheet with ranges.
In this case, to add the values in the cells in rows 5 to 14 in columns A and D one could use an efficient formula like the one DO posted:
=SUM(INDEX(A:A,5):INDEX(A:A,14),INDEX(D:D,5):INDEX(D:D,14))
It's not possible, however, to reproduce it directly in vba.
I thought then of another way, using Index() as an array function that returns an array, to extract the values into a contiguous rectangular range and then to sum the result range.
For ex., for this vba statement:
- select I1:H9
- in the formula bar write: =INDEX(A:D,ROW($5:$14),{1,4})
- confirm with CSE
Now in I1:I9 you have A5:A14 and in H1:H9 you have D5:D14.
The values in this rectangular range is what you get in vba with
,you get a 2D array with these values.Application.Index(MyArray, Evaluate("ROW(5:14)"), Array(1, 4))
Now in another cell you'd use "=SUM(I1:H9)" to get the sum of these values. That's what you get with the vba
Although this way of calculating the sum of the values in the cells in the worksheet does not seem direct or efficient it has the advantage of allowing for a direct reproduction in vba in this simple and concise way:Application.Sum(2D array calculated before)
This statement replicates exactly the same behaviour of Index() in the worksheet but in vba it can also be used with arrays.Debug.Print Application.Sum(Application.Index(MyArray, Evaluate("ROW(5:14)"), Array(1, 4)))
Happy new year!
lecxe
@lecxe, thanks for this great post - I trust others who find it interesting will "tip your scales" accordingly.
One final use for Index in VBA (which lecxe kindly reminded me of) is something I used earlier today, namely:
circumventing the need for Transpose when creating 1d Arrays from Horizontal Range Vectors:
Dim vArray As Variant vArray = Application.Index(Range("A1:J1").Value,1,0)
Last edited by DonkeyOte; 01-02-2011 at 05:04 PM. Reason: removed assertion re: Vertical Vectors as plainly not true (thanks lecxe ;)!)
My Recommended Reading:
Volatility
Sumproduct & Arrays
Pivot Intro
Email from XL - VBA & Outlook VBA
Function Dictionary & Function Translations
Dynamic Named Ranges
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks