I am trying to avoid the use of loops for populating arrays since they take a lot of time when managing a lot of data.
Apparently as well, that is possible and easy in VBA but often results in problems.
Here is the code:
sub populate()
'put the whole column in an array
Dim AppArray() As Variant
Dim AppRange As Range
'calculate the last row of the column 1 of sheets
Dim LstRow As Integer
LstRow = Sheets("whole").Cells(Sheets("whole").Rows.Count, "A").End(xlUp).row
'here I calculate the range that I want to pass to the array
Set AppRange = Sheets("whole").Range(Cells(1, 1), Cells(LstRow, 1))
MsgBox ("apprange " & AppRange.Address)
'i dont know if I need to redim or not
ReDim AppArray(1 To LstRow)
'here comes the point. populate the array with the values of the range
AppArray = AppRange.Value
End Sub
This does not work. I also tried application.tranpose(AppRange.Value).
I used:
For i = 1 To LstRow
Debug.Print AppArray(i)
Next
and an error appears, so somehow there is no AppArray(1).
I would be very happy if you can comment on that. More than just arranging the code suggest even other pages (links) to populate arrays with values of ranges when these ranges are not known in advance.
If the case is that looping is very time consuming and that arrays can be populated straight away, I don't understand why 99% of the pages referring to arrays use a loop (or nested loop) to populate an array.
I found the answer.
dim myRange as range
dim myArray() as variant
myRange = range(cells(2,3),cells(10,15))
redeem myArray(1 to 20,1 to 20)
myArray=myRange
It's always much faster to work with variables and arrays than with cells values.
Related
I previously used the following code -
Dim HSArr(2 To 250) As Variant
Dim HSVal As Long
For HSVal = LBound(HSArr) To UBound(HSArr)
HSArr(HSVal) = Cells(HSVal, 1) & " " & Cells(HSVal, 2)
Next HSVal
It was an array that would concatenate column A and B, then the array would be output onto the worksheet in "P2:P250".
Sub SumData()
Const dFormula As String _
= "=IFERROR(SUMPRODUCT(--(P$2:P$250=I2),D$2:D$250,F$2:F$250),"""")"
With ThisWorkbook.Worksheets("Sheet1").Range("K2:K250")
.Formula = dFormula
.Value = .Value
End With
End Sub
This code does what I want to an extent but requires "P2:P250" to contain the array output, but I don't want to output anything onto the worksheet.
There's a piece of information that I do not understand here, how I can introduce those values from the array into the SUMPRODUCT (instead of "P2:P250"), even if its not by methods of array, as AFAIK I can't use that as a range without it being on the worksheet itself. Any idea?
Building an array with VBA then trying to use it with a formula in a worksheet without placing that information into a worksheet generally wouldn't work, however here you can use the source data in the formula instead:
=IFERROR(SUMPRODUCT(--(A$2:A$250&" "&B$2:B$250=I2),D$2:D$250,F$2:F$250),"""")
At risk of being of topic, I decided to share some code, Q&A-style. If the general opinion is such that this would be off-topic I'll be happy to delete if need be.
Background
Having a Range object holding a certain amount of values I would want to pull those values into an array. The conventional way (at least to me) would be to use ""scripting.dictionary", loop through a Range, or rather an Array in memory, to add these values into a uniques list.
While that works, I wanted to see if there is a method to pull an array of unique items without any loop.
Sample
Imagine the following data in A1:A8:
Vals
A
B
A
B
C
C
B
Question
To retrieve a 1D-array of unique items {A,B,C}, how would we go about doing this without a loop?
Uniques - Dictionary
A very solid (and fast) way of returning a 1D-array of unique values would be to use a conventional Dictionary object as below:
Sub UniquesDictionary()
Dim lr As Long, x As Long
Dim arr As Variant
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
With Sheet1
'Find the last used row
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
arr = .Range("A2:A" & lr).Value
End With
'Loop through memory and fill dictionary
For x = LBound(arr) To UBound(arr)
dict(arr(x, 1)) = 1
Next x
'Pull unique items into a 1D-array
arr = dict.Keys
End Sub
Uniques - Evaluate
Whilst the above works. The wish was to avoid any loop. The way to do this is to use .Evaluate, see below:
Sub UniquesEvaluate()
Dim lr As Long
Dim arr As Variant
With Sheet1
'Find the last used row
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
'Get array of unique values
arr = Filter(.Evaluate("TRANSPOSE(If(Row(A2:A" & lr & ")<>MATCH(A2:A" & lr & ",A2:A" & lr & ",0)+1,""|"",A2:A" & lr & "))"), "|", False)
End With
End Sub
It might seem like a long formula but it really isn't that impressive and boils down to:
=IF(ROW(A2:A8)<>MATCH(A2:A8,A2:A8,0)+1,"|",A2:A8)
The TRANSPOSE is only there to return a 1D-array for FILTER to work with.
The inital formula will only return those values that are sitting on the rows where they are first encountered through MATCH, otherwise it would return a pipe-symbol.
Thus TRANSPOSE(<formula>) returns a 1D-array, e.g.: {A,B,|,|,C,|,|}
Filter then uses this 1D-array as an input, returning an array filtering out those pipe-symbols using FALSE in the "include" parameter, e.g: Filter(<arr>,"|",FALSE) > {A,B,C}.
Comparison
This would only have real purpose if this method would be equally as fast as the more conventional Dictionary so I did a small comparison. As far as my testing goes, there was no really noticable timing difference (around 0 seconds), but since in essence the Evaluate is a CSE formula, larger datasets will get noticable timing differences above let's say 2000 rows of data.
Hopefully this is usefull to those working with smaller datasets.
I am working through an Access 2010 VBA script. I am pulling information from a temporary table that may have 1 to 10 records in it. I am using GetRows to build a two-dimensional array that looks like this, for example:
**0** **1**
8677229 1
10289183 2
11981680 3
13043481 4
I have tested the array function by debug print, and the array does contain values. I want to split out the array values dynamically into variables for later use. In this example, I would like the 0 column to produce 4 variables named Anum(0) through Anum(3), and the 1 column to produce 4 variables named Pay(0) through Pay(3). However, I keep getting a "Error 9, subscript out of range" where indicated below:
Dim db As Database
Dim rs As Recordset
Dim PayAcct As Variant
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT Anum, Pay FROM PayPerAcct")
Dim highval As Integer
Dim i As Integer
i = 0
While Not rs.EOF
PayAcct = rs.GetRows(10)
Wend
highval = UBound(PayAcct, 2)
Dim Anum() As Variant
Dim Pay() As Variant
For i = 0 To highval
'error occurs on the below line
Anum(i) = CStr(PayAcct(0, i))
Pay(i) = CStr(PayAcct(1, i))
Next i
When I manually define Anum and Pay variables and use the Cstr(PayAcct(1,0)) operation, it passes the expected value from the array to the variable, so I don't think that is the problem.
I suspect I am assigning Anum() and Pay() the wrong dimension, because I have seen similar example code in Excel VBA where defining those variables as Range works. I have tried defining Anum() and Pay() as Range (which doesn't work, because this is Access VBA), Variant, and Object.
Any thoughts or tips?
Edit -
The below ended up working, thanks for your help:
Dim Anum() As String
ReDim Anum(0 To highval)
Dim Pay() As String
ReDim Pay(0 To highval)
Dim Anum() As Variant
declares a dynamic array that hasn't any elements yet, so you can't assign values to it. It needs a ReDim to use it.
But since you already know how many elements you need, what you want is:
Dim Anum(0 To highval) As Variant
or if you know that you will only store strings in it,
Dim Anum(0 To highval) As String
I want to have an array list in vba, hence I have a variant declared in excel vba like:
Dim Students(10) as variant
Now I want to store numbers in Students list. the numbers are not continuous. Sometime like:
Students(2,7,14,54,33,45,55,59,62,66,69)
How can I do this in vba? also how can I access the list items?
Students must be declared as a dynamic array. That is, an array whose bounds can be changed. Dim Students(10) gives an array whose bounds cannot be changed and cannot be loaded from an array.
Dim Students() As Variant
To load Students:
Students = Array(2,7,14,54,33,45,55,59,62,66,69)
To access the elements:
Dim Inx As Long
For Inx = LBound(Students) to UBound(Students)
Debug.Print Students(Inx)
Next
LBound (Lower bound) and UBound mean that the for loop adjusts to the actual number of elements in Students.
This is too complex for you right now, and you'll probably never run into a situation where you'll need this, but:
I use the following method for forming more memory-efficient arrays (because Variant uses the most memory of any variable type) while still having the convenience of declaring the array contents in one line. To follow your example:
Dim Students() As Long
Dim Array2() As String
Array2() = Split("2,7,14,54,33,45,55,59,62,66,69", ",")
ReDim Array1(0) As Long
For Loop1 = LBound(Array2()) To UBound(Array2())
ReDim Preserve Array1(0 To (UBound(Array1) + 1)) As String
Array1(Loop1) = Array2(Loop1)
Next Loop1
ReDim Preserve Array1(0 To (UBound(Array1) - 1)) As Long
Erase Array2
An example of accessing it would be something like:
For Loop1 = LBound(Students) to UBound(Students)
Msgbox Students(Loop1)
Next Loop1
I learned this from here: http://www.vbforums.com/showthread.php?669265-RESOLVED-VBA-Excel-Assigning-values-to-array-in-a-single-line&p=4116778&viewfull=1#post4116778
You can add values to an Array like this...
For i = 1 to 10
Students(i) = i
Next i
Or like this
Students = Array(2,7,14,54,33,45,55,59,62,66,69)
Then you can access the values in the same manor. Note if you use the second option you'll need to declare it as follows:
Dim Students() As Variant
Well,
That depends on how you would supply the values for the array, would you get the values from Worksheet.Range or from TextBox or ListBox , But basically the code would be something like that :
Dim students(10) as Integer
Dim Carrier as Integer
For i = LBound(students) To UBound(Students)
'some code to get the values you want to from whatever is your source
'then assign the value to Carrier
students(i)=Carrier
Next i
It is not good practice to dim an array as Variant when you certainly know that you are going to use integers only, as it will eat alot of memory that is not needed in the first place.
You also should be aware of the bounds of the numbers that are going to be assigned, if it exceeds the Integer limit then you should use Double or Float.
This is my first participation in the site,Cheers.
I'm puzzled why I can't msgbox this joined array. I can do it just fine if I create a static array with typed out values, but with a range of values from excel I keep getting "Invalid Procedure Call or Argument"
I've done a fair amount of research but I'm not able to find any examples of this issue. What am I doing wrong here?
Sub From_sheet_make_array()
Dim myarray() As Variant
Dim dudeString As String
myarray() = Range("B2:B10").Value
dudeString = Join(myarray(), ", ")
MsgBox dudeString
End Sub
The cookie goes to brettdj as resizing a 1D array and populating it is the best way to go (fastest) but I wanted to offer a more lesser-known compact solution in the case that you don't plan to use this on long arrays. It's not as fast than the 1D approach, but not slow like concatenation, but it's convenient when you want to write together fast code (easier not to make typos with one-liners)!
myArray = Range("B2:B10").value
myString = Join(WorksheetFunction.Transpose(myArray), ", ")
or even just:
myString = Join(WorksheetFunction.Transpose(Range("B2:B10").value), ", ")
A variant array created directly from a sheet range is 2D (ie it has rows and columns) - Join requires a 1D array.
So you would need to do something like this
[Updated to read range into variant array, then to convert variant array into 1D array for joining - avoids cell loop]
Note also that using TRANSPOSE as Issun has below on a single column does force a 1D ouctome immediately. So another alternative would be to loop through the columns or rows of a 2D variant array, and TRANSPOSE them column by column (or row by row) to quickly produce a 1D array.
Sub From_sheet_make_array()
Dim X
Dim lngRow As Long
Dim myArray()
X = Range("B2:B10").Value2
ReDim myArray(1 To UBound(X, 1))
For lngRow = 1 To UBound(X, 1)
myArray(lngRow) = X(lngRow, 1)
Next
Dim dudeString As String
dudeString = Join(myArray, ", ")
MsgBox dudeString
End Sub