Data: 2022 FRQ 4
A step-by-step solution to the 2022 AP CSA FRQ 4 (Data), covering 2D array traversal, nested loops, and constrained random number generation in Java.
Filling and scanning a two-dimensional array of numbers is the focus of this AP Computer Science A free-response question — first generating random values that satisfy several rules at once, then traversing the grid column by column to check a pattern.
What This FRQ Tests
- AP CSA units: Unit 8 (2D Array) and Unit 4 (Iteration)
- Core skill: nested loops for both filling and traversing a 2D array (
grid.lengthfor rows,grid[r].lengthfor columns) - Secondary skill: generating random values within a range that also satisfy extra divisibility constraints, without skewing the odds
- Official category: "2D Array" — always FRQ 4 on the AP CSA exam
The Setup
Dataholds:private int[][] grid— a two-dimensional array ofintspublic static final int MAX— a constant upper bound (its exact value isn't given, and doesn't need to be)
- You're asked to write two methods:
repopulate()— fills every element ofgridwith a new random valuecountIncreasingCols()— returns how many columns are "increasing" top to bottom
Part (a): Writing repopulate()
The Rule, Broken Down
Every generated value must satisfy all three of these at once:
- Between
1andMAX, inclusive - Divisible by
10 - Not divisible by
100
And critically: every value that satisfies all three rules must have an equal chance of being generated — the method can't skew the odds toward some valid values over others.
Step-by-Step Approach
- Figure out how many multiples of 10 exist between 1 and
MAX— that'sMAX / 10. - Generate a random whole number from
1toMAX / 10(call it the "multiplier"), and multiply it by 10. This gives a random multiple of 10 in range, with every multiple equally likely. - Check whether that value is divisible by 100 (i.e., is it a "round hundred" like 100, 200, 300...). If so, it's invalid — throw it away and generate again.
- Repeat step 2–3 for every position in the grid, using nested loops for rows and columns.
A do-while loop is the natural tool for step 2–3: you always want to generate a candidate value at least once, and only loop back around if that candidate turns out to be invalid.
The Code
public void repopulate()
{
for (int r = 0; r < grid.length; r++)
{
for (int c = 0; c < grid[r].length; c++)
{
int value;
do
{
int multiplesOfTen = MAX / 10;
int randomMultiple = (int) (Math.random() * multiplesOfTen) + 1;
value = randomMultiple * 10;
}
while (value % 100 == 0);
grid[r][c] = value;
}
}
}
Why Each Piece Matters
Math.random()returns adoublefrom0.0up to (but never including)1.0.Math.random() * multiplesOfTenscales that into the range[0.0, multiplesOfTen).(int) (...)truncates to a whole number from0tomultiplesOfTen - 1.+ 1shifts that range to1throughmultiplesOfTen— every integer in that range is equally likely, since truncation of a uniformly-distributed double lands on each integer with equal probability.randomMultiple * 10converts "the 1st, 2nd, 3rd... multiple of ten" into the actual values10, 20, 30, ..., MAX.while (value % 100 == 0)rejects anything divisible by 100 and loops back — because rejected values are simply discarded and re-rolled (not adjusted or mapped to something else), every remaining valid value keeps its original equal probability.
Why Rejection Sampling (Not Math Tricks) Is the Safer Approach
- It would be possible to compute a formula that skips "round hundreds" directly (e.g., generating a digit 1-9 for the tens place and a separate number for the hundreds place), but that's easy to get subtly wrong — and if the arithmetic isn't careful, it can accidentally make some valid values more likely than others.
- A
do-while"regenerate until valid" loop is simple to reason about and easy to verify: every path through the loop either produces a valid value, or throws away an invalid one and tries again — nothing in between.
Common Mistakes to Avoid
- Using a
whileloop instead ofdo-while. A regularwhileloop needs the candidate value initialized before the check, and would need duplicate generation code before and inside the loop.do-whileavoids that duplication cleanly. - Checking
value % 100 == 0before multiplying by 10. The check has to run on the final candidate value, not on the raw random multiplier. - Off-by-one on the multiplier's range. Forgetting the
+ 1would allow a multiplier of0, producing a value of0— outside the required1toMAXrange. - Trying to "fix" an invalid value instead of regenerating it (e.g., adding 10 to any round-hundred result). This breaks the "equal chance" requirement, since it makes the next multiple of ten twice as likely to appear.
Part (b): Writing countIncreasingCols()
The Rule, Broken Down
- A column is "increasing" if every row after the first is greater than or equal to the row directly above it, in that same column.
- A column with only one row automatically counts as increasing (there's nothing to compare it against).
- Count how many of the grid's columns meet this rule.
Step-by-Step Approach
- Loop over every column index.
- For each column, assume it's increasing until proven otherwise (
boolean increasing = true). - Loop down that column, starting at row
1(not row0— there's no row above row0to compare against). - At each row, compare the current value to the value directly above it. If it's smaller, the column isn't increasing — flip the flag to
false(and let the inner loop keep running; there's no need to stop early). - After the inner loop finishes, if the flag is still
true, increment the count. - After the outer loop finishes, return the count.
The Code
public int countIncreasingCols()
{
int count = 0;
for (int c = 0; c < grid[0].length; c++)
{
boolean increasing = true;
for (int r = 1; r < grid.length; r++)
{
if (grid[r][c] < grid[r - 1][c])
{
increasing = false;
}
}
if (increasing)
{
count++;
}
}
return count;
}
Why Each Piece Matters
grid[0].lengthgives the number of columns — the length of any one row. (grid.lengthgives the number of rows instead — mixing these up is one of the most common 2D array bugs.)- The inner loop starts at
r = 1, not0. Row 0 has no row above it, so there's nothing valid to compare it to. increasingstartstrueand only ever flips tofalse. This correctly handles the "a single-row column is automatically increasing" rule — if the inner loop finds zero violations (including the case where there's only one row and the loop body never runs at all), the flag staystrue.grid[r][c] < grid[r - 1][c]— note both indices share the same columnc; only the row index changes between the current and previous positions.
Tracing the Example
Using the first example from the question:
| Column | Values (top to bottom) | Increasing? |
|---|---|---|
| 0 | 10, 20, 30 | yes — never decreases |
| 1 | 50, 40, 50 | no — drops from 50 to 40 |
| 2 | 40, 20, 30 | no — drops from 40 to 20 |
Result: count ends at 1, matching the question's expected answer.
Common Mistakes to Avoid
- Swapping
grid.lengthandgrid[0].length. This is the classic 2D array bug — always double check which one represents rows and which represents columns. - Starting the inner loop at
r = 0and comparinggrid[0][c]togrid[-1][c]— this doesn't just give the wrong answer, it throws anArrayIndexOutOfBoundsException. - Using
breakto exit the inner loop early after finding one violation. It's not wrong, exactly (the answer would still be correct), but it adds unnecessary complexity — letting the loop finish naturally is simpler to write correctly under exam time pressure. - Checking the wrong direction —
grid[r][c] > grid[r-1][c]instead of<. The rule is "greater than or equal to the row above," so the violation condition is strictly "less than."
Key Takeaways
- 2D array traversal almost always means nested loops — outer loop for one dimension, inner loop for the other — and getting
grid.lengthvs.grid[0].lengthright is essential. - "Equal chance of being generated" is a strong hint toward a rejection-sampling
do-whileloop, not a clever formula. - A boolean flag that starts
trueand can only flip tofalseis the standard pattern for "check that nothing in this sequence violates a rule."