CompSci.rocks
FRQcsapa

SeatingChart: 2014 FRQ 3

A step-by-step solution to the 2014 AP CSA FRQ 3 (SeatingChart), covering filling a 2D array column by column from a List and removing entries that fail a condition in Java.

Turning a plain roster of students into a grid of assigned seats is what this AP Computer Science A free-response question is about — first building that grid one column at a time, then clearing out seats for students who have missed too many classes.

What This FRQ Tests

  • AP CSA units: Unit 7 (ArrayList) and Unit 8 (2D Array)
  • Core skill: filling a 2D array in column-major order — down each column before moving to the next — rather than the more common row-by-row order
  • Secondary skill: visiting every cell of a 2D array with a nested loop to conditionally clear it out and count how many were cleared
  • Official category: "2D Array," which is normally FRQ 4's position — 2014's actual printed order deviates from the now-standard sequence: FRQ 1 (Scramble, Methods and Control Structures), FRQ 2 (a GridWorld case-study question, excluded from this set), FRQ 3 (SeatingChart, 2D Array), and FRQ 4 (Trio, Classes). The fixed 1‑Methods/Control Structures, 2‑Classes, 3‑Array/ArrayList, 4‑2D Array ordering used on modern exams wasn't standardized until the 2019–2020 Course and Exam Description redesign.

The Setup

  • The given Student class (not modified) provides:
    • String getName() — the student's name
    • int getAbsenceCount() — how many classes that student has missed
  • SeatingChart holds one field:
    • private Student[][] seatsseats[r][c] is the student assigned to row r, column c; an empty seat is null
  • You're asked to write two members:
    • The constructor SeatingChart(List<Student> studentList, int rows, int cols) — builds seats, filling it column by column from studentList, in order, with any leftover seats set to null
    • removeAbsentStudents(int allowedAbsences) — clears out (sets to null) every seat whose student has more than allowedAbsences absences, and returns how many were cleared

Part (a): Writing the SeatingChart Constructor

The Rule, Broken Down

  1. seats needs to be created as a new rows-by-cols array of Student.
  2. Students from studentList are placed column by column — every row of column 0 is filled before column 1 even starts, and so on.
  3. Once every entry of studentList has been placed, any remaining seats are left empty (null).

Step-by-Step Approach

  1. Allocate seats as new Student[rows][cols].
  2. Track how many students from studentList have been placed so far, starting at 0.
  3. Loop over columns on the outside, and rows on the inside — this is what produces column-by-column filling instead of the more familiar row-by-row order.
  4. At each (r, c), if there's still an unplaced student left in studentList, place the next one there and advance the counter; otherwise, leave that seat alone.
  5. A freshly-created Student[][] array already starts every entry at null automatically, so seats that never get a student don't need to be set explicitly.

The Code

public SeatingChart(List<Student> studentList, int rows, int cols)
{
    seats = new Student[rows][cols];
    int index = 0;

    for (int c = 0; c < cols; c++)
    {
        for (int r = 0; r < rows; r++)
        {
            if (index < studentList.size())
            {
                seats[r][c] = studentList.get(index);
                index++;
            }
        }
    }
}

Why Each Piece Matters

  • Columns as the outer loop, rows as the inner loop — this is the one detail that makes the whole method match the problem's "filled column by column" requirement. Swapping the loop order would fill row by row instead, matching a different, more common kind of 2D-array problem but the wrong one here.
  • studentList.get(index), not studentList.get(r) or studentList.get(c) — the order students are placed in has nothing to do with which row or column they land in; it only depends on how many have been placed so far, which is exactly what the separate index counter tracks.
  • if (index < studentList.size()) with no else — Java automatically initializes every element of a new object array (like Student[][]) to null. Once index reaches studentList.size(), simply doing nothing for the remaining cells leaves them at that default null, satisfying "empty seats are null" without any extra code.
  • studentList is never modified — only .get(index) is called on it, which just reads a reference; the postcondition that studentList is unchanged holds automatically.

Tracing the Example

Using the question's own roster (10 students, in this order: Karen, Liz, Paul, Lester, Henry, Renee, Glen, Fran, David, Danny) and the call new SeatingChart(roster, 3, 4):

Column Rows filled (top to bottom) index before → after
0 Karen, Liz, Paul 0 → 3
1 Lester, Henry, Renee 3 → 6
2 Glen, Fran, David 6 → 9
3 Danny, (none), (none) 9 → 10

Column 3 only places one more student (Danny, at index = 9) before index reaches studentList.size() (10), so seats[1][3] and seats[2][3] are left at their default null. The resulting grid:

col 0 col 1 col 2 col 3
row 0 Karen Lester Glen Danny
row 1 Liz Henry Fran null
row 2 Paul Renee David null

