FRQ
› csapa
LightBoard: 2019 FRQ 4
A step-by-step solution to the 2019 AP CSA FRQ 4 (LightBoard), covering random 2D array initialization at a fixed probability and column-wise traversal in Java.
A grid of on/off lights is a simple enough idea, but this AP Computer Science A free-response question packs two classic 2D array skills into it — randomly filling a grid so a specific percentage of cells end up on, then scanning down individual columns to answer a rule that depends on the whole column at once.
What This FRQ Tests
- AP CSA units: Unit 8 (2D Array), Unit 4 (Iteration)
- Core skill: nested loops to fill every cell of a 2D array with an independently random value
- Secondary skill: scanning an entire column of a 2D array to compute a count before deciding what a single cell in that column should return
- Official category: "2D Array" — always FRQ 4 on the AP CSA exam
The Setup
LightBoardholds:private boolean[][] lights— a 2D array wheretruemeans a light is on,falsemeans off
- You're asked to write:
- The constructor
LightBoard(int numRows, int numCols)— buildslightsand sets each light on with 40% probability evaluateLight(int row, int col)— computes and returns a light's status based on how many lights are on elsewhere in its column
- The constructor
Part (a): Writing the Constructor
The Rule, Broken Down
- Allocate
lightswith the given number of rows and columns. - Every single cell, independently of every other cell, must end up
truewith exactly a 40% chance (andfalsethe other 60%).
Step-by-Step Approach
- Create the array:
lights = new boolean[numRows][numCols]. - Loop over every row, and inside that, every column.
- For each cell, generate a random
doublewithMath.random()(always in[0.0, 1.0)) and compare it to0.4. - Store the result of that comparison directly — it's
trueexactly when the random value fell in the lowest 40% of the range.
The Code
public LightBoard(int numRows, int numCols)
{
lights = new boolean[numRows][numCols];
for (int r = 0; r < numRows; r++)
{
for (int c = 0; c < numCols; c++)
{
lights[r][c] = Math.random() < 0.4;
}
}
}
Why Each Piece Matters
Math.random()returns a value uniformly distributed across[0.0, 1.0)— every sub-range of that interval is exactly as likely as any other equally-sized sub-range.- The interval
[0.0, 0.4)— the values that makeMath.random() < 0.4true — takes up exactly 40% of the full[0.0, 1.0)range, so the comparison is true with exactly 40% probability, independently for every cell. lights[r][c] = Math.random() < 0.4stores the boolean result of the comparison directly — there's no need for anif/elsethat separately assignstrueorfalse, since the comparison itself already produces a boolean value.- Nested loops are required, not a single loop, because
lightsis two-dimensional — the outer loop walks rows, the inner loop walks every column within that row.
Common Mistakes to Avoid
- Comparing against the wrong side, e.g.
Math.random() > 0.4orMath.random() < 0.6— either of these gives a 60% chance of being on, not 40%. - Writing an unnecessary
if (Math.random() < 0.4) { lights[r][c] = true; } else { lights[r][c] = false; }. This works, but it's more code than the direct boolean assignment for no benefit. - Using only one loop, or reusing the same index variable for both dimensions. Every one of the
numRows * numColscells needs its own independent random check.
Part (b): Writing evaluateLight(int row, int col)
The Rule, Broken Down
- If the light at
(row, col)is currently on: returnfalseif the number of lights on in that same column — counting the current light itself — is even. - If the light is currently off: return
trueif the number of lights on in that column is divisible by three. - In every other case, just return the light's current, unchanged status.
Step-by-Step Approach
- Count how many lights are on somewhere in column
col, by looping down every row of that one column. - Check the current light's own status first, since the rule branches completely differently depending on whether it's on or off.
- If it's on and the count is even, return
false. - Otherwise, if it's off and the count is divisible by 3, return
true. - Otherwise, return
lights[row][col]itself — its current, unmodified status.
The Code
public boolean evaluateLight(int row, int col)
{
int count = 0;
for (int r = 0; r < lights.length; r++)
{
if (lights[r][col])
{
count++;
}
}
if (lights[row][col] && count % 2 == 0)
{
return false;
}
else if (!lights[row][col] && count % 3 == 0)
{
return true;
}
else
{
return lights[row][col];
}
}
Why Each Piece Matters
for (int r = 0; r < lights.length; r++)walks down every row while holdingcolfixed — this is exactly how you traverse a single column of a 2D array, sincelights[r][col]only ever changes the row index.lights.lengthgives the number of rows, so the loop covers every row — includingrowitself, which is exactly what the rule means by "including the current light."- The
&&in each condition requires both the on/off status and the count check to agree before returning early — a light that's on with an odd count, or off with a count not divisible by 3, is meant to fall through to the finalelseon purpose. - The final
else return lights[row][col]handles rule 3 ("otherwise, return the light's current status") without needing to store or recompute anything extra — the array already holds that value.
Tracing the Example
Using the 7×5 board from the question (true = on, false = off), with column counts computed by scanning each column top to bottom:
| Column | Values, rows 0-6 (top to bottom) | Count on |
|---|---|---|
| 0 | T, T, T, T, T, T, F | 6 |
| 1 | T, F, F, F, F, T, F | 2 |
| 3 | T, T, T, F, F, T, F | 4 |
| 4 | T, F, T, T, T, T, F | 5 |
Checking each sample call from the question against the code:
sim.evaluateLight(0, 3):lights[0][3]istrue, column 3's count is 4 (even) → on and even → returns false, matching the question.sim.evaluateLight(6, 0):lights[6][0]isfalse, column 0's count is 6 (divisible by 3) → off and divisible by 3 → returns true, matching the question.sim.evaluateLight(4, 1):lights[4][1]isfalse, column 1's count is 2 (not divisible by 3) → falls through to the finalelse→ returnslights[4][1], which is false, matching the question.sim.evaluateLight(5, 4):lights[5][4]istrue, column 4's count is 5 (odd, not even) → falls through to the finalelse→ returnslights[5][4], which is true, matching the question.
Common Mistakes to Avoid
- Swapping
lights.lengthforlights[row].length(or vice versa) — here they matter for different reasons:lights.lengthcorrectly gives the number of rows to scan down a column, whilelights[row].lengthwould give the number of columns, the wrong dimension entirely for this count. - Forgetting to count the current light itself. The rule explicitly says "including the current light," so the counting loop must run over every row without skipping
row. - Combining the on/off check and the count check with
||instead of&&. This would make rules 1 and 2 fire in situations where they shouldn't. - Checking a count condition without first confirming on/off status — e.g., testing
count % 2 == 0as the very first check, which would incorrectly apply rule 1's logic to a light that's actually off.
Key Takeaways
static double random()returning a value in[0.0, 1.0)means "X% probability" almost always translates directly toMath.random() < X / 100.0, or the literal decimal form of X%.- Traversing a single column of a 2D array means holding the column index fixed and looping the row index from
0tolights.length - 1— the mirror image of the row-traversal pattern from 2022'sData.countIncreasingCols(). - When a rule branches into multiple distinct cases ("if on, do X; if off, do Y; otherwise Z"), check the mutually exclusive condition first, then the secondary condition within each branch, and always keep an explicit final "otherwise" case.