excel fast access to a two dimensional array avoiding loop? - arrays

I have many sheets in my workbook that I have to access hundresds of times. Therefore I convert them into array and I work with the array data; much faster.
Asuming an array
dim myArray(1 to 1000, 1 to 2)
if I look for a value in column 2 according to column 1 the straight fordward way is using a loop
for i=1 to 1000
if myArray(i,1)="XXX" then
myValue=myArray(i,2)
next i
since I do taht hundreds of times I wonder if there is something like lookup function for a two dimensional array or something like this:
myvalue=function(myarray(1)="XXX",myarray(2))
thanks
jose

An alternative approach would be to sort the data in the array using QuickSort and use a Binary Search lookup algorithm. Depending on the size of your arrays and the number of times you want to do lookups on the arrays this could be faster. Or you could try using a Dictionary or Collection.
See my blog posts for examples and comparisons of these methods. https://fastexcel.wordpress.com/2011/08/02/developing-faster-lookups-part-3-a-binary-search-udf/ https://fastexcel.wordpress.com/2011/10/26/match-vs-find-vs-variant-array-vba-performance-shootout/ https://fastexcel.wordpress.com/2012/07/10/comparing-two-lists-vba-udf-shootout-between-linear-search-binary-search-collection-and-dictionary/

your code need "exit for"
For i = 1 To 1000
If myArray(i, 1) = "XXX" Then
myValue = myArray(i, 2)
Exit For
End If
Next i

Related

Most efficient way to parse in a large range in to an array VBA

I have a large range of data in excel that I would like to parse into an array for a user defined function. The range is 2250 x 2250. It takes far too long to parse each cell in via a for loop, and it is too large to be assigned to an array via this method:
dim myArr as Variant
myArr = range("myrange")
Just brainstorming here, would it be more efficient to parse in each column and join the arrays? Any ideas?
Thanks
You're nearly there.
The code you need is:
Dim myArr as Variant
myArr = range("myrange").Value2
Note that I'm using the .Value2 property of the range, not just 'Value', which reads formats and locale settings, and will probably mangle any dates
Note, also, that I haven't bothered to Redim and specify the dimensions of the array: the Value and Value2 properties are a 2-dimensional array, (1 to Rowcount, 1 to Col Count)... Unless it's a single cell, which will be a scalar variant which breaks any downstream code that expected an array. But that's not your problem with a known 2250 x 2250 range.
If you reverse the operation, and write an array back to a range, you will need to set the size of the receiving range exactly to the dimensions of the array. Again, not your problem with the question you asked: but the two operations generally go together.
The general principle is that each 'hit' to the worksheet takes about a twentieth of a second - some machines are much faster, but they all have bad days - and the 'hit' or reading a single cell to a variable is almost exactly the same as reading a seven-million-cell range into a variant array. Both are several million times faster than reading that range in one cell at a time.
Either way, you may as well count any operation in VBA as happening in zero time once you've done the 'read-in' and stopped interacting with the worksheet.
The numbers are all very rough-and-ready, but the general principles will hold, right up until the moment you start allocating arrays that won't fit in the working memory and, again, that's not your problem today.
Remember to Erase the array variant when you've finished, rather than relying on it going out of scope: that'll make a difference, with a range this size.
This works fine.
Sub T()
Dim A() As Variant
A = Range("A2").Resize(2250, 2250).Value2
Dim i As Long, j As Long
For i = 1 To 2250
For j = 1 To 2250
If i = j Then A(i, j) = 1
Next j
Next i
Range("A2").Resize(2250, 2250).Value2 = A
End Sub
I think the best options are:
Try to limit the data to a reasonable number, say 1,000,000 values at a time.
Add some error handling to catch the Out of Memory error and then try again, but cut the size in half, then by a third, a quarter, etc...until it works.
Either way, if we're using data sets in the order of 5,000,000 values and you want to make sure that the program will run, you will need to adjust the code to chop up the data.

What is the Best Method for Storing Data

