Multidimensional Arrays

   


Just as a picture has two dimensions and the room you sit inside is three dimensional, a line only has one dimension and a dot zero dimensions. The five values represented by the array variable accountBalances of Listing 10.1 in Chapter 10, "Arrays Part I: Array Essentials," can be viewed as being positioned on a line one after the other, and each value can be accessed by specifying exactly one index. For those reasons, accountBalances, along with the remaining arrays used in the previous chapter, are called one-dimensional arrays.

C# allows you to specify arrays of two dimensions, requiring two indexes to identify an array element, and arrays of three dimensions calling for three indexes. In fact, C# lets you specify arrays with any number of dimensions you might require.

Dimensions

graphics/bulb.gif

The number of dimensions indicates the number of extensions in a given direction. It is possible for us to visualize the implications of zero, one, two, and three dimensions, as shown in Figure 11.1.


Figure 11.1. Visualizing zero, one, two, and three dimensions.
graphics/11fig01.gif

Even though it is difficult (near impossible) to visualize more than three dimensions, it is possible and sometimes useful to include arrays with four or more dimensions in a program. To avoid headaches and sleepless nights wondering about the warped and weird worlds of many dimensions, all you need to know here is that the number of indexes needed to reference an array element is equal to the number of dimensions of the array.

Note

graphics/common.gif

An array is n-dimensional if it has n indexes.


Consequently, you could regard phone numbers of seven digits, such as 8327881, to be seven dimensional in that the seven indexes give you access to exactly one telephone that can be viewed as an array element. In a C# program that simulates phones and phone calls, we could even declare an array variable called phone and access a specific phone with the command phone[8, 3, 2, 7, 8, 8, 1].

In this section, you will see how we declare and work with multidimensional arrays.

Two-Dimensional Arrays

Rather than looking at seven dimensional arrays, let's start with an example that utilizes an array of two dimensions.

Recall our elevator simulation program from Chapter 5, "Your First Object-Oriented C# Program." A floor request was defined as a "person" requesting to be taken to a particular "floor" by "pressing" a "button" in the "elevator." Suppose we intend to track the number of floor requests given to an elevator over a seven-day period on an hourly basis. If done manually by simply sitting inside a real elevator, a person could write these statistics down in a table similar to that shown in Table 11.1. In this particular case, she has already recorded the first two hours of the first day; the elevator here received 89 and 65 requests, respectively.

To specify any particular hour, we need to provide two indexes the day and the hour. For that reason, the table is two-dimensional. In this case (day:1, hour:0) is equal to 89.

Table 11.1. Manually Recording Floor Requests Hourly for Seven Days
  Hour 0 Hour 1 Hour 2 Hour 3 Hour 23
Day 1 89 65      
Day 2          
Day 7            

Note: Hour 0 is the hour from midnight 00:00 until one o'clock 01:00.

Declaring and Defining Two-Dimensional Arrays

How can we effectively implement the equivalent of a floor request tracking system in a C# program? For starters, we need a data structure to contain the data shown in Table 11.1. We could attempt to use a one-dimensional array with 168 (7 x 24) entries, but it would be a cumbersome task to determine which statistic goes with which index, not to mention the difficulty of interpreting this array. For example, who would know that index [79] is equivalent to day 4/hour 7? If, instead, we apply a two-dimensional array, we can allow one index for the row and one index for the column, letting us express day 4, hour 7 as [3,7]. (Note that day 4 becomes index 3 due to the zero-based index, whereas hour 7 remains index 7 because the first hour is hour 0, as shown in Table 11.1)

Note

graphics/common.gif

Just as the first entry of a one-dimensional array has index zero, multidimensional arrays also begin their indexes with zero. Consequently, the first element in a two-dimensional array is at [0,0].


If we call our two-dimensional array requests with the base type ushort, we can declare and define it as shown in Figure 11.2.

Figure 11.2. Declaring and defining the two-dimensional requests array.
graphics/11fig02.gif

This is very similar to declaring a one-dimensional array. However, one comma is needed inside the otherwise empty pair of square brackets of the declaration, and two numbers (7 and 24 in this case) separated by a comma are needed to specify the lengths of the two dimensions when creating a new array with the new keyword.

The array variable declaration, the creation of the new array object, and the assignment of the array object's reference to the array variable (shown as the first source code line in Figure 11.2) can (as with a one-dimensional array) be broken up into two statements. The result of this break up is shown in the lower part of Figure 11.2.

Our new array can be depicted as shown in Figure 11.3.

Figure 11.3. The two dimensional array: requests.
graphics/11fig03.gif

The first dimension, specifying a particular day, has index values ranging from 0 to 6, and the second dimension, indicating the hour, ranges from 0 to 23. To access the fourth hour of the third day, you need to write the following:

 requests[2,3] 

where 2 signifies the third day, and 3 signifies the fourth hour.

Note

graphics/common.gif

By convention, the first index of a two-dimensional array denotes the row of its corresponding table and the second index corresponds to the column.


