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
Studentclass (not modified) provides:String getName()— the student's nameint getAbsenceCount()— how many classes that student has missed
SeatingChartholds one field:private Student[][] seats—seats[r][c]is the student assigned to rowr, columnc; an empty seat isnull
- You're asked to write two members:
- The constructor
SeatingChart(List<Student> studentList, int rows, int cols)— buildsseats, filling it column by column fromstudentList, in order, with any leftover seats set tonull removeAbsentStudents(int allowedAbsences)— clears out (sets tonull) every seat whose student has more thanallowedAbsencesabsences, and returns how many were cleared
- The constructor
Part (a): Writing the SeatingChart Constructor
The Rule, Broken Down
seatsneeds to be created as a newrows-by-colsarray ofStudent.- Students from
studentListare placed column by column — every row of column0is filled before column1even starts, and so on. - Once every entry of
studentListhas been placed, any remaining seats are left empty (null).
Step-by-Step Approach
- Allocate
seatsasnew Student[rows][cols]. - Track how many students from
studentListhave been placed so far, starting at0. - 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.
- At each
(r, c), if there's still an unplaced student left instudentList, place the next one there and advance the counter; otherwise, leave that seat alone. - A freshly-created
Student[][]array already starts every entry atnullautomatically, 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), notstudentList.get(r)orstudentList.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 separateindexcounter tracks.if (index < studentList.size())with noelse— Java automatically initializes every element of a new object array (likeStudent[][]) tonull. OnceindexreachesstudentList.size(), simply doing nothing for the remaining cells leaves them at that defaultnull, satisfying "empty seats arenull" without any extra code.studentListis never modified — only.get(index)is called on it, which just reads a reference; the postcondition thatstudentListis 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
studentListwithrorcinstead 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-allocatedStudent[][]is already full ofnullby 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 anIndexOutOfBoundsExceptioninstead of quietly leaving the remaining seats empty.
Part (b): Writing removeAbsentStudents
The Rule, Broken Down
- Visit every seat in the grid, in any order.
- If a seat holds a student with more than
allowedAbsencesabsences, clear that seat tonulland count it. - Seats that are already empty, or hold a student within the allowed limit, are left untouched.
- Return the total number of seats cleared.
Step-by-Step Approach
- Start a counter at
0. - Use a nested loop to visit every
(r, c)position inseats. - At each position, first check the seat isn't already
null— calling a method on anullreference would crash. - If it holds a student, compare that student's
getAbsenceCount()toallowedAbsences. - If the count is strictly greater, set that seat to
nulland increment the counter. - 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] != nullchecked 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 aNullPointerExceptionon every empty seat.seats.lengthfor rows,seats[r].lengthfor columns — this is the standard way to size a nested loop over a 2D array without hardcodingrows/colsvalues, and it works correctly regardless of the actual dimensions passed to the constructor.- Strict
>, not>=— the rule is "more thanallowedAbsences," 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
nullcheck. Calling.getAbsenceCount()on an already-empty seat throws aNullPointerExceptionand crashes the method the first time it hits one. - Using
>=instead of>. The example specifically includes Paul at exactly 4 absences withallowedAbsences = 4, and he must not be removed — using>=would incorrectly clear his seat and return4instead of3. - Forgetting to increment
countright alongside clearing the seat. Both actions belong together inside the sameifblock; doing one without the other either miscounts or leaves a seat that should be cleared still occupied. - Mixing up
seats.lengthandseats[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 thenullcheck first in a&&condition so short-circuit evaluation protects the method call that follows it.