I am creating a Word userform using VBA. I store several configuration using array in the program code, such as the following:
Public arrConfiguration[2, 3] as Integer
where index 2 represent type 0 to 2, and index 3 represent properties 0 to 3 for each type.
However, I planned to modify the program for larger amount of data (such as for 100 different types of data and 50 properties for each data).
My question is,
should I keep storing the data using array in the program, so that it will be
Public arrConfiguration[99, 49] as Integer
or store it in an Excel file, and make the program open the Excel file and access the cells repeatedly? Which one is better?
Thank you.
Please prefer excel. Sample example data image is appended here-under.
For cre­at­ing two dimen­sional dynamic array in excel, fol­low the steps below:
◾Declare the two dimen­sional Array
◾Resize the array
◾Store val­ues in array
◾Retrieve val­ues from array
Sub FnTwoDimentionDynamic()
Dim arrTwoD()
Dim intRows
Dim intCols
intRows = Sheet1.UsedRange.Rows.Count
intCols = Sheet1.UsedRange.Columns.Count
ReDim Preserve arrTwoD(1 To intRows, 1 To intCols)
For i = 1 To UBound(arrTwoD, 1)
For j = 1 To UBound(arrTwoD, 2)
arrTwoD(i, j) = Sheet1.Cells(i, j)
Next
Next
MsgBox "The value is B5 is " & arrTwoD(5, 2)
End Sub
In the Message Box you will get the following output.
Further To visualize a two dimensional array we could picture a row of CD racks. To make things easier, we can imagine that each CD rack could be for a different artist. Like the CDs, the racks would be identifiable by number. Below we'll define a two dimensional array representing a row of CD racks. The strings inside of the array will represent album titles.
For multidimensional arrays it should be noted that only the last dimension can be resized. That means that given our example above, once we created the array with two CD racks, we would not be able to add more racks, we would only be able to change the number of CDs each rack held.
You can simplify #skkakkar code:
dim x as variant
x = range("A1").CurrentRegion
No Redim, no loops.
Depending on how you see things evolving, you might want to consider accessing your Excel data via ADO, rather than OLE Automation. That way, if you decide to change your storage system to Access, SQL Server or something else, you will have less work to do.
How To Use ADO with Excel Data from Visual Basic or VBA (Microsoft)
https://support.microsoft.com/en-gb/kb/257819
Read and Write Excel Documents Using OLEDB (Codeproject)
http://www.codeproject.com/Tips/705470/Read-and-Write-Excel-Documents-Using-OLEDB

Populating an Array in VBA from different parts of a spreadsheet

