🚨 The Sub Call

AP CSA · 4.13 — Implementing 2D Array Algorithms

Your teacher’s out sick. You’re covering 1st period, and everything you need — attendance, quiz scores, empty seats — lives in one seating chart. A seating chart is a 2D array: rows are pods, columns are seats.

The one idea to hold onto: a 2D array is an array wearing another array as a trenchcoat.

chart              <- outer array: one slot per ROW
   chart[row]         <- the array INSIDE it: one slot per SEAT
      chart[row][col]    <- one specific seat

Every algorithm below — traverse, search, sum, find-the-max — is a 1D algorithm you already know. The only new move is running it once per row. That’s the entire unit.

You’ll leave able to: traverse a grid, search it for a value and its location, accumulate sums/counts/averages, and track a “best so far” while walking — the pattern behind almost every 2D FRQ.

Run cells in a Java kernel (IJava) if you have one — otherwise just write the code and paste it into your IDE. // TODO = your blank. <details> = a hidden hint + solution; open it only once you’re actually stuck.

import java.util.Arrays;

// -1 = empty seat. Anything else = that student's most recent quiz score.
int[][] scoreChart = {
    {87, 92, -1, 76, 88, 91},   // Pod A
    {-1, 65, 72, 89, -1, 94},   // Pod B
    {90, 88, 77, -1, 82, 79},   // Pod C
    {68, -1, 95, 91, 84, -1},   // Pod D
    {73, 86, 90, 88, -1, 77}    // Pod E
};
String[] podNames = {"Pod A", "Pod B", "Pod C", "Pod D", "Pod E"};
System.out.println("Grid loaded: " + scoreChart.length + " pods x " + scoreChart[0].length + " seats.");

Chapter 1 — The Full Sweep

Walk the room pod by pod, seat by seat — row-major order, the default traversal:

for (int row = 0; row < chart.length; row++) {
    for (int col = 0; col < chart[row].length; col++) { ... }
}

Need seat-by-seat across every pod instead (column-major)? Swap which loop goes outside — same two lines, new question answered. That swap is the entire concept of “traversal order.”

Task: write printSeatingChart, printing every seat in row-major order as Pod A, Seat 1: 87 (or EMPTY for -1). Seats are 1-indexed for humans; col is 0-indexed.

public static void printSeatingChart(int[][] chart, String[] podNames) {
    for (int row = 0; row < /* TODO */; row++) {
        for (int col = 0; col < /* TODO */; col++) {
            String seatValue = (chart[row][col] == -1) ? "EMPTY" : /* TODO: score as a String */;
            System.out.println(/* TODO: podNames[row] + ", Seat " + (seat #) + ": " + seatValue */);
        }
    }
}
printSeatingChart(scoreChart, podNames);

