Arrays
General Array Processing – Part 1
What is an Array?
An array is an indexed set of data. Arrays are used to group and index a set of values. The individual values held in array are called the elements of the array.
Declaring Arrays
Arrays are declared in the same manner as other variables (i.e., using the keywords "Dim", "Private", "Public", etc.), except that the array bounds are coded in parentheses following the variable name (if a fixed-length array is being declared) or an empty pair of parentheses follow the variable name (if a variable-length, or dynamic array is being declared). Fixed-length are used when you know in advance how many elements you need to work with; variable-length (dynamic) arrays are used when you don't know in advance how many elements you will be working with.
The syntax for declaring an array is:
[Dim | Private | Public | Protected | Friend] arrayname() [As datatype]
Sample declarations for a one-dimensional array:
Array Declaration |
Notes |
Dim aintCount(9) As Integer |
declares a 10-element array, indexed 0 to 9 |
Dim aintCount() As Integer |
declares a variable-length array whose bounds will be determined at run-time |
Note: Versions of VB prior to VB.NET allowed the lower bound of an array to be any number – for example an array declared as "Dim VB6Array(-4 To 5) As Integer" would have declared a 10-element array indexed -4 to 5 – however, this is no longer valid in .NET; the lower bound of an array is always indexed as zero.
To refer to an individual element of an array in a procedural statement, place the desired index in parentheses next to the array name. For example, the following statement will print the 5th element of aintCount:
Console.Write(aintCount(4))
Arrays can be multi-dimensional. For example, a two-dimensional array would represent a matrix of rows and columns (like an Excel spreadsheet). Sample declarations for multi-dimensional arrays:
Array Declaration |
Notes |
Dim asngSales(3,4) As Single
|
declares a 2-dimensional array (four rows indexed 0 to 3, by five columns indexed 0 to 4) |
Dim asngResults(3, 11, 4) As Single
|
declares a 3-dimensional array (the first dimension has four elements indexed 0 to 3, within that, the second dimension has 12 elements indexed 0 to 11, and within that, the third dimension has five elements indexed 0 to 4) |
To refer to an individual element of a multi-dimensional array in a procedural statement, place the desired indices in parentheses next to the array name (you must have one index per dimension, separated by commas). Examples:
Print asngSales(2, 3)
Print asngResults(0, 11, 4)
Initializing Arrays
Arrays can be initialized when they are declared using the following technique:
Dim astrStateName() As String = {"ALASKA", "ALABAMA", ... "WEST VIRGINIA", "WYOMING"}
Arrays that are initialized when they are declared must be dynamic (note the empty parentheses). The number of elements in the array are implied by the number of comma-separated values within the curly brackets.
Constant data can also be loaded into an array with individual assignment statements:
Dim astrStateName(49) As String
astrStateName(0) = "ALASKA"
astrStateName(1) = "ALABAMA"
.
.
.
astrStateName(48) = "WEST VIRGINIA"
astrStateName(49) = "WYOMING"
Data is also typically loaded into an array from a file or database table.
Example: Loading and Displaying an Array of Random Numbers
The following example uses a For/Next loop to load an array of 10 integers with random numbers between 1 and 100, then uses a second For/Next loop to display the results.
VB Code: |
Screen-shot of run: |
Module Module1
Public Sub Main() Dim aintRandomNum(9) As Integer Dim intX As Integer Randomize() ' load the "aintRandomNum" array with ' random numbers between 1 and 100 ... For intX = 0 To 9 aintRandomNum(intX) = Int(100 * Rnd() + 1) Next Console.WriteLine("Ten random numbers between 1 and 100:") ' display the contents of the "aintRandomNum" array ... For intX = 0 To 9 Console.WriteLine(CStr(aintRandomNum(intX))) Next Console.WriteLine("Press Enter to close this window.") Console.ReadLine() End Sub
End Module
|
|
Download the VB project code for the example above here.
Example: Loading a Two-Dimensional Array from a File and Displaying Its Contents
The next example reads in a file containing four weeks worth of sales data. Each record represents one week of data. There are seven amount fields in each record, representing sales amounts for Sunday through Saturday of each week. The format of the file is sequential, comma-delimited. Its contents is as follows:
1234,1765,3244,2453,2364,4567,3256
4646,1231,3466,2344,3446,3242,1231
1454,3466,1314,3464,2312,3466,6578
5453,4356,3453,3423,2355,2356,5534
The sample program reads this data into a two-dimensional (7 X 4) array, and then displays its contents on the console.
VB Code: |
Screen-shot of run: |
Module Module1
Sub Main()
' Declare a 2-dimensional array consisting of 4 rows and 7 columns. ' The first dimension (3) represents 4 weeks (indexed 0 to 3). ' The second dimension (6) represents 7 days in each week (indexed 0 to 6). Dim adecDailySales(3, 6) As Decimal
' Declare variables for file processing ... Dim strSalesFileName As String Dim intSalesFileNbr As Integer
' Declare variables to be used to access elements of the array ... Dim intX As Integer Dim intY As Integer
' Set up the file name ... strSalesFileName = My.Application.Info.DirectoryPath & "\SALES.DAT" ' Get an available file handle ... intSalesFileNbr = FreeFile()
' Open the sales file ... FileOpen(intSalesFileNbr, strSalesFileName, OpenMode.Input)
' Use a nested For/Next loop to load the sales data from the file ' (one field at a time) into the appropriate element of the array. ' The "outer" loop (using intX) executes four times, once for each ' record in the file ... For intX = 0 To 3 ' The "inner" loop (using intY) executes seven times for every one ' execution of the outer loop - once for each field (sales amount) ' in the record ... For intY = 0 To 6 ' Note that the Input function specifies only one item: ' adecDailySales(intX, intY). This will cause the current field ' of the current record to be stored in that element of the array. ' Thus, this nested loop will cause the "adecDailySales" array to ' be loaded in the following sequence: ' (0,0) (0,1) (0,2) (0,3) (0,4) (0,5) (0,6) ' (1,0) (1,1) (1,2) (1,3) (1,4) (1,5) (1,6) ' (2,0) (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) ' (3,0) (3,1) (3,2) (3,3) (3,4) (3,5) (3,6) Input(intSalesFileNbr, adecDailySales(intX, intY)) Next Next
' Close the sales file ... FileClose(intSalesFileNbr)
' Print headings on the console. Console.WriteLine("Week #".PadLeft(6) & "Sun".PadLeft(6) & "Mon".PadLeft(6) _ & "Tue".PadLeft(6) & "Wed".PadLeft(6) & "Thu".PadLeft(6) _ & "Fri".PadLeft(6) & "Sat".PadLeft(6)) Console.WriteLine("------".PadLeft(6) & "---".PadLeft(6) & "---".PadLeft(6) _ & "---".PadLeft(6) & "---".PadLeft(6) & "---".PadLeft(6) _ & "---".PadLeft(6) & "---".PadLeft(6))
' Use a nested For/Next loop to print the sales data from the array. ' The "outer" loop (using intX) executes four times, once for each ' "row" of the array ... For intX = 0 To 3 Console.Write(CStr(intX).Trim.PadLeft(6)) ' The "inner" loop (using intY) executes seven times for every one ' execution of the outer loop - once for each "column" (sales amount) ' in the current "row" ... For intY = 0 To 6 Console.Write(CStr(adecDailySales(intX, intY)).Trim.PadLeft(6)) Next Console.WriteLine() Next
Console.WriteLine() Console.WriteLine("Press Enter to close this window.") Console.ReadLine()
End Sub
End Module |
|
Download the VB project code for the example above here.
Examples: Using ReDim and ReDim Preserve
When you don't know in advance how many array elements you will need, such as when loading data from a file or from keyboard input, you can use dynamic arrays. You change the size of a dynamic array with the ReDim statement (with or without the Preserve option). The syntax is:
ReDim [Preserve] varname(subscripts) [As type]
The ReDim statement (without the Preserve option) resizes an array, but does not retain any values previously loaded into the array, which makes it not as useful as ReDim Preserve.
You could use ReDim by itself when you don't know how big the array should be at design-time, but you obtain the size prior to loading the array.
Following is an example which calculates the average temperature from a series of temperatures input by the user – but the user is prompted in advance for the number of temperatures to be entered.
VB Code: |
Screen-shot of run: |
Module Module1
Sub Main()
Dim aintTemperatures() As Integer Dim intNbrToEnter As Integer Dim intX As Integer Dim intTotTemp As Integer Dim intAvgTemp As Decimal
Console.Write("Number of temperatures to be entered: ") intNbrToEnter = Val(Console.ReadLine()) Console.WriteLine()
If intNbrToEnter = 0 Then Exit Sub
ReDim aintTemperatures(intNbrToEnter - 1)
For intX = 0 To (intNbrToEnter - 1) Console.Write("Temperature #" & (intX + 1) & ": ") aintTemperatures(intX) = Val(Console.ReadLine) intTotTemp += aintTemperatures(intX) Next
intAvgTemp = CInt(intTotTemp / intNbrToEnter) Console.WriteLine() Console.WriteLine("The average temperature entered was " & intAvgTemp & ".") Console.WriteLine() Console.WriteLine("Press Enter to close this window.") Console.ReadLine()
End Sub
End Module |
|
Download the VB project code for the example above here.
ReDim with the Preserve option is useful when you don't know in advance how many items will be input. When you use ReDim Preserve, you typically are dynamically changing (increasing) the upper bound of the array in a loop as you load the array. Following is an example of how the previous example could be modified to use ReDim Preserve. Note that within the input loop, the variable "intX" is incremented by 1. Then ReDim Preserve is used to resize the aintTemperatures array using the new value of intX.
VB Code: |
Screen-shot of run: |
Module Module1
Sub Main()
Dim aintTemperatures() As Integer Dim intCurrTemp As Integer Dim intX As Integer Dim intNumberOfEntries As Integer Dim intTotTemp As Integer Dim intAvgTemp As Integer
Console.Write("Enter a temperature (0 to quit): ") intCurrTemp = Val(Console.ReadLine())
intX = -1 intNumberOfEntries = 0
Do Until intCurrTemp = 0 intX += 1 ReDim Preserve aintTemperatures(intX) aintTemperatures(intX) = intCurrTemp intTotTemp += aintTemperatures(intX) intNumberOfEntries += 1 Console.Write("Enter a temperature (0 to quit): ") intCurrTemp = Val(Console.ReadLine()) Loop
Console.WriteLine()
If intNumberOfEntries = 0 Then Console.WriteLine("No temperatures were entered.") Else intAvgTemp = CInt(intTotTemp / intNumberOfEntries) Console.WriteLine("The average of the " & intNumberOfEntries _ & " temperatures entered was " & intAvgTemp & ".") End If
Console.WriteLine() Console.WriteLine("Press Enter to close this window.") Console.ReadLine()
End Sub
End Module |
|
Download the VB project code for the example above here.
Example: Loading a VB Structure Array from a File and Displaying Its Contents
It is often useful to work with the contents of a set of data (be it the contents of a small text file or the recordset results of a database query) in a VB Structure, where the Structure mirrors the layout of the fields of the dataset to be worked with. (NOTE: In pre-.NET versions of VB, structures were referred to as "user defined types" (or UDTs) and were referenced with the keyword "Type" rather than "Structure".)
In the next example, the contents of the "EMPLOYEE.DAT" file (comma-delimited version) that we have worked with in some of the previous topics will be loaded into a Structure array, and then its contents will be displayed on the console.
The code listing and resulting output for the sample program is shown below. Explanations of pertinent parts of the program will then follow.
VB Code: |
Screen-shot of run: |
Module Module1
Private Structure EmployeeRecord Dim EmpName As String Dim DeptNbr As Integer Dim JobTitle As String Dim HireDate As Date Dim HrlyRate As Single End Structure
Private maudtEmpRecord() As EmployeeRecord
Public Sub Main()
Dim strEmpFileName As String Dim intEmpFileNbr As Integer
Dim intArrX As Integer Dim intX As Integer
strEmpFileName = My.Application.Info.DirectoryPath & "\EMPLOYEE.DAT" intEmpFileNbr = FreeFile()
FileOpen(intEmpFileNbr, strEmpFileName, OpenMode.Input)
intArrX = -1
Do Until EOF(intEmpFileNbr) intArrX += 1 ReDim Preserve maudtEmpRecord(intArrX) With maudtEmpRecord(intArrX) Input(intEmpFileNbr, .EmpName) Input(intEmpFileNbr, .DeptNbr) Input(intEmpFileNbr, .JobTitle) Input(intEmpFileNbr, .HireDate) Input(intEmpFileNbr, .HrlyRate) End With Loop
FileClose(intEmpFileNbr)
For intX = 0 To UBound(maudtEmpRecord) With maudtEmpRecord(intX) Console.WriteLine(.EmpName.PadRight(20) _ & .DeptNbr.ToString.PadRight(6) _ & .JobTitle.PadRight(20) _ & Format(.HireDate, "MM/dd/yyyy").PadRight(12) _ & Format(.HrlyRate, "Standard")) End With Next
Console.WriteLine() Console.WriteLine("Press Enter to close this window.") Console.ReadLine()
End Sub
End Module |
|
In the General Declarations section (after the "Module" header, but before any Sub or Function is defined), the Structure "EmployeeRecord" is declared:
Private Structure EmployeeRecord
Dim EmpName As String
Dim DeptNbr As Integer
Dim JobTitle As String
Dim HireDate As Date
Dim HrlyRate As Single
End Structure
This is followed by the declaration of the variable "maudtEmpRecord" ("As" datatype "EmployeeRecord"). Note that "maudtEmpRecord" is declared with a pair of empty parentheses after its name, indicating that this will be a dynamic array.
Private maudtEmpRecord() As EmployeeRecord
In the input loop that loads the records from the file into the structure array, note that the technique used in the previous example to increase the upper bound of the dynamic array is used here as well. The variable "intArrX" is incremented by 1, then ReDim Preserve is used to re-size the maudtEmpRecord array using the new value of intArrX.
intArrX += 1
ReDim Preserve maudtEmpRecord(intArrX)
This is followed by the code to load the fields of the current input record into the corresponding items of the current element of the maudtEmpRecord array. Note that a With/End With block is used to "factor out" the reference to "maudtEmpRecord(intArrX)".
With maudtEmpRecord(intArrX)
Input(intEmpFileNbr, .EmpName)
Input(intEmpFileNbr, .DeptNbr)
Input(intEmpFileNbr, .JobTitle)
Input(intEmpFileNbr, .HireDate)
Input(intEmpFileNbr, .HrlyRate)
End With
If With/End With was not used, the set of statements above would have to be written as follows:
Input(intEmpFileNbr, maudtEmpRecord(intArrX).EmpName)
Input(intEmpFileNbr, maudtEmpRecord(intArrX).DeptNbr)
Input(intEmpFileNbr, maudtEmpRecord(intArrX).JobTitle)
Input(intEmpFileNbr, maudtEmpRecord(intArrX).HireDate)
Input(intEmpFileNbr, maudtEmpRecord(intArrX).HrlyRate)
The last portion of the program loops through the structure array and displays its contents on the console. The "For" statement introduces the UBound function, which determines upper bounds of an array. Again, a With/End With block is used to "factor out" the reference to "maudtEmpRecord(intArrX)".
For intX = 0 To UBound(maudtEmpRecord)
With maudtEmpRecord(intX)
Console.WriteLine(.EmpName.PadRight(20) _
& .DeptNbr.ToString.PadRight(6) _
& .JobTitle.PadRight(20) _
& Format(.HireDate, "MM/dd/yyyy").PadRight(12) _
& Format(.HrlyRate, "Standard"))
End With
Next
Download the VB project code for the example above here.
More on ReDim and ReDim Preserve
The ReDim statement is used to size or resize a dynamic array that has already been formally declared using a Private, Public, or Dim statement with empty parentheses (without dimension subscripts). You can use the ReDim statement repeatedly to change the number of elements and dimensions in an array. ReDim without Preserve always clears the previous contents of the array.
If you use the Preserve keyword, you can resize only the last array dimension and you can't change the number of dimensions at all. For example, if your array has only one dimension, you can resize that dimension because it is the last and only dimension. However, if your array has two or more dimensions, you can change the size of only the last dimension and still preserve the contents of the array. The following example shows how you can increase the size of the last dimension of a dynamic array without erasing any existing data contained in the array. If you make an array smaller than it was, data in the eliminated elements will be lost.
ReDim SomeArray(10, 10, 10)
. . .
ReDim Preserve SomeArray (10, 10, 15)
The Erase Statement
The Erase statement is used to clear the contents of a dynamic array. The syntax is:
Erase arrayname
Example:
Erase aintTemperatures
The LBound and UBound Functions
You can use the LBound and UBound functions to determine the lower- and upper- bounds, respectively, of an array. The syntax is:
LBound(arrayname[()][, dimension])
UBound(arrayname[()][, dimension])
Note that the array name may be specified with or without a set of empty parentheses. The second parameter, which is optional, specifies which dimension of the array you want the bound of, which can be useful if you have a multi-dimensional array. If dimension is omitted, 1 is assumed.
For example, if you have an array declared as:
Dim aintTemperatures(6) As Integer
Then the statement
Console.WriteLine(LBound(aintTemperatures))
would display 0
And the statement
Console.WriteLine(UBound(aintTemperatures))
would display 6.
The LBound and UBound functions are useful when you need to loop through a dynamic array after it has been loaded. For example, say you loaded the dynamic array in an initial Sub when the program began, and then later, in some other Sub, you need to process its values. If you used local variables to set the bounds of the array with ReDim Preserve in the initial Sub, you won't know how many elements the array contains when you need to process the array in the other Sub (because the values of the local variables would not be retained once you leave the initial Sub). LBound and UBound can help in that situation:
For intX = LBound(maintTemperatures) To UBound(maintTemperatures)
' process maintTemperatures(intX)
Next
Note: Since the lower bound of .NET arrays is always zero, you can simply use "0" instead of the LBound function. The above code could be rewritten as:
For intX = 0 To UBound(maintTemperatures)
' process maintTemperatures(intX)
Next
The UBound function is also needed when you must process an array that has been passed to a Sub or Function, because the bounds of the array cannot be specified in the Sub or Function header's argument list. The reason that the bounds of an array cannot be specified in a Sub or Function header's argument list is that the same Sub or Function can be used to process arrays of different sizes. Therefore, you should use the UBound function in the Sub or Function to determine the bounds of the array that has been passed.
Example: Handling an Empty Array
If a dynamic array has no elements (i.e., a ReDim or ReDim Preserve statement was never executed on it), when you subsequently attempt to reference an element of that array or if you attempt to use UBound on that array, a run-time error will occur. As a test, I took a copy of the previous project that loads the employee file and cleared the records of the input file (thus resulting in an empty, zero-length file for input). Upon running the program, the run-time error occurred:
The reason for the above error is that the statement within the input loop that starts with:
Do Until EOF(intEmpFileNbr)
would never be executed, because EOF (end-of-file) will be triggered as soon as the file is opened. This means that the maudtEmpRecord array will never be loaded. When the program tries to reference it with this statement:
For intX = 0 To UBound(maudtEmpRecord)
the run-time error will occur.
You can test for an empty array by using the keywords Is Nothing. Sample syntax is as follows:
If SomeArray Is Nothing Then
' maybe display a message, or just don't do anything
Else
' process the array
End If
You can also test for an empty array by using the keywords IsNot Nothing. Sample syntax is as follows:
If SomeArray IsNot Nothing Then
' process the array
End If
The sample program below is a variation on the previous employee display program, with the additional check for an empty array. This version uses an empty employee file for input.
VB Code: |
Screen-shot of run: |
Module Module1
Private Structure EmployeeRecord Dim EmpName As String Dim DeptNbr As Integer Dim JobTitle As String Dim HireDate As Date Dim HrlyRate As Single End Structure
Private maudtEmpRecord() As EmployeeRecord
Public Sub Main()
Dim strEmpFileName As String Dim intEmpFileNbr As Integer
Dim intArrX As Integer Dim intX As Integer
strEmpFileName = My.Application.Info.DirectoryPath & "\EMPLOYEE.DAT" intEmpFileNbr = FreeFile()
FileOpen(intEmpFileNbr, strEmpFileName, OpenMode.Input)
intArrX = -1
Do Until EOF(intEmpFileNbr) intArrX += 1 ReDim Preserve maudtEmpRecord(intArrX) With maudtEmpRecord(intArrX) Input(intEmpFileNbr, .EmpName) Input(intEmpFileNbr, .DeptNbr) Input(intEmpFileNbr, .JobTitle) Input(intEmpFileNbr, .HireDate) Input(intEmpFileNbr, .HrlyRate) End With Loop
FileClose(intEmpFileNbr)
If maudtEmpRecord Is Nothing Then Console.WriteLine("File is empty.") Else For intX = 0 To UBound(maudtEmpRecord) With maudtEmpRecord(intX) Console.WriteLine(.EmpName.PadRight(20) _ & .DeptNbr.ToString.PadRight(6) _ & .JobTitle.PadRight(20) _ & Format(.HireDate, "MM/dd/yyyy").PadRight(12) _ & Format(.HrlyRate, "Standard")) End With Next End If
Console.WriteLine() Console.WriteLine("Press Enter to close this window.") Console.ReadLine()
End Sub
End Module
|
|
Download the VB project code for the example above here.