Defining 2D Arrays

A 2D array is a way of storing data similar to a table with rows and columns, created by storing multiple arrays inside of a parent array. Just like a spreadsheet, where each cell can be identified by its row and column number, elements in a 2D array can be accessed using their respective row and column indices. This organization allows for efficient storage and retrieval of data in a structured format, making it particularly useful for tasks involving tabular data or grids.

For example, take a 2D array initialized like this (more on how to initialize later):

/*
int[][] arr2D = {
    {3, 2, 7},
    {2, 6, 8},
    {5, 1, 9},
}

Now compare this code segment to this visual:

image

image sourced from geeksforgeeks

Here you can see how the “table” (2D array) is being constructed of multiple 1-dimensional arrays.

How to initialize a 2D array in Java

In Java, you can initialize a 2D array using various methods, depending on your requirements and preferences. Here are some common approaches:

Using Array Initializer

You can directly initialize a 2D array using an array initializer. This method is suitable when you know the values of the array elements at compile time. An important thing to note is that you can actually create 2D arrays with 1D arrays of varying lengths, adding more complexity to the 2D array that can be useful in some scenarios.

/*
int[][] array2D = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
*/

Initializing with default values

You can create a new 2D array with a set number of rows and columns with preset values:

  • null for reference types (i.e. String, char, boolean)
  • 0 for numeric types (i.e. int, double, float)
int rows = 2;
int columns = 2;
int[][] array2D = new int[rows][columns];

Indexing through a 2D array

In this image, you can see the indices assigned to each row and column:

image

image sourced from javarevisited

Using these indices, you can get data from the 2D array by indexing twice in this format: arr[row][column]

For example, to get 4 in the image above, you would do arr[1][2], because it is in the row at index 1 and the column at index 2 (assuming the 2d array is called arr).

Feel free to mess with the indices in the code block below to get different numbers

// Initializing 2D array
int[][] arr = {
    {1, 1, 1},
    {1, 2, 4},
    {1, 3, 9},
};

// indexing to get data in format arr[row][column]
arr[1][2];
4

However, the bulk of programming with 2D arrays involves loops, such as returning an array of the sums of each row in a 2D array:

public class Example {
    // Method for calculating the sum of a 2D array using nested for loops
    public static int[] rowSums(int[][] arr2D) {
        // Get number of rows
        int rows = arr2D.length;

        // New array to return sums
        int[] sums = new int[rows];

        // Loop through each row
        for (int i = 0; i < rows; i ++) {
            // Get number of columns for the selected row (useful for uneven amounts)
            int cols = arr2D[i].length;

            // Define sum for the row;
            int sum = 0;

            // Iterate through each column within the row, effectively iterating through all points
            for (int j = 0; j < cols; j ++) {
                sum += arr2D[i][j];
            }

            // Add sum to array
            sums[i] = sum;
        }

        return sums;
    }

    // method for printing arrays
    public static void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]);
            if (i < arr.length - 1) {
                System.out.print(", ");
            }
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // tester 2d array
        int[][] arr2D = {
            {3, 4, 1},
            {1, 1, 1},
            {2, 6, 4},
        };

        // calculate sums of each row
        int[] sums = rowSums(arr2D);

        // print results
        printArray(sums);
    }
}

Example.main(null);
8, 3, 12

Notes on Common Mistakes

Here are some notes on common mistakes made in the context of the 2D arrays question. People around the country will definitely make all of these mistakes many times over, so let’s hope that none of those people are people at our school.

Array Errors

We just had a lesson on common Arrays errors, so rather than rephrase their work, give these mistakes another look.

There are some Array errors that are especially relevant to 2D arrays problems, however. For example…

  • Off-By-One Errors

When working with a 2D array, you are working with an array full of arrays. In this structure, there is a set number of rows and columns when the structure is initialized. The length of each row and the number of rows are often different, so both must be identified properly before iterating using index-based loops.

Out of bounds errors will always lose you at least one point on the scoring guidelines, according to our research.

  • For-Each loop modification

If values are intended to be modified during an iterative process, do not use a for-each loop. The elements accessed using this strategy are copies; accessing the array indexes directly to make modifications is the only way for them to be retained. See the example below:

import java.util.Arrays;

int[][] modifyArray = {
    {1, 4, 3},
    {3, 8, 9}
};

System.out.println("Prior to modification: " + Arrays.deepToString(modifyArray));

for (int[] row : modifyArray) {
    for (int col : row) {
        if (col == 3) {
            col = 0;
        }
    }
}

System.out.println("After to modification: " + Arrays.deepToString(modifyArray));
Prior to modification: [[1, 4, 3], [3, 8, 9]]
After to modification: [[1, 4, 3], [3, 8, 9]]

Instead, when modifying, you can use a traditional for loop (which also comes with the added benefit of having direct access to the relevant indexes):

