06 September 2007 @ 04:55 pm
Working with Arrays in VBA  

Getting Started with Arrays in Visual Basic for Applications


A good overview over arrays in VBA can be found in the MSDN. It covers the basics pretty well (with code examples), e.g.
- what arrays are and why you'd need them,
- how to create arrays and resize dynamic arrays,
- it talks about the differences between arrays and variants containing arrays,
- how to assign one array to another,
- how to return an array from a function or pass arrays to a procedure,
and much more...

There're a couple of VBA code examples for working with arrays and how to use arrays to work with values from an Excel worksheet.
There's also a post with examples about using arrays to work with Excel cell values and performance considerations in this journal.

Now, with the basics covered, there're a few more things about arrays to keep in mind:
  • You can't define constant arrays. If you need an array of constants, you'll have to use a variable array, fill it at runtime, and take care that you don't change it later on.


  • The declaration
    Dim myArray(10) As Integer
    is allowed, but you should better use
    Dim myArray(1 To 10) As Integer

    It makes the code easier to understand, and you can be sure about the lower boundary and not have to rely on the default setting (Option Base) being what you expect it to be (0 or 1). The same applies for ReDim.


  • Arrays as parameters are always passed ByRef, which means only a pointer to the array will be passed to the function/procedure, not the array itself. If you change the array in the procedure, the changes will be visible in the calling program.

    If you insist on passing an array by value, use
    Run "Procedurename", Parameter1, Parameter2, ...
    Since Run automatically converts all arguments into values, changes to the array in the called procedure won't affect the original array.


  • You can get the lower boundary of an array with the function
    LBound(myArray, dimension)
    and the upper boundary with the function
    UBound(myArray, dimension)

    dimension is the number of the dimension you want to know, with 1 being the first dimension and so on. For a one-dimensional array, you don't need to specify the 1.


  • Looping over all elements of an array with:
    For Each myElement In myArray
      mySum = mySum + myElement
    Next
    is usually faster than
    For i = LBound(myArray) To UBound(myArray)
      mySum = mySum + myArray(i)
    Next

    And you have the advantage that you don't have to worry what exactly the lower and upper boundaries of the array are.
    But the For Each variant only works for reading array elements! If you change myElement, the corresponding element in the array stays untouched.
    myElement must be of data type Variant, no matter of what data type the array elements are.


  • You can check whether a variable of data type Variant contains an array with
    IsArray(myVariant)


Useful Functions for Working with Arrays in VBA


The Array function is an easy way to create and fill an array with values. Pass all values as parameters to the function, and it'll return a one-dimensional array with these values as elements, in the same order as they were passed to the function. The first element has always the index 0, independent from any Option Base settings.

Sub TestArray()
Dim myArray() As Variant

  'create array from list of comma separated strings
  myArray = Array("One", "Two", "Three")
  'display array elements
  MsgBox myArray(0) & vbCr & myArray(1) & vbCr & myArray(2)
  'this also works with numbers as arguments
  myArray = Array(10, 20, 30)
  'display array elements
  MsgBox myArray(0) & vbCr & myArray(1) & vbCr & myArray(2)
End Sub
The Array function always returns an array of data type Variant, but the data type of the elements can differ. It depends on the type of value which is passed to the function.

For example,
Array("One", 2, 3.4)
would return an array with the first element being of data type String. The second element is of data type Integer and the last element is of data type Double.

If you pass nothing to the Array function, it'll return an empty array. In this case, the upper boundary of the array is -1, smaller than the lower boundary, which is always 0.
For example,
UBound(Array())
would return -1.


If you don't have all values separately, but rather a list of values in one string, you can use the Split function to separate them and create a one-dimensional array of strings. Again, the resulting array always begins with index 0.
You can specify the delimiter that separates the values in the string, e.g. comma or semicolon. If you don't specify a delimiter, the string will be split by the spaces.

If you pass an empty string to the Split function, it'll return an empty array. Like with the array function, the returned array has an upper boundary of -1 if it is empty.
You won't get an empty array when you pass a string to the Split function that doesn't contain the delimiter. In this case the returned array contains one element, which is the string itself.


