2D Arrays
Introduction to 2D Arrays
As we have noted previously, an array is a group of data consisting of the same type. This means that we can have an array of primitive data types (such as integers):
[1, 2, 3, 4, 5]
["hello", "world", "how", "are" "you"]
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Additionally, we can have 2D arrays which are not rectangular in shape. These are called jagged arrays:
[['a', 'b', 'c', 'd'], ['e', 'f'], ['g', 'h', 'i', 'j'], ['k']]
Why use 2D arrays?

It is useful to use 2D arrays for situations where you need to store and organize data by rows and columns. For example, exporting data to be used in a spreadsheet.

You can condense multiple arrays down to a single variable using 2D arrays. For example, if you have 10 students who each have 10 different quiz grades, you can represent the overall class quiz grades as a 10x10 2D array by having each row represent a student and each column represent one of the quizzes they have taken.

2D arrays can be used to map out data. For example, if you want to create a game of tictactoe, you can represent the game state by using a 3x3 2D array.
There are many other ways to use 2D arrays depending on the application. The only downside is that once initialized, no new rows or columns can be added or removed without copying the data to a newly initialized 2D array. This is because the length of arrays in Java are immutable (unable to be changed after creation).
Declaration, Initialization, and Assignment
When declaring 2D arrays, the format is similar to normal, onedimensional arrays, except that you include an extra set of brackets after the data type. In this example, int
represents the data type, the first set of brackets []
represent an array, and the second set of brackets []
represent that we are declaring an array of arrays.
int[][] intTwoDArray;
[]
) of int
arrays (int[])
. So we end up with int[][]
. Now that we've declared a 2D array, let's look at how to initialize it with starting values. When initializing arrays, we define their size. Initializing a 2D array is different because, instead of only including the number of elements in the array, you also indicate how many elements are going to be in the subarrays. This can also be thought of as the number of rows and columns in the 2D matrix.
1 2 

1 2 

If you already know what values are going to be in the 2D array, you can initialize it and write all of the values into it at once. We can accomplish this through initializer lists.

In Java, initializer lists are a way of initializing arrays and assigning values to them at the same time.

We can use this for 2D arrays as well by creating an initializer list of initializer lists.
An example of an intializer list for a regular array would be:
char[] charArray = {'a', 'b', 'c', 'd'};
There are three situations in which we can use initializer lists for 2D arrays:

In the case where the variable has not yet been declared, we can provide an abbreviated form since Java will infer the data type of the values in the initializer lists:
double[][] doubleValues = {{1.5, 2.6, 3.7}, {7.5, 6.4, 5.3}, {9.8, 8.7, 7.6}, {3.6, 5.7, 7.8}};

If the variable has already been declared, you can initialize it by creating a
new
2D array object with the initializer list values:1 2
String[][] stringValues; stringValues = new String[][] {{"working", "with"}, {"2D", "arrays"}, {"is", "fun"}};

The previous method also applies to assigning a new 2D array to an existing 2D array stored in a variable.
Accessing Elements in a 2D Array
For a normal array, all we need is to provide an index (starting at 0
) which represents the position of the element we want to access. Let's look at an example!
Given an array of five strings:
String[] words = {"cat", "dog", "apple", "bear", "eagle"};
0
, the last element of the array minus one (in this case, 4
), and any of the elements in between. We provide the index of the element we want to access inside a set of brackets. Let's see those examples in code: 1 2 3 4 5 6 7 8 

1 2 3 4 5 


The first value represents and row and the second value represents a column in the matrix.

The first value represents which subarray to access from the main array and the second value represents which element of the subarray is accessed.
The above example of the 2D array called data
can be visualized like so. The indices are labeled outside the matrix:
Using this knowledge, we now know that the result of int stored = data[0][2];
would store the integer 6
. This is because the value of 6
is located on the first row (index 0
) and the third column (index 2
). Here is a template which cal be used for accessing elements in 2D arrays:
datatype variableName = existing2DArray[row][column];
ArrayIndexOutOfBoundsException
error will be given by the application. Modifying Elements in a 2D Array
Now let's review how to modify elements in a normal array.
For a one dimensional array, you provide the index of the element which you want to modify within a set of brackets next to the variable name and set it equal to an acceptable value:
storedArray[5] = 10;
For 2D arrays, the format is similar, but we will provide the outer array index in the first set of brackets and the subarray index in the second set of brackets. We can also think of it as providing the row in the first set of brakcets if we were to visualize the 2D array as a rectangular matrix:
twoDArray[1][3] = 150;
Let's say we wanted to replace four values from a new 2D array called intTwoD
. Look at this example code to see how to pick individual elements and assign new values to them.
1 2 3 4 5 6 

