RandomStringChooser / RandomLetterChooser: 2016 FRQ 1
A step-by-step solution to the 2016 AP CSA FRQ 1 (RandomStringChooser), covering building a class from scratch around an ArrayList and extending it with inheritance in Java.
Picking random items from a shrinking pool, without ever repeating one, is the core challenge behind this AP Computer Science A free-response question — and it asks you to build that behavior into a class completely from scratch before extending it with a subclass.
What This FRQ Tests
- AP CSA units: Unit 5 (Writing Classes) and Unit 9 (Inheritance)
- Core skill: designing a class's private state and every one of its methods from a plain-English description, with no skeleton provided
- Secondary skill: extending that class with
extendsand calling its constructor throughsuper(...) - Official category: Classes — this question asks you to write an entire class in part (a), then extend it in part (b). It's printed as FRQ 1 in the 2016 booklet; under the modern fixed ordering (adopted starting with the 2019–2020 course redesign), Classes is always FRQ 2, so this year's order doesn't match the current pattern.
The Setup
- A
RandomStringChooserobject is constructed from an array of non-nullStringvalues. When it's first built, every string is considered "available." getNext()returns a randomly chosen string from whatever's still available, and that string is never returned again by a later call. Once nothing is left,getNext()returns"NONE".- Neither the constructor nor any method may alter the array passed into the constructor — copying its contents into your own storage is explicitly allowed (and is the way to satisfy that rule).
RandomLetterChooseris given as a subclass ofRandomStringChooser. It comes with astatichelper,getSingleLetters(String str), already implemented (not shown) — it returns an array of one-letter strings, one per character ofstr(e.g.,getSingleLetters("cat")returns{ "c", "a", "t" }).- You're asked to write two things: the entire
RandomStringChooserclass (part a), and the constructor forRandomLetterChooser(part b).
Part (a): Writing the RandomStringChooser Class
The Rule, Broken Down
- Store the given strings somewhere private, without ever exposing or altering the original array.
- Each call to
getNext()must return a string that hasn't been returned before. - Once a string is returned, it can never be chosen again.
- If nothing is left to choose from, return
"NONE"instead of crashing or returningnull.
Step-by-Step Approach
- Declare one private field: an
ArrayList<String>to hold whatever strings are still available. - In the constructor, create that
ArrayListand copy every element of the given array into it, one at a time. Copying (rather than storing a reference to the array itself) is what keeps the original array untouched no matter whatgetNext()later does. - In
getNext(), check first whether anything is left. If the list is empty, return"NONE"immediately. - Otherwise, generate a random valid index into the list.
- Remove the string at that index and return it in the same step — removing it is exactly what makes it unavailable for next time.
The Code
public class RandomStringChooser
{
private ArrayList<String> available;
public RandomStringChooser(String[] wordArray)
{
available = new ArrayList<String>();
for (int i = 0; i < wordArray.length; i++)
{
available.add(wordArray[i]);
}
}
public String getNext()
{
if (available.size() == 0)
{
return "NONE";
}
int index = (int) (Math.random() * available.size());
return available.remove(index);
}
}
Why Each Piece Matters
- Copying into a new
ArrayListin the constructor — ifavailableinstead pointed directly atwordArray(or wrapped it without copying), removing an item fromavailablecould end up changing the caller's original array, which the problem explicitly forbids. - Checking
available.size() == 0before generating a random index —Math.random() * available.size()would just evaluate to0on an empty list, but then calling.remove(0)on an emptyArrayListthrows an exception. Checking first avoids that entirely. (int) (Math.random() * available.size())—Math.random()returns adoublefrom0.0up to (but never reaching)1.0. Multiplying by the current size and truncating with(int)scales that into a valid index from0tosize() - 1— and it has to be recomputed against the current size every call, since the pool shrinks after each removal.available.remove(index)—ArrayList'sremove(int index)conveniently does two jobs in one call: it takes the string out of the list (so it's no longer available) and returns the value that was removed, which is exactly the stringgetNext()needs to hand back.
Tracing the Example
Using the question's own example — wordArray = {"wheels", "on", "the", "bus"}, then six calls to getNext():
Since Math.random() is different every run, the exact strings returned aren't fixed — but the question shows one possible output, bus the wheels on NONE NONE, and this table reproduces it with one valid sequence of random picks:
| Call | List before | Index chosen | Returned | List after |
|---|---|---|---|---|
| 1 | [wheels, on, the, bus] |
3 | bus |
[wheels, on, the] |
| 2 | [wheels, on, the] |
2 | the |
[wheels, on] |
| 3 | [wheels, on] |
0 | wheels |
[on] |
| 4 | [on] |
0 | on |
[] |
| 5 | [] |
— | NONE |
[] |
| 6 | [] |
— | NONE |
[] |
That reproduces bus the wheels on NONE NONE exactly — and no matter which indices happen to be chosen on a given run, the list only ever shrinks, so the code is guaranteed to eventually run out and start returning "NONE".
Common Mistakes to Avoid
- Storing the constructor parameter directly (
available = wordArray;as an array, or wrapping it without copying). Any later removal risks corrupting data the caller still owns — the problem is explicit that this isn't allowed. - Generating the random index before checking whether the list is empty.
available.size()being0doesn't crash the random-number math, but the very next line —remove(index)— will, since there's nothing at any index of an empty list. - Forgetting to re-read
available.size()on every call. Caching the original array length and reusing it would eventually generate an index that no longer exists once strings have been removed. - Using an
int[]of "used" flags alongside the original array instead of actually removing anything. It can be made to work, but it's more bookkeeping than necessary — removing the chosen string is simpler and automatically keepsavailable.size()accurate.
Part (b): Writing the RandomLetterChooser Constructor
The Rule, Broken Down
RandomLetterChooserextendsRandomStringChooser— it inheritsgetNext()for free, and never needs to override it.- The only thing left to write is its constructor, which builds a chooser out of the individual letters of a given string, not the whole string itself.
getSingleLetters(str)already does the work of splittingstrinto single-letter strings — the instructions are explicit that you must use it to get full credit, not reimplement it.
Step-by-Step Approach
- Recognize that
RandomStringChooser's constructor already does exactly what's needed — it takes aString[]and marks every element available — soRandomLetterChooserjust needs to hand it the right array. - Call
getSingleLetters(str)to turnstrinto an array of one-letter strings. - Pass that array straight into the parent constructor with
super(...).
The Code
public RandomLetterChooser(String str)
{
super(getSingleLetters(str));
}
Why Each Piece Matters
super(...)must be the very first statement in the constructor. Java requires this for any explicit superclass constructor call, and here it's also the only way to initialize the privateavailablefield declared insideRandomStringChooser— a subclass can never touch a superclass'sprivatefields directly, no matter what.- Calling
getSingleLetters(str)instead of writing a manual letter-splitting loop — the method isstaticand already fully implemented, so calling it both satisfies the "you must usegetSingleLettersappropriately" requirement and avoids duplicating logic that already exists. - No new fields or methods are needed at all. Once the parent class is constructed with the right starting array,
getNext()— inherited unchanged fromRandomStringChooser— already behaves correctly for letters instead of whole words.
Tracing the Example
Using the question's own example — new RandomLetterChooser("cat"), then four calls to getNext() printed back-to-back with no separator:
getSingleLetters("cat")returns{ "c", "a", "t" }(given).super({ "c", "a", "t" })runsRandomStringChooser's constructor, soavailablestarts as[c, a, t].
One valid sequence of random picks that reproduces the question's own sample output, actNONE:
| Call | List before | Index chosen | Returned | List after |
|---|---|---|---|---|
| 1 | [c, a, t] |
1 | a |
[c, t] |
| 2 | [c, t] |
0 | c |
[t] |
| 3 | [t] |
0 | t |
[] |
| 4 | [] |
— | NONE |
[] |
Concatenated with no spaces (since the loop prints letterChooser.getNext() alone), that's "a" + "c" + "t" + "NONE" — exactly actNONE, matching the question's example.
Common Mistakes to Avoid
- Putting any code before
super(...). Even a harmless-looking line above it fails to compile — the superclass constructor call has to come first, unconditionally. - Reimplementing letter-splitting by hand (e.g., looping over
str.length()and building single-character strings) instead of callinggetSingleLetters. It might produce the same array, but the rubric specifically requires using the given method. - Trying to call
getSingleLettersas if it needed an object (likethis.getSingleLetters(str)in a context wherethisisn't valid yet). It'sstatic, so it's called directly by name, which also happens to be necessary sincethisisn't usable until aftersuper(...)has run.
Key Takeaways
- When a subclass needs to initialize private state it inherits but can't touch directly,
super(...)is the only path in — and it always has to be the first line of the constructor. - Designing a class's fields from scratch starts with asking what needs to persist between method calls — here, exactly one
ArrayListof "what's still available" is enough to support the entire class. - A method that both removes an item from a collection and returns that item (like
ArrayList.remove(int index)) is a natural fit anywhere "pick one and take it out of the pool" shows up.