changing size of 2D array with vba - arrays

I always have trouble with Arrays which is why I usually avoid them but this time I'm trying to get my head round them
I'm trying to change the size of my Global Array inside vba
I have declared it using Public UseArr() As String
Now I've written a function that searches an SQL table and returns user information as a record set.
I want to take this record set and put it into my Global Array
This is the bit of code I've written for populating it
a = 0
If Not Not UseArr Then
For i = 0 To UBound(UseArr)
If StrComp(UseArr(i, 0), rs("Alias")) = 0 Then a = 1
Next i
b = i
Else
b = 0
End If
If a = 0 Then
ReDim Preserve UseArr(0 To b, 0 To 10)
With rs
If Not .BOF And Not .EOF Then
For j = 0 To 10
If Not rs(j) = "" Then
UseArr(b, j) = rs(j)
Else
UseArr(b, j) = "Null"
End If
Next j
End If
End With
End If
The idea being if the user is already in there it doesn't populate, and if not it populates.
It works fine for initialising the Array however when I go to put in a second user it throws a resize error.
Can anyone help?
Thanks in advance
Tom
Update with Dictionary Attempt
If UseList Is Nothing Then
Set UseList = New Dictionary
MsgBox "Created New"
End If
If UseList.Exists(rs("Alias")) Then
Dim temp()
For i = 0 To 10
temp(i) = rs(i + 1)
Next i
With UseList
.Add Key:=rs("Alias"), Item:=temp
End With
End If
Debug.Print UseList

You can only Redim Preserve the last dimension of a multi-dimensional array - see here. Have you considered using a Collection or Dictionary instead?
edit: using the code you've posted above, here's how you would display element 4 from the array associated with the key "tom"
MsgBox UseList("tom")(4)
or equivalently
MsgBox UseList.Item("tom")(4)

Here you have some explanation about how a Dictionary object works and some of its attributes and functions.
I think it's the best to reach your goal because they are so easy to use, fast and efficient.
First you have to import the mscorlib.dll into the Project References.
After you can use something like this to declare the dictionary:
Dim UseDict As Dictionary
Set UseDict = New Dictionary
To know if the Key you're searching is not in the Dictionary and then add the new user:
If Not UseDict.Exists(Key) Then
UseDict.Item(Key) = 1
End If
The Value is not important here, but if you wanted to count how many times a key appears somewhere, you could increment the value when UseDict.Exists(Key) = True.
That's what the Dictionaries, Hash-maps or Maps stand for: count and search efficiently.
Hope it helps!

I attach a code with some corrections. I think the problem is that you are trying to access to an array as if it was a variable. That means you have to loop through the item of a key.
I add comments to the code below:
Update
If UseList Is Nothing Then
Set UseList = New Dictionary
MsgBox "Created New"
End If
If UseList.Exists(rs("Alias")) Then
'i think is better to specify the data type and the dimension.
Dim temp(10) as string
'if you loop from 0 to 10 the array will have 11 elements
For i = 0 To 9
temp(i) = rs(i + 1)
Next i
'This will work also and looks nicer (in my opinion) than the method
'you pasted, but if that worked anyway don't change it ;)
UseList(rs("Alias")).Item = temp
End If
Now, if you want to retrieve the result you must:
For i = 0 To UBound(UseList.Item(rs("Alias")) - 1
Debug.Print UseList.Item(rs("Alias"))(i)
Next i
Give me feedback when you test the code, please :)

Related

Is there a way to transfer all values from one array to another, then erase the original array?