Here is a before and after image showing when the 2D array was first initialized compared to when the four elements were accessed and modified:
Review of Nested Loops
Nested loops consist of two or more loops placed within each other. We will be looking at one loop nested within another for 2D traversal.
The way it works is that, for every iteration of the outer loop, the inner loop finishes all of its iterations.
Here is an example using for loops:
1 2 3 4 5 6 

The outer index is: 0
The inner index is: 0
The inner index is: 1
The inner index is: 2
The inner index is: 3
The outer index is: 1
The inner index is: 0
The inner index is: 1
The inner index is: 2
The inner index is: 3
The outer index is: 2
The inner index is: 0
The inner index is: 1
The inner index is: 2
The inner index is: 3
This is an important concept for 2D array traversal, because for every row in a two dimensional matrix, we want to iterate through every column.
Nested loops can consist of any type of loop and with any combination of loops. Let's take a look at a few more interesting examples.
Here is an example of nested while loops:
1 2 3 4 5 6 7 8 9 

1 2 3 4 5 6 7 8 9 10 

0 0 0 0 0
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
6 12 18 24 30
Traversing 2D Arrays: Introduction
Traversing 2D arrays using loops is important because it allows us to access many elements quickly, access elements in very large 2D arrays, and even access elements in 2D arrays of unknown sizes.
Let's remember the structure of 2D arrays in Java:
char[][] letterBlock = {{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j', 'k', 'l'}};
Let’s see what happens when we access elements of the outer array
1 2 3 4 

[a, b, c]
[d, e, f]
[g, h, i]
[j, k, l]
Let’s take a look at an example which produces the same output, but can handle any sized 2D array.
1 2 3 

[a, b, c]
[d, e, f]
[g, h, i]
[j, k, l]
[][]
, where the first set of brackets contains the index of the element of the outer array and the second set of brackets contains the index of the element in the subarray. If we wanted to retrieve the letter 'f'
we would use: char storedLetter = letterBlock[1][2];
You might be wondering how we can figure out the number of iterations needed in order to fully traverse the 2D array.

In order to find the number of elements in the outer array, we just need to get the length of the 2D array.

int lengthOfOuterArray = letterBlock.length;

When thinking about the 2D array in matrix form, this is the height of the matrix (the number of rows)


In order to find the number of elements in the subarray, we can get the length of the subarray after it has been retrieved from the outer array.

Remember that we retrieved the sub array earlier using this format:
char[] subArray = letterBlock[0];

Therefore, we can use this to get the length of the first subarray in the 2D array

int lengthOfSubArray = letterBlock[0].length;

When thinking about the 2D array in matrix form, this is the width of the matrix (the number of columns)


In most cases, getting the length of the first subarray in the 2D array will apply to the rest of the subarrays (if it is rectangular in shape), but there are rare occasions where the length of the subarrays could be different. This occurs if the 2D array is a jagged array.

Let's look at an example:
1 2 3 4 5 6 

a
as being the outer loop index, and the variable b
as being the inner loop index. This gives the following output:
Accessed: a Accessed: b Accessed: c
Accessed: d Accessed: e Accessed: f
Accessed: g Accessed: h Accessed: i
Within the nested for loop, we can see that each of the subarray elements are being accessed by using the outer loop index for the outer array, and the inner loop index for the subarray.
Here is a diagram to help visualize how the 2D array is traversed using nested loops:
We don't have to only use regular for loops for traversing 2D arrays. We can use enhanced for loops if we do not need to keep track of the indices. Since enhanced for loops only use the element of the arrays, it is a bit more cumbersome to keep track of which index we are at. This same idea applies to while and dowhile loops as well. This is why we usually use regular for loops except for when we want to do something simple like printing.
Traversing 2D Arrays: Practice with Loops
In enhanced for loops, each element is iterated through until the end of the array. When we think about the structure of 2D arrays in Java (arrays of array objects) then we know that the outer enhanced for loop elements are going to be arrays.
Let's take a look at an example:
Given this 2D array of character data:
char[][] charData = {{'a', 'b', 'c', 'd', 'e', 'f'},{'g', 'h', 'i', 'j', 'k', 'l'}};
1 2 3 4 5 6 

for( datatype elementName : arrayName){
. Since 2D arrays in Java are arrays of arrays, each element in the outer enhanced for loop is an entire row of the 2D array. The nested enhanced for loop is then used to iterate through each element in the extracted row. Here is the output of the above code: a b c d e f
g h i j k l
1 2 3 4 5 6 7 8 9 10 

a b c d e f
g h i j k l
Traversing 2D Arrays: RowMajor Order
Rowmajor order for 2D arrays refers to a traversal path which moves horizontally through each row starting at the first row and ending with the last.
Although we have already looked at how 2D array objects are stored in Java, this ordering system conceptualizes the 2D array into a rectangular matrix and starts the traversal at the top left element and ends at the bottom right element.
Here is a diagram which shows the path through the 2D array:
This path is created by the way we set up our nested loops. In the previous exercise, we looked at how we can traverse the 2D array by having nested loops in a variety of formats, but if we want to control the indices, we typically use standard for loops.
Let’s take a closer look at the structure of the nested for loops when traversing a 2D array:
Given this 2D array of strings describing the element positions:
1 2 3 4 

1 2 3 4 5 6 7 8 9 10 

Step: 0, Element: [0][0]
Step: 1, Element: [0][1]
Step: 2, Element: [0][2]
Step: 3, Element: [1][0]
Step: 4, Element: [1][1]
Step: 5, Element: [1][2]
Step: 6, Element: [2][0]
Step: 7, Element: [2][1]
Step: 8, Element: [2][2]
Step: 9, Element: [3][0]
Step: 10, Element: [3][1]
Step: 11, Element: [3][2]
This is because in our for loop, we are using the number of rows as the termination condition within the outer for loop header a < matrix.length;
. Additionally, we are using the number of columns b < matrix[a].length
as the termination condition for our inner loop. Logically we are saying: “For every row in our matrix, iterate through every single column before moving to the next row”. This is why our above example is traversing the 2D array using rowmajor order.
Here is a diagram showing which loop accesses which part of the 2D array for rowmajor order:
Why Use RowMajor Order?
Rowmajor order is important when we need to process data in our 2D array by row. You can be provided data in a variety of formats and you may need to perform calculations of rows of data at a time instead of individual elements. Let's take one of our previous checkpoint exercises as an example. You were asked to calculate the sum of the entire 2D array of integers by traversing and accessing each element. Now, if we wanted to calculate the sum of each row, or take the average of each row, we can use rowmajor order to access the data in the order that we need. Let's look at an example!
Given a 6X3 2D array of doubles:
1 2 3 4 5 6 

Calculate the sum of each row using rowmajor order:
1 2 3 4 5 6 7 8 

The output for the above code is:
Row: 0, Sum: 1.62
Row: 1, Sum: 2.16
Row: 2, Sum: 1.04
Row: 3, Sum: 1.15
Row: 4, Sum: 2.04
Row: 5, Sum: 1.06
Traversing 2D Arrays: ColumnMajor Order
Columnmajor order for 2D arrays refers to a traversal path which moves vertically down each column starting at the first column and ending with the last.
This ordering system also conceptualizes the 2D array into a rectangular matrix and starts the traversal at the top left element and ends at the bottom right element. Columnmajor order has the same starting and finishing point as rowmajor order, but it’s traversal is completely different
Here is a diagram which shows the path through the 2D array:
In order to perform columnmajor traversal, we need to set up our nested loops in a different way. We need to change the outer loop from depending on the number of rows, to depending on the number of columns. Likewise we need the inner loop to depend on the number of rows in its termination condition.
Let's look at our example 2D array from the last exercise and see what needs to be changed.
Given this 2D array of strings describing the element positions:
1 2 3 4 

1 2 3 4 5 6 7 8 9 10 

Step: 0, Element: [0][0]
Step: 1, Element: [1][0]
Step: 2, Element: [2][0]
Step: 3, Element: [3][0]
Step: 4, Element: [0][1]
Step: 5, Element: [1][1]
Step: 6, Element: [2][1]
Step: 7, Element: [3][1]
Step: 8, Element: [0][2]
Step: 9, Element: [1][2]
Step: 10, Element: [2][2]
Step: 11, Element: [3][2]
matrix
is different from the way we accessed them when using rowmajor order. Let’s remember that the way we get the number of columns is by using matrix[0].length
and the way we get the number of rows is by using matrix.length
. Because of these changes to our for loops, our iterator a
now iterates through every column while our iterator b
iterates through every row. Since our iterators now represent the opposite values, whenever we access an element from our 2D array, we need to keep in mind what indices we are passing to our accessor. Remember the format we use for accessing the elements matrix[row][column]
? Since a
now iterates through our column indices, we place it in the right set of brackets, and the b
is now placed in the left set of brackets. Here is a diagram showing which loop accesses which part of the 2D array for columnmajor order:
Why Use ColumnMajor Order?
Column major order is important because there are a lot of cases when you need to process data vertically. Let’s say that we have a chart of information which includes temperature data about each day. The top of each column is labeled with a day, and each row represents an hour. In order to find the average temperature per day, we would need to traverse the data vertically since each column represents a day. As mentioned in the last exercise, data can be provided in many different formats and shapes and you will need to know how to traverse it accordingly.
Let’s look at our sum example from the last exercise, but now using columnmajor order.
Given a 6X3 2D array of doubles:
1 2 3 4 5 6 

1 2 3 4 5 6 7 8 

Column: 0, Sum: 2.83
Column: 1, Sum: 4.31
Column: 2, Sum: 1.93
Combining Traversal and Conditional Logic
When working with 2D arrays, it is important to be able to combine traversal logic with conditional logic in order to effectively navigate and process the data. Here are a few ways in how conditional logic can affect 2D array traversal:

Skipping or selecting certain rows and columns

Modifying elements only if they meet certain conditions

Complex calculations using the 2D array data

Formatting the 2D array

Avoiding exceptions / smart processing
Let’s go over a few examples which use these ideas:
First, let’s think about a situation where you have some string data inside a 2D array. We have an application which allows users to input events on a calendar. This is represented by a 5x7 2D array of strings. Due to the fact that the number of days in each month is slightly different and that there are less than 35 days in a month, we know that some of our elements are going to be empty. We want our application to do a few things:

Detect which days of which weeks have something planned and alert us about the event.

Count the number of events for each week

Count the number of events for each day
Here is a visualization of what our calendar data looks like after a user has entered in some event information:
1 

There are a few things to note:

Rowmajor or columnmajor order can be used to access the individual events

Rowmajor order must be used to count the number of events per week since each row represents a week
Let’s take care of the first 2 requirements in one set of nested rowmajor loops
1 2 3 4 5 6 7 8 9 10 11 12 13 

Week: 1, Day: 1, Event: volunteer
Week: 1, Day: 2, Event: delivery
Week: 1, Day: 5, Event: doctor
Week: 1, Day: 7, Event: soccer
Total number of events for week 1: 4
Week: 2, Day: 2, Event: exam 1
Week: 2, Day: 4, Event: mechanic
Week: 2, Day: 7, Event: soccer
Total number of events for week 2: 3
Week: 3, Day: 1, Event: volunteer
Week: 3, Day: 2, Event: off work
Week: 3, Day: 4, Event: birthday
Week: 3, Day: 6, Event: concert
Total number of events for week 3: 4
Week: 4, Day: 2, Event: exam 2
Week: 4, Day: 5, Event: doctor
Week: 4, Day: 7, Event: soccer
Total number of events for week 4: 3
Week: 5, Day: 1, Event: visit family
Total number of events for week 5: 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 

Number of events on Sundays: 3
Number of events on Mondays: 4
Number of events on Tuesdays: 0
Number of events on Wednesdays: 2
Number of events on Thursdays: 2
Number of events on Fridays: 1
Number of events on Saturdays: 3
Additionally, we can use conditional logic to skip portions of the 2D array. For example, let’s say we wanted to print the events for weekdays only and skip the weekends.
We could use a conditional statement such as if(j!=0 && j!=6)
in order to skip Sunday (0
) and Saturday (6
).
These modifications to our 2D array traversal are very common when processing data in applications. We need to know which cells to look at (skipping column titles for example), which cells to ignore (empty data, invalid data, outliers, etc.), and which cells to convert (converting string input from a file to numbers).
2D Array Review
Let’s review the concepts we have learned throughout these notes.
Arrays are objects in Java, we can have arrays of objects, therefore we can also have arrays of arrays. This is the way 2D arrays are structured in Java.
We can declare and initialize 2D arrays in a few different ways depending on the situation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 

char c = letters[0][1];

We can also think of them as the index of the outer array and the index of the subarray

We can modify elements the same way
letters[1][2] = 'z';
We traverse 2D arrays using nested loops.

We can use loops of any type, but we typically use nested for loops to keep track of the indices

Rowmajor order traverses through each row moving horizontally to the right through each row

Columnmajor order traverses through each column moving vertically down through each column

Rowmajor order and columnmajor order start and end on the same elements, but the paths are different.

In order to convert rowmajor to columnmajor, we need to make the outer loop terminating condition depend on the number of columns, make the inner loop terminating condition depend on the number of rows, and flip the variables in our accessor within the inner loop to ensure that we don’t try to access outside of the 2D array since we flipped the direction of traversal.
Here are examples of rowmajor and columnmajor order:
1 2 3 4 5 6 7 8 9 10 11 12 13 

Here is an example of traversal with conditional logic.
Given this 2D array of Strings:
String[][] words = {{"championship", "QUANTITY", "month"},{"EMPLOYEE", "queen", "understanding"},{"method", "writer", "MOVIE"}};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 

Before...
[[championship, QUANTITY, month],
[EMPLOYEE, queen, understanding],
[method, writer, MOVIE]]
After...
[[CHAMPIONSHIP, quantity, MONTH],
[employee, QUEEN, UNDERSTANDING],
[METHOD, WRITER, movie]]