CompSci.rocks
FRQcsapa

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

  • LightBoard holds:
    • private boolean[][] lights — a 2D array where true means a light is on, false means off
  • You're asked to write:
    • The constructor LightBoard(int numRows, int numCols) — builds lights and 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

Part (a): Writing the Constructor

The Rule, Broken Down

  1. Allocate lights with the given number of rows and columns.
  2. Every single cell, independently of every other cell, must end up true with exactly a 40% chance (and false the other 60%).

Step-by-Step Approach

  1. Create the array: lights = new boolean[numRows][numCols].
  2. Loop over every row, and inside that, every column.
  3. For each cell, generate a random double with Math.random() (always in [0.0, 1.0)) and compare it to 0.4.
  4. Store the result of that comparison directly — it's true exactly 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 make Math.random() < 0.4 true — 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.4 stores the boolean result of the comparison directly — there's no need for an if/else that separately assigns true or false, since the comparison itself already produces a boolean value.
  • Nested loops are required, not a single loop, because lights is 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.4 or Math.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 * numCols cells needs its own independent random check.

Part (b): Writing evaluateLight(int row, int col)

The Rule, Broken Down

  1. If the light at (row, col) is currently on: return false if the number of lights on in that same column — counting the current light itself — is even.
  2. If the light is currently off: return true if the number of lights on in that column is divisible by three.
  3. In every other case, just return the light's current, unchanged status.

Step-by-Step Approach

  1. Count how many lights are on somewhere in column col, by looping down every row of that one column.
  2. Check the current light's own status first, since the rule branches completely differently depending on whether it's on or off.
  3. If it's on and the count is even, return false.
  4. Otherwise, if it's off and the count is divisible by 3, return true.
  5. 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 holding col fixed — this is exactly how you traverse a single column of a 2D array, since lights[r][col] only ever changes the row index.
  • lights.length gives the number of rows, so the loop covers every row — including row itself, 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 final else on 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] is true, column 3's count is 4 (even) → on and even → returns false, matching the question.
  • sim.evaluateLight(6, 0): lights[6][0] is false, 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] is false, column 1's count is 2 (not divisible by 3) → falls through to the final else → returns lights[4][1], which is false, matching the question.
  • sim.evaluateLight(5, 4): lights[5][4] is true, column 4's count is 5 (odd, not even) → falls through to the final else → returns lights[5][4], which is true, matching the question.

Common Mistakes to Avoid

  • Swapping lights.length for lights[row].length (or vice versa) — here they matter for different reasons: lights.length correctly gives the number of rows to scan down a column, while lights[row].length would 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 == 0 as 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 to Math.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 0 to lights.length - 1 — the mirror image of the row-traversal pattern from 2022's Data.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.

Related FRQs