✅ Hint + solution ```java public static void printSeatingChart(int[][] chart, String[] podNames) { for (int row = 0; row < chart.length; row++) { for (int col = 0; col < chart[row].length; col++) { String seatValue = (chart[row][col] == -1) ? "EMPTY" : String.valueOf(chart[row][col]); System.out.println(podNames[row] + ", Seat " + (col + 1) + ": " + seatValue); } } } ``` `row` is bounded by `chart.length` (rows), `col` by `chart[row].length` (that row's columns) — never swap them. This traversal costs `rows × cols` visits, i.e. **O(n²)**-shaped. And always `<`, never `<=` — `chart.length` is one index *past* the last valid row, so `<=` guarantees an `ArrayIndexOutOfBoundsException`.

A parent wants to know where their kid, who scored a 94, was sitting. Now the location matters, so you search and return {row, col} — or {-1, -1} if it’s nowhere to be found (same sentinel idea as an empty seat).

Task: write findSeat(chart, targetScore). The moment you find a match, return immediately — don’t keep scanning once you already have your answer.

public static int[] findSeat(int[][] chart, int targetScore) {
    for (int row = 0; row < chart.length; row++) {
        for (int col = 0; col < chart[row].length; col++) {
            if (/* TODO: is this the seat? */) {
                return /* TODO: {row, col} */;
            }
        }
    }
    return /* TODO: not-found sentinel */;
}
int[] result = findSeat(scoreChart, 94);
System.out.println("Found at: Pod " + result[0] + ", seat index " + result[1]);

✅ Hint + solution ```java public static int[] findSeat(int[][] chart, int targetScore) { for (int row = 0; row < chart.length; row++) { for (int col = 0; col < chart[row].length; col++) { if (chart[row][col] == targetScore) return new int[] {row, col}; } } return new int[] {-1, -1}; } ``` `return` exits the method from *any* depth of nesting instantly — the cleanest way to write "stop as soon as you find it." Searching for `94` should give `{1, 5}` (Pod B). Graders explicitly check for this early exit; scanning the whole grid after already finding the answer costs real FRQ points.

Chapter 3 — The Headcount

Front office needs: empty seats, filled seats, and the class average — all from one traversal, using accumulators instead of three separate loops.

Task: write classSummary, returning {emptyCount, occupiedCount, average}. Average = total ÷ occupied seats (empties never took the quiz) — and guard against dividing by zero if the whole class is absent.

public static double[] classSummary(int[][] chart) {
    int emptyCount = 0, occupiedCount = 0, totalScore = 0;
    for (int row = 0; row < chart.length; row++) {
        for (int col = 0; col < chart[row].length; col++) {
            if (chart[row][col] == -1) {
                /* TODO: update the right counter */
            } else {
                /* TODO: update the right counter(s) */
            }
        }
    }
    double average = /* TODO: guard divide-by-zero, watch int-division truncation */;
    return new double[] {emptyCount, occupiedCount, average};
}
double[] s = classSummary(scoreChart);
System.out.println("Empty: " + s[0] + " | Occupied: " + s[1] + " | Avg: " + s[2]);

✅ Hint + solution ```java public static double[] classSummary(int[][] chart) { int emptyCount = 0, occupiedCount = 0, totalScore = 0; for (int row = 0; row < chart.length; row++) { for (int col = 0; col < chart[row].length; col++) { if (chart[row][col] == -1) { emptyCount++; } else { occupiedCount++; totalScore += chart[row][col]; } } } double average = (occupiedCount == 0) ? 0.0 : (double) totalScore / occupiedCount; return new double[] {emptyCount, occupiedCount, average}; } ``` Expect **7 empty, 23 occupied, avg ≈ 83.57**. The `(double)` cast matters: without it, `totalScore / occupiedCount` is `int / int` and silently truncates (`1922 / 23` → `83`, not `83.57`) — a bug that runs fine and just quietly gives the wrong number. (Same trick works column-wise: keep an `int[] seatTotals` sized to the column count, indexed by `col`, accumulating while you traverse row-major — no need to change traversal order to answer a column-based question.)

Capstone — SeatFinder

Find the top scorer in the whole grid, location included. This is a new shape: track the best candidate while you walk, updating your “champion” any time you beat it — the backbone of every max/min FRQ.

Task: write findTopScorer, returning {row, col, score}. Start your champion score at -1 (any real score beats it instantly).

public static int[] findTopScorer(int[][] chart) {
    int bestScore = -1, bestRow = -1, bestCol = -1;
    for (int row = 0; row < chart.length; row++) {
        for (int col = 0; col < chart[row].length; col++) {
            if (/* TODO: real score AND beats current champion? */) {
                /* TODO: crown new champion */
            }
        }
    }
    return new int[] {bestRow, bestCol, bestScore};
}
int[] top = findTopScorer(scoreChart);
System.out.println("Top: " + podNames[top[0]] + ", seat " + (top[1] + 1) + ", score " + top[2]);

✅ Hint + solution ```java public static int[] findTopScorer(int[][] chart) { int bestScore = -1, bestRow = -1, bestCol = -1; for (int row = 0; row < chart.length; row++) { for (int col = 0; col < chart[row].length; col++) { if (chart[row][col] != -1 && chart[row][col] > bestScore) { bestScore = chart[row][col]; bestRow = row; bestCol = col; } } } return new int[] {bestRow, bestCol, bestScore}; } ``` Champion ends up at **Pod D, seat 3, score 95**. Flip `>` to `<` and the starting value to `Integer.MAX_VALUE` and you've built a *minimum* finder with the identical skeleton — same pattern, opposite direction.

🎯 Exam-Ready Checklist

  • Loops correctly nested, bounds < never <=
  • chart.length = rows, chart[row].length = columns — never swapped
  • Accumulators start correctly: sums at 0, “best so far” at a value guaranteed to lose
  • -1/sentinel handled for “not found,” divide-by-zero guarded
  • Method signature matches exactly what was asked

Reflect: every algorithm here is a 1D algorithm you already knew, run once per row. Which decision — row-major, column-major, or “track the best” — do you now reach for without being told? That instinct is the actual goal of 4.13. The trenchcoat’s off: it was two arrays the whole time, and now so is every grid you’ll ever look at.

📚 References