Accessing the Elements of a Two-Dimensional Array

After declaring the array variable and assigning it a two-dimensional array object reference, we are ready to access the individual array elements.

Apart from the extra indexes needed to identify an array element of a two-dimensional array, an array element of a two-dimensional array is applied in the same fashion and acts in the same way as that of a one-dimensional array. Consequently, it can be used in the same context as a single variable with the same type as its base type. For example, we could assign the value 89 to the first hour of the first day by writing the following:

 requests[0,0] = (ushort) 89; 

Notice the cast (ushort) applied in this line. In our case, the base type of requests is ushort. Because 89 is of type int, and because there is no implicit conversion path specified from int to ushort, we need to apply the cast operator (ushort).

We can also involve the array elements in calculations. The following line assigns the total number of requests for the first three hours of the fourth day to a variable called subTotal:

 subTotal = (ushort) (requests[3,0] + requests[3,1] + requests[3,2]); 

As you will see in Listing 11.3, a two nested loop construct is primordial for traversing the elements of a two dimensional array. For that reason, I will spend a bit of time on the inner logic of this symbiotic relationship.

Recall that a one-dimensional array can be traversed with just one for loop, as shown in Listing 11.1.

Listing 11.1 A Single for Loop Traversing a One-Dimensional Array
... int [] myArray = new int [24]; for (int j = 0; j < 24, j++) {       ...       ... myArray[j] ...       ... } ... 

Note that the source code of Listing 11.1 does not compile.

The effect of this loop body is illustrated in Figure 11.4, which shows flow of control in the for loop and how this relates to which array element is being visited. During the first loop, myArray[0] is accessed because j is zero, the second execution of the loop body accesses myArray[1], and this cycle continues until the loop body has been repeated 24 times. When j is 24, the for loop is terminated because the loop condition j < 24 is false. Notice that if myArray in this example represented the hours of one single day, we would visit every single hour through the for loop shown in Listing 11.1.

Figure 11.4. Single for loop traversing a one dimensional array.
graphics/11fig04.gif

The array variable requests not only represent the hours of one day but seven days. Thus, by repeating the process of Figure 11.4 seven times, we can visit all hours of not just one day but seven days. We can construct this functionality by inserting the previous for loop into another for loop that repeats itself seven times. This has been done in the source code extract found in Listing 11.2.