import java.util.Arrays;

int[][] actualModifyArray = {
    {1, 4, 3},
    {3, 8, 9}
};

System.out.println("Prior to modification: " + Arrays.deepToString(actualModifyArray));

for (int r = 0; r < actualModifyArray.length; r++) {
    for (int c = 0; c < actualModifyArray[r].length; c++) {
        if (actualModifyArray[r][c] == 3) {
            actualModifyArray[r][c] = 0;
        }
    }
}

System.out.println("After to modification: " + Arrays.deepToString(actualModifyArray));
Prior to modification: [[1, 4, 3], [3, 8, 9]]
After to modification: [[1, 4, 0], [0, 8, 9]]

FRQ Scoring Guidelines Mistakes

To succeed on the AP test, it’s important to be able to identify which elements of a correct answer earn points.

  • Most often, if you create a method in a previous part used to interact with a 2D array, it is intended that you use that method again in future methods.

Means of traversing and interacting with 2D arrays are relatively specific in the context of one College Board problem (i.e., iterate through to determine a condition about a row, find a value in a row, search columns by condition, etc.), so make sure to analyze the context to determine if a certain method may be used again to abstract what would otherwise be a more complex task in a future method. With practice, this connection should be obvious.

If there is no possible relevance between two methods, this may not necessarily be the case.

Make sure that you use the proper parameters when calling your own method! Scoring guidelines are sometimes lenient about this, but don’t tempt fate. Just format your call properly.

  • If a “helper” method is provided to you in a question, make sure to note it and use it.

If one is provided, it is most certainly present to make the process of writing the code less complex. Scoring guidelines will always include utilizing past student-created methods and “helper” methods.

You can also use the helper method’s functionality to aid your thinking about a question. If you are confused by its content and aren’t sure how to tackle the problem instinctively, you can be sure that a provided “helper” method will be a part of the solution.

Once again, make sure that you’re using the proper parameters!

  • Know how to use column-major order. (Many go into the test having only used row-major order.)

It’s very possible that a question will prompt you to access a 2D array by its columns. (We found two in research for this lesson.) If you know you haven’t practiced column-major order, give the code below a look. It might be a good idea to create your own code cell with a unique 2D array to practice with.

int[][] array = {
    {3, 5, 1},
    {9, 9, 7}
};

// you should always be able to use array[0].length for the number of columns
// since each row is the same length
int colNum = 1;
for (int col = 0; col < array[0].length; col++) {
    System.out.print("Column " + colNum + ":\t");
    for (int row = 0; row < array.length; row++) {
        System.out.print(array[row][col] + "\t");
    }
    System.out.println();
    colNum++;
}
Column 1:	3	9	
Column 2:	5	9	
Column 3:	1	7	

2021 2D Arrays FRQ

This question involves manipulating a 2D array of integers. You will write two static methods, both of which are in a class named ArrayResizer. Complete parts (a) and (b) for full credit.

Part A

(a) Write the method isNonZeroRow, which returns true if and only if all elements in row r of a two-dimensional array array2D are not equal to zero. For example, consider the following statement, which initializes a two-dimensional array.

Complete the isNonZeroRow method.

int[][] arr = {
    {2, 1, 0},
    {1, 3, 2},
    {0, 0, 0},
    {4, 5, 6}
};

Complete the isNonZeroRow method.

/** Returns true if and only if every value in row r of array2D is non-zero.
* Precondition: r is a valid row index in array2D.
* Postcondition: array2D is unchanged.
*/
class checkZero {
    public static boolean isNonZeroRow (int[][] array2D, int r) {
        for (int i = 0; i < array2D[r].length; i++) {
            if (array2D[r][i] == 0) {
                return false;
            }
        }
        return true;
    }

    public static void main (String[] args) {
        int[][] arr = {
            {2, 1, 0},
            {1, 3, 2},
            {0, 0, 0},
            {4, 5, 6}
        };

        System.out.println(isNonZeroRow(arr, 1));
    }
}

checkZero.main(null);
true

Part B

(b) Write the method resize, which returns a new two-dimensional array containing only rows from array2D with all non-zero values. The elements in the new array should appear in the same order as the order in which they appeared in the original array. The following code segment initializes a two-dimensional array and calls the resize method.

When the code segment completes, the following will be the contents of smaller.

int[][] arr = {
    {2, 1, 0},
    {1, 3, 2},
    {0, 0, 0},
    {4, 5, 6}
};
int[][] smaller = ArrayResizer.resize(arr);

When the code segment completes, the following will be the contents of smaller.

{ {1, 3, 2}, {4, 5, 6} }

A helper method, numNonZeroRows, has been provided for you. The method returns the number of rows in its two-dimensional array parameter that contain no zero values.