I'm running into a problem with a block of code I'm trying to develop at my job. Essentially, I'm creating a userform in excel where folks will enter data for railcars as they get loaded at a certain location (we'll call these "spot 1, spot 2, spot 3, etc.").
Sometimes they'll have to move that car to a different spot, in which case I want them to be able to keep all the information on the railcar from the first/original entry, and then erase the data from the original spot once that's done.
To accomplish this in a more streamlined fashion, I've established arrays for each of the 5 spots that reference all the cells they're entering data into on the userform:
Dim spot1information(14)
spot1information(0) = UserForm.ProductType1.Value
spot1information(1) = UserForm.ProductID1.Value
spot1information(2) = UserForm.BatchID1.Value
etc....
Dim spot2information(14)
spot2information(0) = UserForm.ProductType2.Value
spot2information(1) = UserForm.ProductID2.Value
spot2information(2) = UserForm.BatchID2.Value
etc....
And so forth for all five spots. I don't know if this makes things more difficult, but note that these array values aren't all of the same type. For instance, index (0) will be a string, but index (10) is a DATETIME and index (12) is defined as Long.
So say that they are moving a car from spot 1 to spot 2. In short, I want the code to do the following:
Replace the values of indices 0 - 6 in spot2information (which is currently empty) with the values of indices 0 - 6 in spot1information (which the user has filled on the userform).
I'm only interested in carrying over indices 0-6 because they contain the pertinent railcar information
Empty every value of spot1information to ""
To accomplish this, I tried the following code and a few variations thereof:
If OriginalSpot.Value = 1 Then
If DestinationSpot.Value = 2 Then
For i = 0 to 6
spot2information(i) = spot1information(i)
Next
For Each i in spot1information
spot1information(i) = ""
Next
End If
End If
However, this keeps coming up with a type mismatch. I figure because the data in the spot2information array is empty, and the data in the spot1information array is not, but I'm not entirely sure of a way around this.
Update: I did what was suggested below and replaced: spot1information(i) = "" with Erase spot1information
The code now essentially works! The values of array "spot2information" are now the former values of "spot1information", with "spot1information" now empty.
The 2D array suggested below also works like a charm. New problem I've been facing is that array values are updating, but the userform isn't. (note: in the future I'll be posting this type of thing as a separate question, my apologies!)
Easier to manage this as a 2D array:
Sub Tester()
Dim spots(1 To 5, 0 To 14), n As Long, i As Long
'fill your spot arrays from the form....
For n = 1 To 5
spots(n, 0) = UserForm.Controls("ProductType" & n).Value
spots(n, 1) = UserForm.Controls("ProductID" & n).Value
spots(n, 2) = UserForm.Controls("BatchID" & n).Value
'etc etc
Next n
'swap a spot with another
Debug.Print spots(2, 1), spots(3, 1)
SwapSpots spots:=spots, fromSpot:=2, toSpot:=3
Debug.Print spots(2, 1), spots(3, 1)
End Sub
Sub SwapSpots(spots, fromSpot As Long, toSpot As Long)
Dim n As Long
For n = 0 To 6
spots(toSpot, n) = spots(fromSpot, n)
spots(fromSpot, n) = Empty 'empty the source slot value
Next n
End Sub
Assuming the DataType of the arrays is the same by Index i.e. index(0) is string for all spots, Index(2) is long for all spots, and so on.
If that is the case then this part should not produce any error:
For i = 0 to 6
spot2information(i) = spot1information(i)
Next
The error should be happening in this part more precisely in the line marked with #
For Each i in spot1information
spot1information(i) = "" '#
Next
and the reason for the error it seems to be that trying to assign a string value "" to a numeric type, given the "mismatch" error.
Using For Each i in spot1information indicates that you want to "Initiate" or Erase the entire array, therefore I suggest to use this line instead of the For…Next method.
Erase spot1information
In regards this:
But I've now run into a new problem, where the values on the userform haven't updated to reflect the new values stored in the array. Do I need to somehow "refresh" the userform?
You just updated the arrays, then you need to run the procedures used to update the values of the objects affected by both arrays in the UserForm.

Vb.Net Sort 3x One Dimensional Arrays

I have 3 one dimensional arrays.
Each contains information that corresponds to the other 2 arrays.
e.g Array 1 contains a customer first name
Array 2 contains a customer last name
Array 3 contains the customer phone number.
This is not my actual example but is easiest to explain.
How do I sort all three arrays so that they are sorted in order by say customer last name.
If Mr Smith is sorted and has moved from position 10 to position 5 in the lastname array, I would expect his phone number and first name to also be in position 5 in the respective arrays.
I am dealing with arrays with 10,000's of items so I would like to avoid looping (my current method) as this is incredibly slow.
Hoping to use the array.sort methods.
Can someone help me?
Ok - So I have tried to use a new data Type but am still at a loss how I can instantly filter using this. Below is my sample code which has a couple of issues. If someone can resolve - it would love to learn how you did it.
The purpose of the code is to return an array containing grouped issues.
For simplicity I have assumed in the example that each constant found is an issue.
If an issue is found, combine it with other issues found on that same worksheet.
e.g The number 2 is found in both cells A1 and A2 on sheet 1. The array should return A1:A2.
If the issues are found in A1 on sheet 1 and A2 in sheet 2, two seperate array entries would be returned.
Test File and Code Here
Public Type Issues
ws_Sheet As Integer
rng_Range As String
s_Formula As String
s_Combined As String
d_ItemCount As Double
End Type
Sub IssuesFound()
Dim MyIssues() As Issues
Dim i_SheetCount As Integer
Dim s_Formula As String
Dim rng_Range As Range
Dim d_IssueCounter As Double
Dim s_SearchFor As String
Dim a_TempArray() As Issues
Dim d_InsertCounter As Double
d_IssueCounter = -1
' Loop All Sheets Using A Counter Rather Than For Each
For i_SheetCount = 1 To ActiveWorkbook.Sheets.Count
' Loop all Constants On Worksheet
For Each rng_Range In Sheets(i_SheetCount).Cells.SpecialCells(xlCellTypeConstants, 23)
If d_IssueCounter = -1 Then
' First Time and Issue Is Found, Start Recording In An Array
d_IssueCounter = d_IssueCounter + 1
ReDim MyIssues(0)
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Going To Look For Issues Found On The Same Sheet with The Same Constant Value
s_SearchFor = i_SheetCount & "#" & rng_Range.Value
' HELP HERE: Need To Ideally Return Whether The Above Search Term Exists In The Array
' Without looping, and I want to return the position in the array if the item is found
a_TempArray = MyIssues 'Filter(MyIssues.s_Combined, s_SearchFor, True, vbTextCompare)
If IsVarArrayEmpty(a_TempArray) = True Then
' New Issue Found - Increase Counter By + 1
d_IssueCounter = d_IssueCounter + 1
' Increase The Array By 1
ReDim Preserve MyIssues(d_IssueCounter)
' Record The Information About The Constant Found. Sheet Number, Constant, Range, and also a combined string for searching and the array position
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Get The Array Position Where Other Issues With The Same Worksheet and Constant are Stored
d_InsertCounter = a_TempArray.d_ItemCount
' Add The New Found Constant To The Range Already Containing The Same Constants on This Worksheet
MyIssues(d_InsertCounter).rng_Range = Union(rng_Range, Range(MyIssues(d_InsertCounter).rng_Range)).AddressLocal
End If
End If
Next
Next
End Sub
Function IsVarArrayEmpty(ByRef anArray As Issues)
Dim i As Integer
On Error Resume Next
i = UBound(anArray, 1)
If Err.Number = 0 Then
IsVarArrayEmpty = False
Else
IsVarArrayEmpty = True
End If
End Function
Sample Test File and Code Here
As suggested, you should not be using concurrent arrays at all. You should be defining a type with three properties and then creating a single array or collection of that type.
To answer your question though, there is no way to sort three arrays in concert but there is a way to sort two. What that means is that you can create a copy of the array that you want to use as keys and then use the copy to sort one of the other arrays and the original to sort the other. Check out the documentation for the Array.Copy overload that takes two arrays as arguments.
That said, copying the array and then sorting twice is a big overhead so you may not gain much, if anything, from this method. Better to just do it the right way in the first place, i.e. use a single array of a complex type rather than concurrent arrays of simple types. It's not 1960 any more, after all.

How to prevent dynamic array from including a blank first element

I'm really struggling with why my array keeps including an empty element as its first element.
I'm populating an array from the selected choices in a listbox and I keep getting an empty first element in the array. I'm not getting any empty elements afterwards, just for the first element.
I've looked at lots of code to remove an empty element, all of which is beyond my understanding of arrays at this point, but that is not ideally for what I'm looking: I'm looking to prevent that empty element from appearing in my array in the first place.
Would anyone please help me understand what I need to change? I've tried using Option Base 1 and iterating from 1 instead of 0, but I get an error when trying to do so. Additionally, I did Redim my array as (0 To 0) and (0 to UBound) as well but nothing changed from my code below.
I'm assuming it has to do with the way I'm iterating through my listbox elements but I'm not sure how to fix the issue.
ReDim Part_Number_Array(1 To 1) As Variant
For Selected = 0 To Part_Number_Select.ListCount - 1
If Part_Number_Select.Selected(Selected) = True Then
ReDim Preserve Part_Number_Array(1 To UBound(Part_Number_Array) + 1) As Variant
'Add Part Number to the Array
Part_Number_Array(UBound(Part_Number_Array)) = _
Part_Number_Select.List(Selected)
'UpperBoundCount = UBound(Part_Number_Array)
'MsgBox "The upper bound is " & UpperBoundCount
End If
Next Selected
Thanks in advance for your help.
That is a known vb limitation which causes us all some "Empty/not empty" and member-count issues.
I have dealt with this by:
Define a class with Add/Delete/Find/etc methods no showing the array and provide a Count property. Also, you can use a Collection (and then there are some interesting derivatives, like the Dictionaries)
OR
OPTION BASE 0 (to be more C like) and use a _Count variable, initialized to 0, to keep track exactly how many useful items does the array hold and do Redims 0 to NewCount+1.
I think it is because you are redimming 1 to 1 outside the loop, then inside the loop you are redimming again before adding another element to it, then when you do add the element you are using the ubound. So it seems what happening is, you're redimming 1 to 1, which gives the array 1 empty slot. then, inside the loop you are redimming it 1 to UBound(Part_Number_Array) + 1). the + 1 at the end adds another slot (ubound is currently 1, 1 + 1 = 2) and then, when you add the element you are using Part_Number_Array(UBound(Part_Number_Array)) = so that statement puts the element at the ubound of the array, which is 2, leaving the first spot blank.
The solution:
ReDim Part_Number_Array(1 To 1) As Variant
For Selected = 0 To Part_Number_Select.ListCount - 1
If Part_Number_Select.Selected(Selected) = True Then
'Add Part Number to the Array before redimming, thus putting the new element in the 1 empty slot
Part_Number_Array(UBound(Part_Number_Array)) = _
Part_Number_Select.List(Selected)
'UpperBoundCount = UBound(Part_Number_Array)
'MsgBox "The upper bound is " & UpperBoundCount
ReDim Preserve Part_Number_Array(1 To UBound(Part_Number_Array) + 1) As Variant
End If
Next Selected
then, after the loop you can do - ReDim Preserve Part_Number_Array(1 To UBound(Part_Number_Array) - 1) notice the minus 1, this will eliminate the last slot (which is empty because of having a redim statement without adding another element)

Sheet/Range Array to loop through making graphs

Apologies aside, my problem has become quite the curiousity and I can't find any answers as of yet.
A piece of my program is designed to search through a graph to see if a series already exists, and if it doesn't, it creates it. As of now the program is fine and dandy. HOWEVER, in doing so for 8 different graphs, the code is not only long, but could potentially be more inefficient. So, I decided to attempt to loop through an array. This is what one of the graphing blocks looks like:
EDIT: I didn't specifically say, but Z IS the name of a sheet AND the name of the series, as defined earlier in the code, so that is not an issue.
Sheets("A").Select
Count = ActiveChart.SeriesCollection.Count
Fail = 0
For c = 1 To Count
If ActiveChart.SeriesCollection(c).Name = Z Then
With ActiveChart.SeriesCollection(c)
.Values = Worksheets(Z).Range("AJ5:AJ45")
.XValues = Worksheets(Z).Range("AP5:AP45")
End With
Exit For
Fail = Fail - 1
End If
Fail = Fail + 1
Next c
If Fail = Count Then
ActiveChart.SeriesCollection.NewSeries
c = ActiveChart.SeriesCollection.Count
With ActiveChart.SeriesCollection(c)
.Values = Worksheets(Z).Range("AJ5:AJ45")
.XValues = Worksheets(Z).Range("AP5:AP45")
.Name = Z
.MarkerStyle = 1
.MarkerSize = 9
End With
End If
(With 7 more of those underneath, with different sheets and Y values). SO, I tried this:
Dim SheetArr(0 To 7) As Sheets
Set SheetArr(0) = Sheets("A")
Set SheetArr(1) = Sheets("B")
etc..
Dim RangeArr(0 To 7) As Range
Set RangeArr(0) = Range("AJ5:AJ45")
Set RangeArr(1) = Range("AK5:AK45")
etc..
And starting a loop, replacing individual values with SheetArr(i) and RangeArr(i) respectively so only one block of code exists, but none of the graphs even begin plotting.
Am I dimming arrays wrong, or is this just not possible?
(Sorry for a super long post, just trying to be as clear as possible.)
Instead of trying to create an array of sheets, which I'm not sure you can do, you can create an array of strings containing the sheet names.
Dim SheetNames(0 To 7) As String
SheetNames(0) = "A"
SheetNames(1) = "B"
...
Then use it like this
For IntSheet = 0 To 7
ThisWorkbook.Sheets(SheetNames(IntSheet)).Select
...
Next
Address Comment About Ranges:
I stepped through the code below, testing out the range assignment and the appropriate ranges were selected
Dim RangeArr(0 To 7) As Range
Set RangeArr(0) = Range("AJ5:AJ45")
Set RangeArr(1) = Range("AK5:AK45")
RangeArr(0).Select 'Selects AJ5:AJ45
RangeArr(1).Select 'Selects AK5:AK45

VBA - Use multidimensional array instead of two separate ones?

Lots of stupid questions lately, but I would appreciate some input on this one.
I've got a string that comes from an INI-file. It looks like Firstname=FIRSTNAME. This is basically an array with loads of these. I want to split these up, but preserve both of them. So, I managed to put Firstname in it's own array and FIRSTNAME in it's own. But then a colleague of mine said "Why don't you just use a multidimensional array instead?". And this got me thinking, putting Firstname in 0 and FIRSTNAME in 1. But how do I do that?
This is my code right now:
For iTeller = 0 To UBound(arrIniName)
If Not arrIniName(iTeller) = "" Then
arrIniName(iTeller) = Split(arrIniName(iTeller), "=")(0)
End If
Next
For iTeller = 0 To UBound(arrIniValue)
If Not arrIniValue(iTeller) = "" Then
arrIniValue(iTeller) = Split(arrIniValue(iTeller), "=")(1)
End If
Next
Both arrIniName and arrIniValues consists of the exact same array to begin with. Which looks like this:
arrIniName(0) "Fistname=FIRSTNAME"
arrIniName(1) "Lastname=LASTNAME"
arrIniName(2) "Initials=INITIALS"
So I basically split each one into their own separate arrays the way I do it now. But putting them in a multi dimensional array would probably be better? Because then I'd have just one array to manage and could also pull that array through a For Each loop.
Edit: I ended up doing it like this, where Values is the array
For Each s In Values
Dim strName, strValue
s = Split(s, "=")
strName = s(0)
strValue = s(1)
'Run function strName, strValue
Next
The ideal solution sounds like a Dictionary (a data structure which holds Key/Value pairs - exactly what you have in your INI file).
A multi-dimensional array would not be necessary here, as you only have 2 dimensions (key, and value). Arrays are generally more difficult to work with than dictionary's as they are hard to resize, so you need to know how many items you have upfront.
Therefore, I would suggest the following code:
Dim dict As Dictionary
Set dict = new Dictionary
Dim key as String
Dim value as String
For iTeller = 0 To UBound(arrIniValue)
If Not arrIniValue(iTeller) = "" Then
key = Split(arrIniValue(iTeller), "=")(0)
value = Split(arrIniValue(iTeller), "=")(1)
dict.Add(key, value)
End If
Next
However, if you want to use a multi-dimensional array, then the following will do it:
' Declare a 2-dimensional array, of dimensions "n by 2".
Dim results(UBound(arrIniValue), 2) As String
For iTeller = 0 To UBound(arrIniValue)
If Not arrIniValue(iTeller) = "" Then
key = Split(arrIniValue(iTeller), "=")(0)
value = Split(arrIniValue(iTeller), "=")(1)
results(iTeller, 0) = key
results(iTeller, 1) = value
End If
Next

Resources