Listing 11.2 Two Nested for Loops Traversing a Two-Dimensional Array
... ushort [ , ] requests = new ushort [7,24]; ... for (int i = 0; i < 7; i++) {     ...     for (int j = 0; j < 24 ; j++)     {         ...             //this block is repeated seven times         ... requests[i, j] ...         ...     } ... } ... 

Note that the source code of Listing 11.2 does not compile.

The flow of control when executing the nested for loop construct of Listing 11.2 is illustrated in Figure 11.5, and as in Figure 11.4, it shows which array element is visited during which loop. Execution starts in the upper-left corner of the figure where the execution of the loop body of the outer loop is commenced. Because this outer loop body consists of the for loop presented in Listing 11.2, its execution is similar to that of Figure 11.4. The only difference being that an index ( i ) indicating the day being processed is added. The first execution of the outer loop body is more or less a repeat of Figure 11.4. In fact, all seven executions of the outer loop body have this appearance. By the time i is 24 and j is 7, all array elements have been visited once and the nested loop is terminated.

Figure 11.5. Nested for loops to traverse an entire two-dimensional array.
graphics/11fig05.gif

Listing 11.3 utilizes the requests array to hold the number of requests given to an elevator during each hour over a seven-day period. For simplicity, the program generates the number of requests per hour randomly by applying the System.Random class you have met previously (see lines 8, 18, and 20). The elevator is assumed to be the busiest between 8 in the morning and 6 in the evening when the number of requests will be between 20 and 99 (see line 20). If outside these hours, the number of requests will be between 1 and 10 (see line 18).

The numbers of requests are stored in requests (see line 7) and printed in a table with 7 rows and 24 columns, as shown in the sample output after Listing 11.3. requests is then used to calculate the total requests per day and the average requests per hour. Both statistics are also displayed in the sample output.

Listing 11.3 ElevatorRequestTracker.cs
01: using System; 02: 03: class ElevatorRequestTracker 04: { 05:     public static void Main() 06:     { 07:         ushort[,] requests = new ushort [7,24]; 08:         Random randomizer = new Random(); 09:         int sum; 10: 11:          // Randomly generate number of requests received for every 12:          // hour of the day and every day of the week. 13:         for (int i = 0; i < 7; i++) 14:         { 15:             for (int j = 0; j < 24 ; j++) 16:             { 17:                 if ((j < 8) || (j > 18)) 18:                     requests[i,j] = (ushort)randomizer.Next(1,10); 19:                 else 20:                     requests[i,j] = (ushort)randomizer.Next(20,99); 21:             } 22:         } 23:  24:          //Print out table showing requests of all hours of every day 25:         Console.WriteLine("                                  Hour\n"); 26:         Console.Write("    "); 27:         for (int i = 0; i < 24; i++) 28:         { 29:             Console.Write("{0,2}  ",i); 30:         } 31: 32:         Console.Write("\nDay"); 33:         for (int i = 0; i < 7; i++) 34:         { 35:             Console.Write("\n{0}    ", (i + 1)); 36:             for (int j = 0; j < 24; j++) 37:             { 38:                 Console.Write("{0,2}  ", requests[i,j]); 39:             } 40:         } 41: 42:          // Calculate and print total number of requests on a daily basis 43:         Console.WriteLine("\n\nTotal number of request per day:\n"); 44:         for (int i = 0; i < 7; i++) 45:         { 46:             sum = 0; 47:             for (int j = 0; j < 24; j++) 48:                 sum += requests[i,j]; 49:             Console.WriteLine("Day {0} : {1} ", (i + 1), sum); 50:         } 51: 52:          // Calculate and print average requests on an hourly basis 53:         Console.Write("\nAverage requests per hour:\n\nHour:"); 54:         for (int i = 0; i < 24; i++) 55:         { 56:             Console.Write("{0,2}  ",i); 57:         } 58:         Console.Write("\nAver:"); 59:         for (int j = 0; j < 24; j++) 60:         { 61:             sum = 0; 62:             for (int i = 0; i < 7; i++) 63:                 sum += requests[i,j]; 64:             Console.Write("{0,2}  ", (sum / 7)); 65:         } 66:     } 67: } 

Hour

      0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Day 1    7  8  3  6  8  2  2  5 92 67 37 38 87 53 96 29 96 96 35  7  3  2  8  1 2    7  7  2  1  5  6  4  9 34 58 39 88 28 76 58 88 80 68 40  2  7  7  2  6 3    6  4  5  5  6  3  8  5 98 38 86 31 22 36 20 40 37 44 23  4  2  1  1  3 4    5  3  9  7  9  6  4  1 59 61 49 29 34 90 43 93 69 76 88  8  8  4  8  6 5    6  1  9  4  7  8  5  5 50 37 20 42 39 33 67 49 88 84 71  5  8  8  1  3 6    1  2  7  5  3  2  4  2 58 54 26 93 74 20 44 73 24 86 78  9  8  1  7  5 7    2  5  1  1  7  4  7  7 92 68 47 66 26 20 43 38 86 87 83  8  1  7  7  8 Total number of request per day: Day 1: 788 Day 2: 722 Day 3: 528 Day 4: 769 Day 5: 650 Day 6: 686 Day 7: 721 Average requests per hour: Hour: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Aver: 4  4  5  4  6  4  4  4 69 54 43 55 44 46 53 58 68 77 59 6  5  4  4  4 

Note:

  • Because the numbers of requests are generated randomly, they will vary between different test runs of the program.

Line 7 declares the two-dimensional array variable requests as shown earlier. Line 8 declares randomizer to hold a reference to an object of type Random (or System.Random) and assigns it a reference to a new object of type Random. randomizer is now ready to generate random numbers. Lines 13 22 contain two nested for loops. The outer for loop lets i move from 0 to 6 by increments of one (see line 13); the inner for loop dictates j to move from 0 to 23 (see line 15), also by increments of one. As a result, the nested for loop construct is able to traverse our two-dimensional array and access every single element when i and j are used as indexes to specify an element in requests as in requests[i, j]. Lines 18 and 20 do exactly this by positioning requests[i,j] in the loop body of the innermost for loop and assigns a random number to every array element of requests. Notice that an if statement (beginning in line 17) is applied to determine the size of the random number assigned to each element. If j is outside the hours between 8 and 18 (if ((j < 8) || (j > 18)), assign a random number between 1 and 10 (line 18). If j is inside the hours between 8 and 18, assign a random number between 20 and 99 to the requests element (line 20). Recall that the argument of the Next method of randomizer takes two arguments the former sets the lower limit for the random value returned, and the latter sets the upper limit.

Lines 24 40 are concerned with printing the full table of request numbers contained in requests. Lines 25 30 simply print the headings for the hours. Lines 32 40 once again uses a nested for loop; this time to print the full contents of requests.

Lines 44 50 calculate the total number of requests given for each day. For every day processed, we need to set the sum variable to zero (line 46), add together all requests for the 24 hours of that day (lines 47 and 48), and print this result to the console (line 49).

Lines 53 65 calculate and print the average number of requests for each hour during the seven days. Instead of calculating the sum of the entries in an entire row of the requests table, as in lines 44 50, we need to sum the entries in an entire column and then divide this result by seven to find the average (line 64). The inner for loop of lines 44 50 becomes the outer for loop, and the outer for loop becomes the inner for loop, as shown in lines 59 65.

Consider accessing an element of the array requests with the following command (as in Listing 11.3):

 requests[i, j] 

If i is held fixed and j is varying (for example, during a loop), programmers often say that a row of the array is being accessed. Conversely, if j is fixed and i varied, we say that a column is being accessed.

Note

graphics/common.gif

In general, to traverse an entire n-dimensional array, use n nested loops and access the array elements from the loop body of the innermost loop.


Viewing the Two-Dimensional Array As an Array of Arrays

It is possible to think of the two dimensional array requests as an array of arrays. If we focus for a moment on the first dimension of requests (representing days), we can view the seven days as a one-dimensional array (see Figure 11.6). If we then include the hours, each "day" element can be regarded as consisting of a one dimensional "hour" array.

Figure 11.6. A two dimensional array can be viewed as an array of arrays.
graphics/11fig06.gif

Notice that every "hour" array in requests is of the same length (24); in other words, every row as viewed in Figure 11.3 is of the same length. Consequently, requests can be called a rectangular array.

When the rows of a two-dimensional array are all of the same length, the array is said to be rectangular.

This term can also be used to describe an array of multiple dimensions. An array for which it is true that any dimension can be viewed to contain one dimensional arrays all with the same length is said to be a rectangular array.

Case Study: The "Find the Tropical Island" Game

For a moment, let's put the banks, account balances, and elevators to rest. Instead, the game presented here lets you "travel" to an exotic tropical island…if you can find it. You are flying an airplane and are on your way to this paradise when the navigation system suddenly breaks down and looses part of its ability to communicate with you (due to a software bug). The only way to navigate now is through trial (qualified guessing) and error, accompanied by feedback from the navigation system, which now only provides a hint of the island's location relative to that of each guess. A guess consists of two numbers (a row and a column) specifying a point on a map. When the guess is entered into the navigation system, it will tell you whether the island is

  • To the East or the West of the point (guess)

  • To the North or the South of the point (guess)

  • If the row of the point (guess) is the same as that of the island's location (on the same horizontal line)

  • If the column of the point (guess) is the same as that of the island's (on the same vertical line)

The trial and error will continue until you enter the coordinates that match those of the island's location.

Note

graphics/common.gif

True story: A specific type of fighter plane was found to flip upside down every time it crossed the equator during its first test flights, providing quite a shock to its pilot. The software bug causing this problem was fortunately detected and rectified soon after.


Software specification:

We want to write a program to simulate the situation just described. The user is initially shown a map on the console of the area where the island is located, but, due to the faulty navigation system, only water (in the form of the symbol ~) can be seen here initially. The user determines the size of the map at the start of the game by entering the number of rows and columns. Each point on the map can be identified with a row index and a column index. Figure 11.7 indicates the location of point [1,4] in a map of size 5 rows x 10 columns.

Figure 11.7. The map of the faulty navigation system.
graphics/11fig07.gif

The location of the island is chosen randomly and secretly by the program. The island is thus hiding behind one of the "waves" (~) shown in Figure 11.7.

The user is now ready to enter guesses. Each guess must consist of a location with a row and a column number. The feedback accompanying each guess is to be provided in the following way.

Every second guess is evaluated in terms of whether the island is located to the north or the south of the provided guess. All other guesses are evaluated in terms of east/west.

After each guess, a new map is printed on the console with a new letter printed in the location of the guess. An N on the map indicates that the island is to the north of the guess, an S to the south, an E to the east, and a W to the west. If the guess happens to have the same row as the island during a north/south evaluation, an R (for Row) is printed on the map. Similarly, if the guess during an east/west evaluation is found to have the same column as the island, a C is printed on this location. When the pilot (user) through luck (and hopefully clever logical deductions) finally enters a location matching that of the island, an I is printed in this position and the pilot is congratulated along with a message telling her how many (or few) guesses she used in total. The game is then terminated and the user is asked if she cares to play yet another. To see an example of this user interface, please have a look at the sample output after Listing 11.4, which contains the complete Find the Island program. In this sample, the island is located at position [2,4], indicated by an I shown on the last map of the sample output.

The code references in the following section can be found in Listing 11.4 displayed later.

Software design:

Identifying the classes of the program

Two classes can be identified NavigationSystem (lines 51 135) and FindTheIslandGame (lines 3 49). The latter is a container for the Main() method and controls the main flow of the game.

Identifying the instance variables of the classes:

  • The NavigationSystem class This class utilizes a two-dimensional map. It further holds the secret location of the island and also keeps track of the number of guesses entered by the user. Thus, we need to declare the following four instance variables (see lines 53 56):

     char [,] map; byte secretRow = 0; byte secretColumn = 0; int guessCounter = 0; 
  • The FindTheIslandGame class All variables are kept within the Main() method (lines 5 48), so there are no instance variables in this class.

Identify the methods in each class:

NavigatorSystem class:

A constructor (lines 58 72) is imperative for the NavigatorSystem class to perform the following tasks:

  • Create a new map (two-dimensional array object declared in line 53) with the number of rows and columns specified by the user and assign its reference to the map instance variable (line 60).

  • Randomly generate the secret location of the island and store the coordinates in two variables (here called secretRow and secretColumn) for later guess evaluations (lines 63 and 64).

  • Fill the map with "waves" (~) because this is the first view the user gets of the map (lines 66 70).

  • Set the guessCounter to zero (line 71).

Printing out the map with the same style as that shown in the following sample output requires quite a few lines of code due to headings, alignments, and a nested for loop. As a result, this procedure deserves its own method, called PrintMap() (lines 74 94). Notice that this method is called from outside the NavigatorSystem object (line 34), as well as from within (line 127).

The Main() method must be able to present the guesses of the user to the NavigatorSystem for evaluation and feedback. A method called EvaluateGuess (lines 96 129) exposes this functionality. It is equipped with two formal parameters to receive and hold the guess (line 96) during the execution of EvaluateGuess. The method will provide most of the feedback by printing the new map onscreen (line 127), but the caller (located in line 41) needs to know whether the guess is correct or not to direct its flow accordingly. Consequently, EvaluateGuess must return a bool value reflecting whether the guess is correct or not.

The number of guesses should be updated automatically as guesses are received by the EvaluateGuess method (see line 100). However, we need an accessor method to retrieve this value. In this case, it is called GetNumberOfGuesses (lines 131 134).

FindTheIslandGame class

Only the Main() method is needed in the FindTheIslandGame class. It controls the overall flow of the program. It starts a new game, lets the user enter guesses until the island is found, and will repeat these two program segments until the user quits the program.

Internal method design:

1) Main() of the FindTheIslandGame class:

The user must be able to play as many games as he or she wishes without having to start the program over and over again. This is a repeated action and calls for a loop construct, and, because the user is always expected to play at least one game, the preferred iteration statement is the do-while loop. The loop body of this construct stretches from lines 15 47 and is repeated until the answer provided by the user in line 46 is different from "y" (or "Y") (line 47), in which case, the Main() method and thereby the program is terminated.

Every individual game is also of a repetitive nature the user provides guess…the program responds…the user provides guess…the program responds…and so on, until the user provides a correct guess. Because the user will always provide at least one guess, we again apply the do-while loop spanning from lines 35 41. The only task of the loop body is to fetch the guess from the user. It is then presented to navigator (the NavigatorSystem object declared in line 7), which takes care of the evaluation and feedback required. Observe that only when the guess (consisting of rowGuess and columnGuess) is correct will

 navigator.EvaluateGuess(rowGuess, columnGuess) 

of line 41 be true; otherwise, it is false. The reason why EvaluateGuess manages to return true only if rowGuess and columnGuess form a correct guess is explained in Figure 11.8. The arguments rowGuess and columnGuess are passed along when EvaluateGuess is called in line 41, and are assigned to their corresponding formal parameters with the same names (line 96). Only if both rowGuess is equal to secretRow and columnGuess is equal to secretColumn in line 104 will the otherwise false islandFound variable (see line 98) be set to true in line 107 before its value is returned in line 128.

Figure 11.8. When the guess is correct EvaluateGuess returns the value true.
graphics/11fig08.gif

We want the do-while loop to repeat itself until the guess is correct. Because a do-while loop repeats its loop body until the loop condition (see line 41) is false, we must negate the Boolean value returned by the EvaluateGuess method by using the not operator (symbolized by !) as in the following:

 !navigator.EvaluateGuess(rowGuess, columnGuess) 

We thereby terminate the stream of guesses requested, received, and processed when the guess is correct.

2) The constructor of the NavigatorSystem class (lines 58 72):

An important feature of the program is to let the user choose the number of rows and columns of the map at the beginning of every game. All parts of the program relying on the size of the map must adjust to these changing requests from the user. One effective and relatively simple way of doing this is to first create the map with its specified measurements (see lines 58 and 60) and then use System.Array's built-in method GetLenth(<dimension>) whenever the total number of rows or columns is needed in the program.

Note

graphics/common.gif

The GetLength(<dimension>) is another of System.Array's handy built-in methods. It provides the length of the specified dimension of a given multidimensional array. For example, if we declare map as follows:

 char [ , ] map = new char [5,10]; 

then

 map.GetLength(0) 

will return the length of the first dimension, in this case, 5, and

 map.GetLength(1) 

returns the length of the second dimension, which is 10.


This strategy is not only used in lines 63, 64, 66, and 68 of the constructor, but also in other parts of the program (lines 79, 87, and 90).

In lines 63 and 64, the secret row and column numbers cannot be any greater than the number of rows and columns respectively in the map. To provide the correct restrictions to the random number generator randomizer, the upper limits are then set to map.GetLength(0) 1 and map.GetLength(1) 1, respectively.

In many regards, a constructor is similar to a normal method. For example, it might contain formal parameters in its header, as shown in line 58. So whenever a new NavigationSystem object is created, as in line 22 (see Figure 11.9), the arguments provided in the parentheses after the classname must match the formal parameters of the constructor (line 58). The values of these arguments will then be transferred to the formal parameters, which then can be applied during the execution of the constructor body.

Figure 11.9. Formal parameters of a constructor.
graphics/11fig09.gif

3) Evaluating a guess in the EvaluateGuess method (lines 96 129):

If the guess is correct (making the Boolean expression of line 104 true), there is no need to conduct any further analyses; we only need to assign the letter I to the location (line 106), print out the map (line 127), and return the value true (lines 107 and 128).

On the other hand, if the guess is incorrect, EvaluateGuess needs to relate guess to the secret position of the island. For every second guess, the guess must be evaluated in terms of north/south direction, all other guesses in terms of east/west. The north/south analysis is performed in lines 111 116 if guessCounter is found to be even in line 109, and the east/west analysis in lines 120 125 is performed if guessCounter is odd.

Figure 11.10 illustrates the directions used in the game. An island has been positioned arbitrarily in a map at location [2,4]. Thirteen locations and their corresponding letters are shown along with their meaning. A corresponding true Boolean expression is further shown for each letter. For example, according to the first row of the table, an N is equivalent to (rowGuess > secretRow) being true.

Figure 11.10. Navigating the map.
graphics/11fig10.gif

We now can implement this logic in our EvaluateGuess method that spans lines 104 126.

Listing 11.4 FindTheIslandGame.cs
01: using System; 02: 03: class FindTheIslandGame 04: { 05:     public static void Main() 06:     { 07:         NavigationSystem navigator; 08:         int numberOfRows = 0; 09:         int numberOfColumns = 0; 10:         byte rowGuess = 0; 11:         byte columnGuess = 0; 12:         string answer; 13: 14:         do 15:         { 16:              //Let user determine the size of the map 17:             Console.WriteLine("First Choose size of map: "); 18:             Console.Write("Enter number of rows: "); 19:             numberOfRows = Convert.ToInt32(Console.ReadLine()); 20:             Console.Write("Enter number of columns: "); 21:             numberOfColumns = Convert.ToInt32(Console.ReadLine()); 22:             navigator = new NavigationSystem(numberOfRows, numberOfColumns); 23: 24:             Console.WriteLine("\nBelow is a map of the ocean:\n" + 25:                 "There is a hidden island behind one of the waves\n" + 26:                 "Can you find it?\n" + 27:                 "Make a (qualified) guess by entering a row and a column\n" + 28:                 "Every second guess is evaluated in East West direction\n" + 29:                 "The remaining guesses in North South direction\n" + 30:                 "An E in the map means the Island is to the East of this point\n" + 31:                 "W means to the west; N to the North and S to the South\n" + 32:                 "R means correct row but wrong column\n" + 33:                 "C means correct column but wrong row\n"); 34:             navigator.PrintMap(); 35:             do 36:             { 37:                    Console.Write("\nEnter row: "); 38:                 rowGuess = Convert.ToByte(Console.ReadLine()); 39:                 Console.Write("Enter column: "); 40:                 columnGuess = Convert.ToByte(Console.ReadLine()); 41:             } while (!navigator.EvaluateGuess(rowGuess, columnGuess)); 42:             Console.WriteLine("\nWell done! The letter I marks the island"); 43:             Console.WriteLine("You found the island in {0}  guesses", 44:             navigator.GetNumberOfGuesses()); 45:             Console.WriteLine("\nCare to play another game? Y)es N)o"); 46:             answer = Console.ReadLine().ToUpper(); 47:         } while (answer == "Y"); 48:     } 49: } 50:  51: class NavigationSystem 52: { 53:     char [,] map; 54:     byte secretRow = 0; 55:     byte secretColumn = 0; 56:     int guessCounter = 0; 57: 58:     public NavigationSystem(int rows, int columns) 59:     { 60:         map = new char [rows, columns]; 61:          //Randomly choose a secret location for the island. 62:         Random randomizer = new Random(); 63:         secretRow = (byte)randomizer.Next(0, map.GetLength(0)-1); 64:         secretColumn = (byte)randomizer.Next(0, map.GetLength(1)-1); 65:          // Fill the map with ~ to symbolize waves 66:         for (int i = 0; i < map.GetLength(0); i++) 67:         { 68:             for(int j = 0; j < map.GetLength(1); j++) 69:             map[i,j] = '~'; 70:         } 71:         guessCounter = 0; 72:     } 73: 74:     public void PrintMap() 75:     { 76:         byte count = 0; 77: 78:         Console.Write("  "); 79:         for (int i = 0; i < map.GetLength(1); i++) 80:         { 81:             if (count == 10) 82:                 count = 0; 83:             Console.Write(count); 84:             count++; 85:         } 86:         Console.WriteLine(); 87:         for (int j = 0; j < map.GetLength(0); j++) 88:         { 89:             Console.Write(j + ((j < 10) ? " " : "")); 90:             for(int i = 0; i < map.GetLength(1); i++) 91:             Console.Write(map[j,i]); 92:             Console.WriteLine(); 93:         } 94:     } 95: 96:     public bool EvaluateGuess (int rowGuess, int columnGuess) 97:     { 98:         bool islandFound = false; 99: 100:        guessCounter++; 101:         //If location is not found and guessCounter is even 102:         //then check guess for North South direction 103:         //otherwise check for East West direction 104:        if ((rowGuess == secretRow) && (columnGuess == secretColumn)) 105:        { 106:            map[rowGuess, columnGuess] = 'I'; 107:            islandFound = true; 108:        } 109:        else if (guessCounter % 2 == 0) 110:        { 111:            if (rowGuess < secretRow) 112:                map[rowGuess, columnGuess] = 'S'; 113:            else if (rowGuess > secretRow) 114:                map[rowGuess, columnGuess] = 'N'; 115:            else 116:                map[rowGuess, columnGuess] = 'R'; 117:        } 118:        else 119:        { 120:            if (columnGuess < secretColumn) 121:                map[rowGuess, columnGuess] = 'E'; 122:            else if (columnGuess > secretColumn) 123:                map[rowGuess, columnGuess] = 'W'; 124:            else 125:                map[rowGuess, columnGuess] = 'C'; 126:        } 127:        PrintMap(); 128:        return islandFound; 129:    } 130: 131:    public int GetNumberOfGuesses() 132:    { 133:        return guessCounter; 134:    } 135:} First Choose size of map: Enter number of rows: 5<enter> Enter number of columns: 10<enter> Below is a map of the ocean: There is a hidden island behind one of the waves Can you find it? Make a (qualified) guess by entering a row and a column Every second guess is evaluated in East West direction The remaining guesses in North South direction An E in the map means the Island is to the East of this point W means to the west; N to the North and S to the South R means correct row but wrong column C means correct column but wrong row   0123456789 0 ~~~~~~~~~~ 1 ~~~~~~~~~~ 2 ~~~~~~~~~~ 3 ~~~~~~~~~~ 4 ~~~~~~~~~~ Enter row: 3<enter> Enter column: 7<enter>   0123456789 0 ~~~~~~~~~~ 1 ~~~~~~~~~~ 2 ~~~~~~~~~~ 3 ~~~~~~~W~~ 4 ~~~~~~~~~~ Enter row: 1<enter> Enter column: 2<enter>   0123456789 0 ~~~~~~~~~~ 1 ~~S~~~~~~~ 2 ~~~~~~~~~~ 3 ~~~~~~~W~~ 4 ~~~~~~~~~~ Enter row: 2<enter> Enter column: 3<enter>   0123456789 0 ~~~~~~~~~~ 1 ~~S~~~~~~~ 2 ~~~E~~~~~~ 3 ~~~~~~~W~~ 4 ~~~~~~~~~~ Enter row: 4<enter> Enter column: 5<enter>   0123456789 0 ~~~~~~~~~~ 1 ~~S~~~~~~~ 2 ~~~E~~~~~~ 3 ~~~~~~~W~~ 4 ~~~~~N~~~~ Enter row: 3<enter> Enter column: 4<enter>   0123456789 0 ~~~~~~~~~~ 1 ~~S~~~~~~~ 2 ~~~E~~~~~~ 3 ~~~~C~~W~~ 4 ~~~~~N~~~~ Enter row: 2<enter> Enter column: 4<enter>   0123456789 0 ~~~~~~~~~~ 1 ~~S~~~~~~~ 2 ~~~EI~~~~~ 3 ~~~~C~~W~~ 4 ~~~~~N~~~~ Well done! The letter I marks the island You found the island in 6 guesses Care to play another game? Y)es N)o N<enter> 

Jagged Arrays

A two- (or more) dimensional array does not need to be rectangular, meaning the rows of the array do not need to be of the same length. An array of this kind where different rows have different numbers of columns is called a jagged array. To illustrate a jagged array, let's recollect the rectangular requests array from Listing 11.3 that was used to collect elevator requests for seven days, and for 24 hours every day. Line 7 declared and defined it as follows:

 ushort [ , ] requests = new ushort [7,24]; 

If, instead of collecting elevator requests for 24 hours every single day, you want to collect data for say, 18 hours during the fourth and the fifth day, and merely for 12 hours the sixth and seventh day, you can tailor make the array to mirror this need. The corresponding array would be jagged and declared as


graphics/11infig01.gif

The fact that a two-dimensional array is an array of arrays becomes particularly obvious during the declaration just shown. The first line

 ushort [] [] requests; 

contains two empty pairs of square brackets to symbolize this characteristic, and the next line

 requests = new ushort [7][]; 

assigns an array object reference to requests with the following characteristics it contains 7 array elements (the length is 7), where each array element represents an array of ushorts that can be of any length.

We are now free to assign a one-dimensional array of any length to each of these seven array elements. This is done in the next seven lines. For example,

 requests[4] = new ushort [18]; 

assigns a one-dimensional array of length 18 to the fifth element of requests. After the last seven lines shown previously have been executed, requests can be illustrated as shown in Figure 11.11.

Figure 11.11. The jagged array requests.
graphics/11fig11.gif

When the rows of a two-dimensional array are not all of the same length, programmers refer to it as a jagged array. This can be extended to an array of multiple dimensions. An array for which it is true that one of the dimensions contain one-dimensional arrays of differing lengths, the array is said to be jagged.

Sometimes jagged arrays are referred to as ragged arrays.

You cannot use the same syntax to access an individual array element of a jagged array as that used for a rectangular array. Instead of providing one pair of square brackets containing two numbers separated by a comma, as in

 request [3,5]    //only valid for accessing element of a rectangular array 

you need to provide two pairs of square brackets, each containing an index, as in the following:

 requests[3][5]   //valid when accessing element of array of arrays 
Working with Jagged Arrays

If you want to visit every single element of a jagged array, you need to slightly adjust the nested for loops used previously to traverse a rectangular array. This is not so much for syntactic reasons as for logic reasons. The outer for loop remains untouched. However, because the number of inner loops now varies and is dependant on the loop counter of the outer loop (i in this case), we need to involve this variable in the loop condition of the inner loop. This is shown in the next piece of code:


graphics/11infig02.gif

As the outer loop traverses through each row via the i counter, the inner loop is able to constantly adjust to the correct length.

Listing 11.5 puts these pieces together. It is similar to Listing 11.3, but this time utilizes a jagged array to suit a data collection timetable with varying numbers of hours monitored on different days, as shown in Table 11.2.

Table 11.2. Timetable for Elevator Requests Monitoring
Day Number of hours monitored
1 24
2 24
3 24
4 18
5 18
6 12
7 12

The number of requests are generated randomly and assigned to all array elements of requests. The resulting jagged table is then printed out (see sample output).

Listing 11.5 JaggedElevatorRequests.cs
01: using System; 02: 03: class JaggedElevatorRequests 04: { 05:     public static void Main() 06:     { 07:         Random randomizer = new Random(); 08:         ushort [][] requests; 09:         requests = new ushort [7][]; 10:         requests[0] = new ushort[24]; 11:         requests[1] = new ushort[24]; 12:         requests[2] = new ushort[24]; 13:         requests[3] = new ushort[18]; 14:         requests[4] = new ushort[18]; 15:         requests[5] = new ushort[12]; 16:         requests[6] = new ushort[12]; 17: 18:          //Insert randomly generated number of requests for each array 19:          //element of requests. 20:         for (int i = 0; i < requests.Length; i++) 21:         { 22:             for (int j = 0; j < requests[i].Length; j++) 23:             { 24:                 if ((j < 8) || (j > 18)) 25:                     requests[i][j] = (ushort)randomizer.Next(1,10); 26:                 else 27:                     requests[i][j] = (ushort)randomizer.Next(20,99); 28:             } 29:         } 30: 31:          //Print out table showing requests of all hours of every day 32:         Console.WriteLine("                                  Hour\n"); 33:         Console.Write("    "); 34:         for (int i = 0; i < 24; i++) 35:         { 36:             Console.Write("{0,2}  ",i); 37:         } 38:         Console.Write("\nDay"); 39:         for (int i = 0; i < requests.Length; i++) 40:         { 41:             Console.Write("\n{0}    ", (i + 1)); 42:             for (int j = 0; j < requests[i].Length; j++) 43:             { 44:                 Console.Write("{0,2}  ", requests[i][j]); 45:             } 46:         } 47:     } 48: }                                  Hour      0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Day 1    2  7  1  3  4  5  6  9 62 86 39 92 54 65 89 60 62 97 63  4  6  9  4  8 2    2  2  4  5  1  9  3  2 32 75 46 69 47 29 40 76 59 71 97  5  4  7  1  1 3    8  9  7  2  8  4  8  3 49 34 31 36 27 31 89 67 65 30 79  7  3  3  2  4 4    7  8  9  9  9  8  5  4 25 49 73 27 36 29 42 80 85 27 5    3  2  9  1  6  4  7  4 96 38 21 46 83 61 84 44 53 20 6    5  9  3  5  7  7  2  7 57 82 51 55 7    8  9  9  2  9  9  5  7 36 63 33 61 

All parts of the source code have been explained prior to the source code listing.

The individual rows of a jagged array can, like a one-dimensional array, be initialized at the time the array is created with values written explicitly in the source code. Recall how the one-dimensional array ages was initialized by writing:

 byte [] ages  = {23, 27, 21, 30, 19, 34} ; 

Each individual row of a jagged array can be initialized in a similar fashion. If rainfall is a jagged array with 4 rows of base type double and declared as

 double [] [] rainfall = new double [3] []; 

each row can be initialized as shown

 rainfall[0] = new double[] {23.5, 44.1, 87.3, 89.1} ; rainfall[1] = new double[] {22.1, 51.3, 76.2, 12.2, 45.0, 11.9} ; rainfall[2] = new double[] {9.0, 13.5} ; 

assigning four, six, and two elements to each row, respectively.


   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net