I am trying to populate different portions of an array based on different locations within a spreadsheet.
For example
Dim prices(50) as double
prices(0, 1, 2) = Range("i35", "i38")
prices(3,4,5,6,7,8) = Range("b7","b12")
I'm getting a wrong number of dimensions error, because I'm assuming you have to populate the whole thing at once. Is there another syntax to do this better, or is it just a limitation of VBA?
Possibly a for each loop? But that doesn't really help since there are a lot of different ranges.
Thanks for your help
It looks like you are trying to tell VBA that the list of numbers in the parenthesis of the array are positions within an array that will receive multiple items.
That's not what it is. Instead, you can only do one at a time. For example:
Dim prices(50) as double
' My range syntax might be wrong here.
prices(0) = Range("i35", "i35")
prices(1) = Range("i36", "i36")
prices(2) = Range("i37", "i37")
prices(3) = Range("i38", "i38")
..etc. Or, if you're new to programming, you'll find this really cool:
for CounterVariable = 0 to 3
prices(CounterVariable) = Range("i" & 35 + CounterVariable)
next CounterVariable
To Wit ...
an array defined like Dim prices(50) will have one 'dimension', like a straight line.
Picture one row in a spreadsheet, 50 columns long.
If you said Dim prices(50,60) you would have a 2-dimensional array, like a cartesian plane (x,y) or a 50x60 cell spreadsheet where the first number is like the letters, the second, like the numbered rows.
Some languages support 3-dimensional arrays - VariableName(x,y,z). Don't know if VBA does
Therefore, using commas in parenthesis after an array is supported by VBA
BUT it means something completely different than you were hoping (and you can't change that): the values for the dimensions.
So your last line of code is looking for some value in a 6-dimensional universe! So we've got, height, width, depth, time, tesseract, and uh, you've beat me there!!!

Comparing Two Datasets - Slow

I have a set of data in the following format, although very simplified:
DealerName, AccountCode, Value
Dealer1, A-1, 5
Dealer2, A-1, 10
Dealer1, A-2, 20
Dealer2, A-2, 15
Dealer3, A-3, 5
I am trying to achieve an end result that gives me the data summed by AccountCode, so the following in the case of the above data:
AccountCode, Value
A-1, 15
A-2, 35
A-3, 5
I have done this by creating an array of distinct account codes named OutputData and then going through the data comparing the account code to the same field in SelectedDealerData and adding it to the existing values:
For i = 0 To UBound(SelectedDealerData)
For j = 0 To UBound(OutputData)
If SelectedDealerData(i).AccountNumber = OutputData(j).AccountNumber And SelectedDealerData(i).Year = OutputData(j).Year Then
OutputData(j).Units = OutputData(j).Units + SelectedDealerData(i).Units
Exit For
End If
Next j
Next i
There are around 10,00 dealers and 600-1000 account codes for each, so this means a lot of unnecessary looping.
Can someone point me in the direction of a more efficient solution? I am thinking that some kind of Dictionary compare but I am unsure how to implement it.
Add a reference to Microsoft Scripting Runtime for a Dictionary:
Dim aggregated As Dictionary
Set aggregated = New Dictionary
For i = 0 To UBound(SelectedDealerData)
With SelectedDealerData(i)
If aggregated.Exists(.AccountCode) Then
aggregated(.AccountCode) = aggregated(.AccountCode) + .Value
Else
aggregated(.AccountCode) = .Value
End If
End With
Next
For Each Key In aggregated.Keys
Debug.? Key, aggregated(Key)
Next
The code is slow because there are 10 million comparisons and assignment operations going on here (10,000 x 1000).
Also, looping through collections is not very efficient, but nothing can be done about that since the design is already set and maintained the way it is.
Two ways you can make this more efficient (you can time your code right now and see the % savings after these steps).
There are two condition-checks going on with an And. VBA will evaluate both even if the first one is false (no short circuiting). So put nested if then conditions, so that if the first condition fails, you do not proceed to checking the second one. Also, keep the condition more likely to fail in the outer if statement (so it fails fast and moves to the next element). At best, you get a minor speed bump here, at worst, you are no worse off.
There are too many comparisons happening here. Too late to change that, but you can go through the loops based on the following pseudocode if you can sort your collections or build an index which maintains their sort order (save that index array on your spreadsheet if you like). The sorting should be done based on a composite field called Account_Number_Year (just concatenate them)
You can use this concatenated field in the dictionary structure suggested by Alex K. So you can lookup this joint field in the second dictionary and then do operations if needed.
Code to try to fully implement it in VBA:
'Assuming both arrays are sorted
For i = 0 to Ni
MatchingIndex = _
BinarySearchForAccNumberYear(SelectedUserData(i).AccountNumberYear)
Next i
You can look up Binary Search here.
This will reduce your time complexity from O(n^2) to O(n log n) and your code will run an order of magnitude faster.

VBA Array multiplication, elementwise without looping

Hi everybody: Following liitle issue:
Option Base 1
Sub Test()
Dim aa() As Integer
Dim bb() As Integer
ReDim aa(3)
ReDim bb(3)
For j = 1 To 3
aa(j) = j * 2
bb(j) = j * 3
Next j
End Sub
Now the only little thing that I want to do is to multiply the two one dimensional arrays elementwise without looping, and then to unload this new array (6,24,54) in a range. I'm sure this must be easily possible. A solution that I would see is to create a diagonal matrix (array) and then to use mmult, but I'm sure this is doable in a very simple manner. Thanks for the help.
There is no way to do multiplication on each element in the array without a loop. Some languages have methods that appear to do just that, but under the hood they are looping.
As you've mentioned in your comments, you have 2 choices:
Loop through a range and do the multiplication
Dump the range into an array, do the multiplication, then dump back onto a range
It all depends on your data, but almost always you'll find that dumping a range into a variant array, doing your work, and dumping it back will be much faster than looping through a range of cells. How you dump it back into a range will also affect the speed, mind you.
It is possible to multiply ranges without explicit looping, e.g. try:
sub try()
[c1:c3].value = [a1:a3 * b1:b3]
end sub
Same logic as in:
=sumproduct(a1:A3-b1:b3;a1:A3-b1:b3)
I'm not entirely sure that this is possible in any language (with the possible exception of some functional languages). Perhaps if you told us why you wanted to do this we could help you more?

Resources