The reverse of Split is the Join function. It takes an array of strings and returns one string containing all elements from the array. You can specify a delimiter, which will be added between each value.
If you pass an empty array to the Join function, it'll return an empty string.

Sub TestSplitJoin()
Dim myStr As String
Dim myArray() As String

  'string with values, delimited by comma
  myStr = "A1,B2,C3"
  'split string into array of substrings
  myArray = Split(myStr, ",")
  'display array elements
  MsgBox myArray(0) & vbCr & myArray(1) & vbCr & myArray(2)
  'concatenate all elements of array into one string,
  'with " and " connecting them
  myStr = Join(myArray, " and ")
  'display string
  MsgBox myStr
End Sub


If you want to check if a certain item exists in an array of strings, you could loop over all items and compare them with your match string. But you can also use the Filter function for this.

Filter(myArray, myMatch, myInclude)
takes myArray and compares each of its elements with the string in myMatch. Depending on myInclude being True (Default) or False, it'll return an array containing all elements of myArray that contain myMatch, or don't contain it.

The search is case sensitive, so if myMatch is a lower case letter, it won't find elements which contain this letter in upper case and vice versa.

Since the function returns a new array with the found elements, you don't get the indexes of the elements in the searched array. The function only tells you whether elements exist that contain/don't contain the match string, and which, but not where.

If no matching elements were found, the Filter function returns an array containing no elements, and the upper boundary of that array is -1.

Another limitation of the Filter function is that you can't tell it to look for exact matches only. It'll always return all (or none of the) elements that include the match string, in other words, it does Like comparisons, not checks for equality.

The function always compares strings, so if you filter a numeric array, it'll convert the numbers to strings and then check them. And because it doesn't look for exact matches only, a search for a number will return not only elements equal to the match, but also elements, which contain this number as a part, e.g.
Filter(Array(1, 10, 210), 1)
will return all elements of the array because each number has 1 in it.

A few examples to test the Filter function:

Sub TestFilter()
Dim myArray() As Variant
Dim myFilteredArray As Variant

  'create array
  myArray = Array("One", "Two", "Three")
  
  'filter array for elements containing "T"
  myFilteredArray = Filter(myArray, "T", True)
  'show result
  MsgBox "Filtering Array(""One"", ""Two"", ""Three"") " & _
         "for elements with ""T"" returned" & _
         vbCr & Join(myFilteredArray, vbCr)
  'filter array for elements not containing "T"
  myFilteredArray = Filter(myArray, "T", False)
  'show result
  MsgBox "Filtering Array(""One"", ""Two"", ""Three"") " & _
         "for elements without ""T"" returned" & _
         vbCr & Join(myFilteredArray, vbCr)
  'filter array for elements containing "t"
  myFilteredArray = Filter(myArray, "t", True)
  'show result
  MsgBox "Filtering Array(""One"", ""Two"", ""Three"") " & _
         "for elements with ""t"" returned" & _
         vbCr & Join(myFilteredArray, vbCr)
  'filter numeric array for "1"
  myArray = Array(1, 2, 3, 10)
  myFilteredArray = Filter(myArray, 1)
  'show result
  MsgBox "Filtering numeric Array(1, 2, 3, 10) " & _
         "for elements with 1 returned" & _
         vbCr & Join(myFilteredArray, vbCr)

End Sub

The following code is an example how to get only elements that match exactly:

Sub FilterExactly()
Const myMarker As String = "!"
Const myDelimiter As String = ","
Dim myArray() As Variant
Dim mySearchArray As Variant
Dim myFilteredArray As Variant

  'create array
  myArray = Array(1, 2, 3, 10)
  'pre-filter the array for elements containing 1
  myFilteredArray = Filter(myArray, 1)
  
  If UBound(myFilteredArray) > -1 Then
    'mark the beginning and end of each found element
    'myMarker and myDelimiter must be characters that
    'don't occur in any element of the array!
    mySearchArray = Split(myMarker & Join(myFilteredArray, myMarker & _
                    myDelimiter & myMarker) & myMarker, myDelimiter)
    'now filter modified array, include markers in search
    myFilteredArray = Filter(mySearchArray, _
                      myMarker & "1" & myMarker)
    'remove markers from result
    myFilteredArray = Split(Replace(Join(myFilteredArray, _
                      myDelimiter), myMarker, ""), myDelimiter)
  End If
  'show result
  MsgBox "Filtering Array(" & Join(myArray, ", ") & _
         ") for exact matches with 1 returned:" & _
         vbCr & Join(myFilteredArray, vbCr)
End Sub


Multidimensional Arrays vs. Arrays of Arrays



If an array has more than one dimension, it is called a multidimensional array. The number of dimensions is the number of indexes you need to identify an individual element. Lists of things are usually one-dimensional arrays. Tables are two dimensional arrays, and each element can be identified by giving the row and column index. Arrays with more than three dimensions are possible, but rarely used.

Sometimes the data structure in an application is two-dimensional but not rectangular. For example a calendar could be created as an array of months, with each month containing an array of days. Since different months have different numbers of days, the elements do not form a rectangular two-dimensional array.

Of course you could still use a two-dimensional array and just ignore the elements that represent invalid days (like 31st February). But you could also use a so called jagged array, or an array of arrays.
This is a one-dimensional array, of which each element contains an array again. These arrays don't have to be of the same size, so it fits the non-rectangular data structure much better.

In VBA, you can't declare a jagged array using the Dim statement. Instead, you'll declare a one-dimensional main array of type Variant (both static or dynamic are possible). Since variables of data type Variant can hold arrays, you can then assign the (sub)arrays to each element of this main array at run time. These (sub)arrays don't need to be of type Variant, the data type should correspond to the type of data that you want to store in them. But it's also possible to make them Variant again and put (sub)(sub)arrays in there.

While you'll access a single element of a two dimensional array this way:
myArray(Index1, Index2)

you'll address a single element of a jagged array like this:
myArray(Index1)(Index2)


The following example shows how to create and work with both types of arrays using our calendar example:

'Work with a two-dimensional array
Sub TestMulti()
Dim i As Integer
Dim myDays As Integer
Dim myYear(1 To 12, 1 To 31) As String
  
  'enter vacation days
  myYear(3, 15) = "Vacation"
  myYear(3, 16) = "Vacation"
  'count vacation days
  For i = 1 To 31
    If myYear(3, i) = "Vacation" Then
      myDays = myDays + 1
    End If
  Next
  'show result
  MsgBox "There are " & myDays & " vacation days in March."
End Sub

'Work with an array of arrays
Sub TestJagged()
Dim i As Integer
Dim myDays As Integer
Dim myYear(1 To 12) As Variant
Dim myMonth() As Variant
  
  'create array of arrays
  For i = 1 To 12
    'get correct number of days for a month
    myDays = Day(DateSerial(Year(Date), i + 1, 1) - 1)
    'create array for month
    ReDim myMonth(1 To myDays)
    'assign array to year
    myYear(i) = myMonth
  Next
  
  'enter vacation days
  myYear(3)(15) = "Vacation"
  myYear(3)(16) = "Vacation"
  'count vacation days
  myDays = UBound(Filter(myYear(3), "Vacation")) + 1
  'show result
  MsgBox "There are " & myDays & " vacation days in March."
End Sub

 
 
( 3 comments — Leave a comment )
(Anonymous) on February 19th, 2009 07:44 am (UTC)
array
1234.50
If i type this number it should show 1'st digit as name and 1'st 2digits as age and 1'st 3digit as education and 4digits as place and after point it should considered as email
shoshannauqo on June 7th, 2011 02:24 am (UTC)
Хороший блог!
Благодарю за информацию
(Anonymous) on July 20th, 2011 04:46 pm (UTC)
i agree
All can be
( 3 comments — Leave a comment )