Complete the resize method. Assume that isNonZeroRow works as specified, regardless of what you wrote in part (a). You must use numNonZeroRows and isNonZeroRow appropriately to receive full credit.

(Note: This College Board question asks you explicitly to use the methods for credit, but it won’t always mention it even if they expect you to make use of previous methods.)

/** Returns a new, possibly smaller, two-dimensional array that contains only rows from array2D
with no zeros, as described in part (b).
* Precondition: array2D contains at least one column and at least one row with no zeros.
* Postcondition: array2D is unchanged.
*/
public class twoDHacks {
    public static boolean isNonZeroRow (int[][] array2D, int r) {
        for (int i = 0; i < array2D[r].length; i++) {
            if (array2D[r][i] == 0) {
                return false;
            }
        }
        return true;
    }

    public static int numNonZeroRows (int[][] arr) {
        int counter = 0;

        for (int i=0; i<arr.length; i++) {
            if (isNonZeroRow(arr, i)) {
                counter++;
            }
        }

        return counter;
    }

    public static int[][] resize (int[][] array2D) {
        int rows = array2D.length;
        int rows1 = numNonZeroRows(array2D);
        int columns = array2D[0].length;
        int[][] resizedArr = new int[rows1][columns];
        int rowcounter = 0;

        for (int i=0 ; i<rows ; i++) {
            if (isNonZeroRow(array2D, i)) {
                for (int j=0 ; j<columns ; j++) {
                    resizedArr[rowcounter][j] = array2D[i][j];
                }
                rowcounter++;
            }
        }

        return resizedArr;
    }

    public static StringBuilder stringer(int[][] arr) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            result.append("[");
            for (int j = 0; j < arr[i].length; j++) {
                result.append(arr[i][j]);
                if (j < arr[i].length - 1) {
                    result.append(", ");
                }
            }
            result.append("]");
            if (i < arr.length - 1) {
                result.append(",\n");
            }
        }

        return result;
    }


    public static void main (String[] args) {
        int[][] arr = {
            {2, 1, 0},
            {1, 3, 2},
            {0, 0, 0},
            {4, 5, 6}
        };

        int[][] smaller = twoDHacks.resize(arr);

        System.out.println(stringer(smaller));
    }
}

twoDHacks.main(null);
[1, 3, 2],
[4, 5, 6]

Ideas for Extra Credit

  • Finish the entire class ArrayResizer, including coding the “helper” method numNonZeroRows and a tester method that shows functionality.
  • Reflect on the common mistakes provided in this lesson, along with examples related to the provided FRQ.

Reflection on Common Mistakes

Reflecting on the common mistakes students might encounter in AP test FRQs involving 2D arrays, I’ve noticed a few critical areas where errors frequently occur. Understanding these can significantly boost my performance in both practicing and in the actual exam setting.

1. Reusing Methods Efficiently

When I first tackled a multipart FRQ, I often overlooked the importance of reusing methods I had already created. For example, after developing the isNonZeroRow method in part (a) of an FRQ, it’s essential to leverage this method again in subsequent parts like part (b). Initially, I might have tried to recheck conditions manually in each part, which not only wasted time but also increased the risk of errors. Learning to recognize when to reuse methods has helped me streamline my code and reduce potential bugs.

2. Accurate Parameter Handling

Correctly using parameters when calling methods has been another critical area for improvement. Early on, I struggled with ensuring that the parameters I used matched what the method expected. Missteps like calling isNonZeroRow(array2D) without the necessary row index were common. Now, I focus more on aligning my method calls with their defined parameters, such as using isNonZeroRow(array2D, i) within a loop. This attention to detail has helped me avoid simple yet costly errors.

3. Utilizing Provided Helper Methods

Initially, I underestimated the importance of the helper methods provided in FRQs. These methods are specifically designed to facilitate solving the problem and are integral to the intended solution. In the past, I might have ignored these helpers, opting to write my own implementations. This approach often led to redundant code and opened the door to additional mistakes. Now, I make it a point to understand and use these helper methods, which simplifies my coding process and ensures I’m aligning with the test’s expectations.

4. Mastering Column-major Order Traversal

My familiarity with row-major order initially left me unprepared for problems requiring column-major order traversal. This gap in my skills became apparent during practice sessions where specific questions demanded accessing data by columns. To remedy this, I’ve dedicated time to practicing both row-major and column-major order, enabling me to handle whatever the exam throws at me confidently.

Conclusion

Overall, these reflections on common mistakes have taught me the importance of method reuse, accurate parameter usage, the strategic application of provided helper methods, and versatility in array traversal. By addressing these areas, I feel more prepared to tackle array-based FRQs efficiently and effectively. Practicing with these points in mind has not only smoothed out my coding process but also deepened my understanding of how to approach complex problems systematically.