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.