WordList: 2004 FRQ 1
A step-by-step solution to the 2004 AP CSA FRQ 1 (WordList), covering looping through an ArrayList to count and remove elements by length in Java.
A list of words stored in an ArrayList gets counted and filtered by length in this AP Computer Science A free-response question — first tallying how many words match a given length, then removing all of them while keeping everything else exactly where it was.
What This FRQ Tests
- AP CSA units: Unit 6 (Array) and Unit 7 (ArrayList)
- Core skill: looping through an
ArrayListby index to count matching elements - Secondary skill: removing selected elements from an
ArrayListmid-loop without skipping any or disturbing the order of what's left - Official category: "Array/ArrayList," which on the 2004 exam was FRQ 1 — the fixed FRQ 1–4 category order used in more recent years (Methods and Control Structures, Classes, Array/ArrayList, 2D Array, always in that sequence) wasn't standardized until the 2019–2020 Course and Exam Description redesign. 2004's actual printed order was WordList (Array/ArrayList), the Pet/Cat/LoudDog/Kennel hierarchy (Classes), a Marine Biology Simulation question (skipped on this site — see the note below), and Robot (Methods and Control Structures).
The Setup
- The
WordListclass has one field:private ArrayList myList;— a list ofStrings made up of letters. - Notice
myListis declared as a plainArrayList, with no<String>type in sight. That's typical of AP CSA code from this era, before generics became the standard way to declare a collection's element type. It meansmyList.get(i)hands back a plainObject, which has to be cast toStringbefore anyStringmethod can be called on it. - You're asked to write two methods:
numWordsOfLength(int len)— counts how many words are exactlylenletters longremoveWordsOfLength(int len)— removes every word that's exactlylenletters long, keeping the rest in their original order
Part (a): Writing numWordsOfLength(int len)
The Rule, Broken Down
numWordsOfLength returns the number of words in myList whose length is exactly len — not "at least," not "at most," exactly.
Step-by-Step Approach
- Start a counter at
0. - Loop over every index of
myList. - Pull out that element and cast it to
String. - If its length equals
len, increment the counter. - After the loop, return the counter.
The Code
public int numWordsOfLength(int len)
{
int count = 0;
for (int i = 0; i < myList.size(); i++)
{
String word = (String) myList.get(i);
if (word.length() == len)
{
count++;
}
}
return count;
}
Why Each Piece Matters
myList.size(), not a hardcoded number — the loop bound has to match however many words happen to be stored, which can change from oneWordListto another.(String) myList.get(i)— sincemyListisn't declared with a generic type,get(i)returns typeObject. Calling.length()directly on thatObjectwouldn't compile; the cast tells the compiler "trust me, this is really aString."==, not>=or<=— the rule is "exactlylenletters," so anything other than an exact match should be skipped.
Tracing the Example
Using the question's own data — animals.myList contains ["cat", "mouse", "frog", "dog", "dog"]:
| Word | Length | Matches len = 4? |
Matches len = 3? |
Matches len = 2? |
|---|---|---|---|---|
"cat" |
3 | no | yes | no |
"mouse" |
5 | no | no | no |
"frog" |
4 | yes | no | no |
"dog" |
3 | no | yes | no |
"dog" |
3 | no | yes | no |
animals.numWordsOfLength(4)→ 1 (only"frog")animals.numWordsOfLength(3)→ 3 ("cat"and both"dog"s)animals.numWordsOfLength(2)→ 0 (nothing is 2 letters long)
All three match the question's table exactly.
Common Mistakes to Avoid
- Forgetting the
(String)cast. Without it,word.length()won't compile at all, sinceObjecthas nolength()method. - Using
>=or<=instead of==. This method counts exact-length matches only. - Looping with
i <= myList.size(). That reads one index past the end of the list on the final iteration, throwing anIndexOutOfBoundsException.
Part (b): Writing removeWordsOfLength(int len)
The Rule, Broken Down
- Remove every word from
myListthat's exactlylenletters long. - Leave the relative order of whatever remains unchanged.
Step-by-Step Approach
- Loop backward, starting at the last index (
myList.size() - 1) and going down to0. - Cast each element to
Stringand check its length. - If it matches
len, remove it at that index. - Once the loop finishes, every matching word is gone.
Looping backward is the key idea here: when an element is removed from an ArrayList, every element after it shifts down by one index to fill the gap. Removing while walking forward risks skipping the element that just slid into the spot you already passed. Walking backward avoids the problem entirely, since every index still to be checked is before the removal point and is never touched by the shift.
The Code
public void removeWordsOfLength(int len)
{
for (int i = myList.size() - 1; i >= 0; i--)
{
String word = (String) myList.get(i);
if (word.length() == len)
{
myList.remove(i);
}
}
}
Why Each Piece Matters
i = myList.size() - 1, counting down — this is what makes it safe to remove elements mid-loop; every remaining index still points at an element that hasn't been checked yet, regardless of how many removals already happened at higher indices.myList.remove(i)— removes by position, not by value, which is exactly what's needed once the matching index has already been found.- No
returnvalue — this method's signature isvoid; it mutatesmyListdirectly instead of building and returning a new list.
Tracing the Example
Using the question's own sequence of calls, starting from ["cat", "mouse", "frog", "dog", "dog"]:
animals.removeWordsOfLength(4):
i |
Word | Length | Match? | Action |
|---|---|---|---|---|
| 4 | "dog" |
3 | no | keep |
| 3 | "dog" |
3 | no | keep |
| 2 | "frog" |
4 | yes | remove |
| 1 | "mouse" |
5 | no | keep |
| 0 | "cat" |
3 | no | keep |
Result: ["cat", "mouse", "dog", "dog"] — matches the question's expected list.
animals.removeWordsOfLength(3), continuing from that result:
i |
Word | Length | Match? | Action |
|---|---|---|---|---|
| 3 | "dog" |
3 | yes | remove |
| 2 | "dog" |
3 | yes | remove |
| 1 | "mouse" |
5 | no | keep |
| 0 | "cat" |
3 | yes | remove |
Result: ["mouse"] — matches.
animals.removeWordsOfLength(2), continuing from ["mouse"]: "mouse" is 5 letters, no match, so the list stays ["mouse"] — matches.
Common Mistakes to Avoid
- Looping forward with a plain
i++while removing. After a removal, the element that used to be ati + 1slides into positioni, so the very next iteration (i + 1) skips right over it. - Using a for-each loop (
for (Object o : myList)) and removing inside it. Modifying anArrayList's contents while iterating it with a for-each loop throws aConcurrentModificationException. - Forgetting the
(String)cast — same issue as part (a);myListstill isn't a genericArrayList<String>.
Key Takeaways
- Counting matches in an
ArrayListis a simple forward loop, but removing matches safely means looping backward, since removal shifts every later index down by one. - An
ArrayListdeclared without a generic type (ArrayListinstead ofArrayList<String>) returns plainObjects fromget(...), and needs an explicit cast before calling any type-specific method. - "Exactly matches" conditions use
==for numeric comparisons like length — reserve.equals()for comparing the actual text of twoStrings.