RouteCipher: 2011 FRQ 4
A step-by-step solution to the 2011 AP CSA FRQ 4 (RouteCipher), covering filling a 2D array in row-major order and repeatedly encrypting fixed-size chunks of a longer message in Java.
Turning a message into a coded jumble by reading it back out in a different order is the trick behind this AP Computer Science A free-response question — you fill a 2D grid one row at a time, then chain that process together to handle a message longer than a single grid can hold.
What This FRQ Tests
- AP CSA units: Unit 8 (2D Array) and Unit 4 (Iteration)
- Core skill: filling a 2D array in row-major order using a single running index computed from row and column
- Secondary skill: repeatedly processing fixed-size chunks of a longer string until the whole thing is consumed, handling a shorter final chunk
- Official category: "2D Array" — always FRQ 4 on the AP CSA exam
The Setup
- The given
RouteCipherclass has:private String[][] letterBlock— instantiated in the constructorprivate int numRowsandprivate int numColsprivate String encryptBlock()— already implemented; extracts the encrypted text fromletterBlockin column-major order (assume it's correct — you're told not to reimplement it)
- You're asked to write two methods:
private void fillBlock(String str)— fillsletterBlockin row-major orderpublic String encryptMessage(String message)— encrypts a message of any length by repeatedly callingfillBlockandencryptBlockon consecutive chunks
Part (a): Writing fillBlock(String str)
The Rule, Broken Down
- Fill
letterBlockin row-major order: the first row left-to-right, then the second row left-to-right, and so on. - Each cell gets a one-character substring pulled from
str. - If
stris too short to fill every cell, pad the remaining cells with"A". - If
stris too long, ignore whatever characters don't fit.
Step-by-Step Approach
- Loop over every row
r. - Within each row, loop over every column
c. - Compute the row-major position
k = r * numCols + c— this isstr's matching index for this cell. - If
kis still a valid index intostr, pull out that one character as aString. - Otherwise,
strhas run out — fill the cell with"A"instead.
The Code
private void fillBlock(String str)
{
for (int r = 0; r < numRows; r++)
{
for (int c = 0; c < numCols; c++)
{
int k = r * numCols + c;
if (k < str.length())
{
letterBlock[r][c] = str.substring(k, k + 1);
}
else
{
letterBlock[r][c] = "A";
}
}
}
}
Why Each Piece Matters
k = r * numCols + cis the standard formula for converting a 2D row/column position into the equivalent 1D position in row-major order: every complete row before this one contributesnumColscharacters, pluscmore within the current row.str.substring(k, k + 1)— the exact expression the question itself suggests — pulls out a single character as a one-characterString, matching the declared typeString[][] letterBlock(acharwouldn't fit that array type).- The
k < str.length()check happens before callingsubstring, since callingsubstring(k, k + 1)withkpast the end ofstrwould throw an exception instead of just being treated as "ran out of characters."
Tracing the Example
With letterBlock sized 3 rows by 5 columns and str = "Meet at noon" (length 12, so 3 cells short of the full 15):
| Row | k values |
Characters | Result |
|---|---|---|---|
| 0 | 0-4 | M, e, e, t, (space) |
"M" "e" "e" "t" " " |
| 1 | 5-9 | a, t, (space), n, o |
"a" "t" " " "n" "o" |
| 2 | 10-14 | o, n, (out of range) ×3 |
"o" "n" "A" "A" "A" |
This matches the question's table exactly, including the three padded "A" cells at the end.
With the longer str = "Meet at midnight" (length 16, one character more than the 15 available cells), the same formula fills every cell from the string and simply never reaches the trailing "t" — row 2 comes out "d" "n" "i" "g" "h", exactly as the question shows, with the final character silently ignored.
Common Mistakes to Avoid
- Swapping the row/column formula to
c * numRows + r, which fills the array in column-major order instead of row-major. - Using
k <= str.length()instead of<, which would try to read one character past the end ofstron the boundary case. - Writing
'A'(achar) instead of"A"(aString) for the padding —letterBlockis aString[][], so the padding value has to be aStringliteral. - Assuming leftover cells from a previous call stay untouched. They don't need special handling here — the loop always runs over every one of the
numRows * numColscells on every call, so old data is naturally overwritten either with a new character or a fresh"A".
Part (b): Writing encryptMessage(String message)
The Rule, Broken Down
- If
messageis empty, return the empty string immediately. - Otherwise, split
messageinto consecutive, non-overlapping chunks, each up tonumRows * numColscharacters long. - For each chunk — the last one may be shorter — call
fillBlock, thenencryptBlock, and append the result to a running total. - Once the whole message has been consumed, return the accumulated result.
Step-by-Step Approach
- Compute the block size:
numRows * numCols. - Track how much of
messagehas been consumed so far, starting at0. - While there's more of
messageleft: figure out where the current chunk ends (either a full block size ahead, or the end ofmessage, whichever comes first), pull out that chunk, callfillBlockon it, callencryptBlock, and append its result. - Advance the "consumed so far" position by a full block size each time — even on the final, partial chunk — since the loop's own condition will correctly stop it from running again.
- Return the accumulated result.
The Code
public String encryptMessage(String message)
{
String result = "";
int blockSize = numRows * numCols;
int start = 0;
while (start < message.length())
{
int end = start + blockSize;
if (end > message.length())
{
end = message.length();
}
String piece = message.substring(start, end);
fillBlock(piece);
result = result + encryptBlock();
start = start + blockSize;
}
return result;
}
Why Each Piece Matters
start < message.length()as the loop condition, checked before anything else runs, is exactly what makes an emptymessagecorrectly return""— the loop body never executes at all, soresultstays empty.- Clamping
enddown tomessage.length()when a full block would overshoot lets the very last chunk be shorter than a complete block, sincesubstringcan't read past the end of the string. - Advancing
startby the fullblockSize, not by the chunk's actual length, still works correctly on the final partial chunk — the loop conditionstart < message.length()will already be false the next time around either way. - Calling
fillBlockandencryptBlockexactly as given, instead of re-deriving their logic, is explicitly what the question asks for — it states that solutions which reimplement either method's functionality won't receive full credit.
Tracing the Example
Using the question's own example — letterBlock sized 2 rows by 3 columns (blockSize = 6) and encryptMessage("Meet at midnight") (length 16):
| Chunk | piece |
letterBlock after fillBlock |
encryptBlock() returns |
Running result |
|---|---|---|---|---|
1st (start=0) |
"Meet a" |
row 0: "M" "e" "e", row 1: "t" " " "a" |
"Mte ea" |
"Mte ea" |
2nd (start=6) |
"t midn" |
row 0: "t" " " "m", row 1: "i" "d" "n" |
"ti dmn" |
"Mte eati dmn" |
3rd (start=12) |
"ight" (padded to "ightAA") |
row 0: "i" "g" "h", row 1: "t" "A" "A" |
"itgAhA" |
"Mte eati dmnitgAhA" |
After the third chunk, start becomes 18, which is no longer less than message.length() (16), so the loop ends. The final returned string, "Mte eati dmnitgAhA", matches the question's own worked example exactly.
Common Mistakes to Avoid
- Advancing
startby the chunk's actual (possibly shorter) length instead of the fullblockSize. It's usually harmless since it only affects the last iteration, but it conflates two different quantities and is easier to get wrong. - Using a
do-whileloop instead of awhileloop, which would run once even for an emptymessageand fail the "return the empty string" requirement. - Overwriting
resultinstead of appending to it (result = encryptBlock();instead ofresult = result + encryptBlock();), which keeps only the last chunk's encrypted text. - Calling
encryptBlock()beforefillBlock()for a given chunk, which would encrypt stale data left over from the previous chunk instead of the current one.
Key Takeaways
- Converting between a 2D row/column position and a single running index is one formula:
row * numColumns + column. - Processing a long input in fixed-size chunks is a
whileloop pattern: take the next chunk (clamped to whatever remains), process it, advance by the full chunk size, and repeat. - When a problem says reimplementing a given method "will not receive full credit," that's telling you exactly which piece to treat as an already-solved building block rather than something to rewrite yourself.