This matches the question's expected seats grid exactly.

Common Mistakes to Avoid

  • Filling row by row (outer loop over rows, inner loop over columns). This is the far more common pattern for 2D-array problems in general, which makes it an easy default to reach for here — but it directly contradicts this problem's explicit "column by column" requirement and produces a completely different, wrong grid.
  • Indexing studentList with r or c instead of a separate running counter. Rows and columns reset for every new column/row; only a counter that keeps climbing across the entire nested loop tracks "how many students have been placed so far."
  • Manually setting leftover seats to null. Not wrong, exactly, but unnecessary — a freshly-allocated Student[][] is already full of null by default, so explicitly assigning it again is redundant code.
  • Forgetting the bounds check index < studentList.size(). Without it, once every student has been placed, the very next .get(index) call throws an IndexOutOfBoundsException instead of quietly leaving the remaining seats empty.

Part (b): Writing removeAbsentStudents

The Rule, Broken Down

  1. Visit every seat in the grid, in any order.
  2. If a seat holds a student with more than allowedAbsences absences, clear that seat to null and count it.
  3. Seats that are already empty, or hold a student within the allowed limit, are left untouched.
  4. Return the total number of seats cleared.

Step-by-Step Approach

  1. Start a counter at 0.
  2. Use a nested loop to visit every (r, c) position in seats.
  3. At each position, first check the seat isn't already null — calling a method on a null reference would crash.
  4. If it holds a student, compare that student's getAbsenceCount() to allowedAbsences.
  5. If the count is strictly greater, set that seat to null and increment the counter.
  6. After both loops finish, return the counter.

The Code

public int removeAbsentStudents(int allowedAbsences)
{
    int count = 0;

    for (int r = 0; r < seats.length; r++)
    {
        for (int c = 0; c < seats[r].length; c++)
        {
            if (seats[r][c] != null && seats[r][c].getAbsenceCount() > allowedAbsences)
            {
                seats[r][c] = null;
                count++;
            }
        }
    }

    return count;
}

Why Each Piece Matters

  • seats[r][c] != null checked first, combined with && — Java's && short-circuits, so if the seat is empty, getAbsenceCount() is never even called on it. Reversing the order of the two conditions would risk a NullPointerException on every empty seat.
  • seats.length for rows, seats[r].length for columns — this is the standard way to size a nested loop over a 2D array without hardcoding rows/cols values, and it works correctly regardless of the actual dimensions passed to the constructor.
  • Strict >, not >= — the rule is "more than allowedAbsences," so a student sitting at exactly the limit keeps their seat.
  • The loop order here doesn't matter. Unlike part (a), nothing about this method depends on visiting rows or columns in a particular sequence — every seat is independently checked and (possibly) cleared, so row-outer or column-outer both work identically.

Tracing the Example

Using the question's introCS.seats grid (absence counts shown in parentheses) and the call introCS.removeAbsentStudents(4):

Seat Absences > 4? Result
Karen 3 no stays
Lester 1 no stays
Glen 2 no stays
Danny 3 no stays
Liz 1 no stays
Henry 5 yes cleared
Fran 6 yes cleared
(null) skipped stays null
Paul 4 no (not strictly greater) stays
Renee 9 yes cleared
David 1 no stays
(null) skipped stays null

Three seats get cleared (Henry, Fran, Renee), matching the method's expected return value of 3. The resulting grid — Karen, Lester, Glen, Danny unchanged in row 0; Liz kept but Henry and Fran cleared to null in row 1; Paul and David kept but Renee cleared to null in row 2 — matches the question's "after the call" grid exactly, including Paul's seat surviving at exactly 4 absences.

Common Mistakes to Avoid

  • Skipping the null check. Calling .getAbsenceCount() on an already-empty seat throws a NullPointerException and crashes the method the first time it hits one.
  • Using >= instead of >. The example specifically includes Paul at exactly 4 absences with allowedAbsences = 4, and he must not be removed — using >= would incorrectly clear his seat and return 4 instead of 3.
  • Forgetting to increment count right alongside clearing the seat. Both actions belong together inside the same if block; doing one without the other either miscounts or leaves a seat that should be cleared still occupied.
  • Mixing up seats.length and seats[r].length. For a non-square grid (like this example's 3 rows by 4 columns), these are different numbers, and swapping them either misses seats or throws an index error.

Key Takeaways

  • "Column by column" and "row by row" are opposite loop orders for the exact same nested-loop shape — the outer loop picks which one you get, so read the problem's fill order carefully before writing either loop.
  • A freshly-allocated array of objects already defaults every entry to null; you only need to explicitly write to the entries that should hold something.
  • Whenever a 2D array might contain null, put the null check first in a && condition so short-circuit evaluation protects the method call that follows it.

Related FRQs