WordPair / WordPairList: 2018 FRQ 2
A step-by-step solution to the 2018 AP CSA FRQ 2 (WordPairList), covering nested loops for generating every index pair and counting matches in an ArrayList of custom objects in Java.
Every possible pairing between the words in an array is generated and inspected in this AP Computer Science A free-response question — first building the full list of pairs with a nested loop, then counting how many of those pairs repeat the same word twice.
What This FRQ Tests
- AP CSA units: Unit 5 (Writing Classes) and Unit 7 (ArrayList)
- Core skill: a nested loop that generates every pair of indices
(i, j)withi < jexactly once - Secondary skill: looping through an
ArrayListof custom objects to accumulate a count - Official category: "Array/ArrayList," which on the 2018 exam was FRQ 2 (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; 2018's actual printed order was FrogSimulation, WordPair, StringChecker, ArrayTester)
The Setup
- The given
WordPairclass (you don't write this one) has:- A constructor:
WordPair(String first, String second) String getFirst()andString getSecond()— getters for the two stored words
- A constructor:
- The
WordPairListclass you're completing holds:private ArrayList<WordPair> allPairs
- You're asked to write two members:
- The constructor
WordPairList(String[] words)— buildsallPairsfrom every valid pair of indices numMatches()— counts how many pairs inallPairshave the same word stored twice
- The constructor
Part (a): Writing the WordPairList Constructor
The Rule, Broken Down
- For every pair of indices
iandjwhere0 <= i < j < words.length, create oneWordPairobject:(words[i], words[j]). - Each valid pair is added to
allPairsexactly once — never(words[j], words[i])as well, and never a pair wherei == j. - Order inside
allPairsdoesn't matter — the problem explicitly says "in some order."
Step-by-Step Approach
- Initialize
allPairsto a new, emptyArrayList<WordPair>. - Loop
ifrom0up to (not including)words.length. - Inside that, loop
jstarting ati + 1(not0, and noti) up to (not including)words.length. - For each
(i, j), constructnew WordPair(words[i], words[j])and add it toallPairs.
Starting the inner loop at i + 1 instead of 0 is what guarantees i < j and that no pair is generated twice.
The Code
public WordPairList(String[] words)
{
allPairs = new ArrayList<WordPair>();
for (int i = 0; i < words.length; i++)
{
for (int j = i + 1; j < words.length; j++)
{
allPairs.add(new WordPair(words[i], words[j]));
}
}
}
Why Each Piece Matters
j = i + 1, notj = 0— starting the inner loop back at the beginning every time would generate both(words[i], words[j])and(words[j], words[i]), doubling the list and violating "each pair added exactly once."j = i + 1, notj = i— starting atiitself would pair a word with itself (i == j), which the rulei < jexplicitly excludes.words[i]first,words[j]second — matches the order given in the rule,(words[i], words[j]).
Tracing the Example
String[] wordNums = {"one", "two", "three"};
i |
j values used |
Pairs created |
|---|---|---|
| 0 | 1, 2 | ("one", "two"), ("one", "three") |
| 1 | 2 | ("two", "three") |
| 2 | (none — no j satisfies both j < 3 and j > 2) |
— |
Final allPairs: ("one", "two"), ("one", "three"), ("two", "three") — matches the problem's expected list exactly.
A quick check with the trickier example, String[] phrase = {"the", "more", "the", "merrier"};, confirms duplicate words (index 0 and index 2 are both "the") don't need any special handling — they're just two different indices, so they still generate normal pairs: i=0 gives ("the","more"), ("the","the"), ("the","merrier"); i=1 gives ("more","the"), ("more","merrier"); i=2 gives ("the","merrier"). That's the same six pairs, in the same order, as the problem's expected output.
Common Mistakes to Avoid
- Starting the inner loop at
j = 0orj = i. Either one breaks the "each pair exactly once, withi < j" rule — the first doubles the list with reversed duplicates, the second adds invalid self-pairs. - Using
j <= words.length. This reads one index past the end of the array on the last iteration, throwing anArrayIndexOutOfBoundsException. - Swapping the constructor arguments to
new WordPair(words[j], words[i])— this technically still creates a validWordPair, but not the one the rule describes ((words[i], words[j])).
Part (b): Writing numMatches()
The Rule, Broken Down
A pair "matches" if its two stored words are the same word. Count how many pairs in allPairs match.
Step-by-Step Approach
- Track a running count, starting at
0. - Loop over every element of
allPairsby index. - Pull out that
WordPair, and compare its two words with.equals(). - If they match, increment the count.
- After the loop, return the count.
The Code
public int numMatches()
{
int count = 0;
for (int i = 0; i < allPairs.size(); i++)
{
WordPair pair = allPairs.get(i);
if (pair.getFirst().equals(pair.getSecond()))
{
count++;
}
}
return count;
}
Why Each Piece Matters
allPairs.get(i)—ArrayListdoesn't supportallPairs[i]bracket syntax like a plain array does;get(int index)is theArrayListequivalent..equals(), never==— comparingStringcontents always uses.equals();==would (incorrectly) check whether the twoStringobjects are the exact same object in memory.allPairs.size(), notwords.length— the loop bound needs to match the number of pairs generated in part (a), which is a different number from the count of original words.
Tracing the Example
String[] moreWords = {"the", "red", "fox", "the", "red"}; produces 10 pairs (every i < j combination of 5 words):
| Pair | Match? |
|---|---|
("the", "red") |
no |
("the", "fox") |
no |
("the", "the") |
yes |
("the", "red") |
no |
("red", "fox") |
no |
("red", "the") |
no |
("red", "red") |
yes |
("fox", "the") |
no |
("fox", "red") |
no |
("the", "red") |
no |
count ends at 2, matching exampleThree.numMatches() returning 2 exactly, including which two pairs are the matches (the same ones shaded in the original problem).
Common Mistakes to Avoid
- Comparing with
==instead of.equals(). Two separately-builtStrings with identical characters aren't guaranteed to be the same object, so==can silently give wrong answers. - Looping over
words.lengthinstead ofallPairs.size(). These are different numbers — 5 original words produced 10 pairs in this example — so using the wrong one either skips pairs or throws an out-of-bounds exception. - Forgetting
allPairs.get(i)and tryingallPairs[i].ArrayListisn't a plain array — bracket indexing doesn't work on it at all.
Key Takeaways
- "Every pair of indices with
i < j" is a standard nested-loop pattern: the inner loop always starts ati + 1, never0ori. - An
ArrayListof custom objects is looped through the same way as any otherArrayList—get(i)inside a loop bounded bysize()— regardless of what the stored object type is. Stringcomparisons always use.equals(), never==, no matter how deeply nested inside other objects and loops they end up.