CompSci.rocks
FRQcsapa

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) with i < j exactly once
  • Secondary skill: looping through an ArrayList of 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 WordPair class (you don't write this one) has:
    • A constructor: WordPair(String first, String second)
    • String getFirst() and String getSecond() — getters for the two stored words
  • The WordPairList class you're completing holds:
    • private ArrayList<WordPair> allPairs
  • You're asked to write two members:
    • The constructor WordPairList(String[] words) — builds allPairs from every valid pair of indices
    • numMatches() — counts how many pairs in allPairs have the same word stored twice

Part (a): Writing the WordPairList Constructor

The Rule, Broken Down

  1. For every pair of indices i and j where 0 <= i < j < words.length, create one WordPair object: (words[i], words[j]).
  2. Each valid pair is added to allPairs exactly once — never (words[j], words[i]) as well, and never a pair where i == j.
  3. Order inside allPairs doesn't matter — the problem explicitly says "in some order."

Step-by-Step Approach

  1. Initialize allPairs to a new, empty ArrayList<WordPair>.
  2. Loop i from 0 up to (not including) words.length.
  3. Inside that, loop j starting at i + 1 (not 0, and not i) up to (not including) words.length.
  4. For each (i, j), construct new WordPair(words[i], words[j]) and add it to allPairs.

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, not j = 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, not j = i — starting at i itself would pair a word with itself (i == j), which the rule i < j explicitly 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 = 0 or j = i. Either one breaks the "each pair exactly once, with i < 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 an ArrayIndexOutOfBoundsException.
  • Swapping the constructor arguments to new WordPair(words[j], words[i]) — this technically still creates a valid WordPair, 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

  1. Track a running count, starting at 0.
  2. Loop over every element of allPairs by index.
  3. Pull out that WordPair, and compare its two words with .equals().
  4. If they match, increment the count.
  5. 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)ArrayList doesn't support allPairs[i] bracket syntax like a plain array does; get(int index) is the ArrayList equivalent.
  • .equals(), never == — comparing String contents always uses .equals(); == would (incorrectly) check whether the two String objects are the exact same object in memory.
  • allPairs.size(), not words.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-built Strings with identical characters aren't guaranteed to be the same object, so == can silently give wrong answers.
  • Looping over words.length instead of allPairs.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 trying allPairs[i]. ArrayList isn'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 at i + 1, never 0 or i.
  • An ArrayList of custom objects is looped through the same way as any other ArrayListget(i) inside a loop bounded by size() — regardless of what the stored object type is.
  • String comparisons always use .equals(), never ==, no matter how deeply nested inside other objects and loops they end up.

Related FRQs