CompSci.rocks
FRQcsapa

WordChecker: 2024 FRQ 3

A step-by-step solution to the 2024 AP CSA FRQ 3 (WordChecker), covering checking a chain of substrings across an ArrayList and filtering it into a new one with prefixes stripped in Java.

Checking whether a list of words forms an unbroken "chain" — each one hiding the last inside it — is the first half of this AP Computer Science A free-response question, followed by pulling out just the words that start a certain way and trimming that starting piece off.

What This FRQ Tests

  • AP CSA units: Unit 6 (Array) and Unit 7 (ArrayList)
  • Core skill: comparing each element of an ArrayList to the one before it
  • Secondary skill: filtering an ArrayList into a new one while transforming each kept element along the way
  • Official category: "Array/ArrayList" — always FRQ 3 on the AP CSA exam

The Setup

  • WordChecker holds one field: private ArrayList<String> wordList — guaranteed non-null, with no null entries.
  • You're asked to write two methods:
    • boolean isWordChain() — checks whether every element (after the first) contains the one before it as a substring
    • ArrayList<String> createList(String target) — collects every element that starts with target, with that leading target stripped off

Part (a): Writing isWordChain()

The Rule, Broken Down

  1. Compare every element starting from index 1 to the element right before it.
  2. Each of those elements must contain the previous one somewhere inside it (as a substring).
  3. If even one comparison fails, the whole thing isn't a chain.

Step-by-Step Approach

  1. Loop i from 1 up to (not including) wordList.size().
  2. At each i, grab the current element and the one right before it.
  3. Check whether the current element contains the previous one using indexOf.
  4. The moment one comparison fails, return false immediately.
  5. If the loop finishes without ever failing, return true.

The Code

public boolean isWordChain()
{
    for (int i = 1; i < wordList.size(); i++)
    {
        String previous = wordList.get(i - 1);
        String current = wordList.get(i);

        if (current.indexOf(previous) == -1)
        {
            return false;
        }
    }

    return true;
}

Why Each Piece Matters

  • The loop starts at i = 1, not 0 — the first element has nothing before it to compare against, so the rule only applies from the second element onward.
  • current.indexOf(previous) == -1indexOf returns -1 exactly when the argument never appears anywhere inside the string it's called on, which is precisely "does not contain as a substring."
  • Returning false the instant a comparison fails — the whole chain is broken by a single failure, so there's no reason to keep checking the rest once one has already failed.
  • Returning true only after the loop finishes — this correctly handles the case where every single comparison passed.

Tracing the Example

Using both examples from the question:

wordList Comparison Result
["an", "band", "band", "abandon"] "band" contains "an"? yes continue
"band" contains "band"? yes continue
"abandon" contains "band"? yes continue
(loop finishes) returns true — matches
["to", "too", "stool", "tools"] "too" contains "to"? yes continue
"stool" contains "too"? yes continue
"tools" contains "stool"? no returns false — matches

Common Mistakes to Avoid

  • Starting the loop at i = 0 and trying to compare the first element to something before it, which doesn't exist.
  • Checking previous.indexOf(current) instead of current.indexOf(previous) — the rule is specifically that each element contains the previous one, not the other way around.
  • Continuing to loop and overwrite the result after a failure is found, instead of returning immediately — this risks a later, passing comparison masking an earlier failure.

Part (b): Writing createList(String target)

The Rule, Broken Down

  1. Look at every element of wordList.
  2. Keep only the ones that start with target.
  3. For each one kept, strip off that leading occurrence of target before adding it to the result.
  4. Keep the original relative order.

Step-by-Step Approach

  1. Create an empty ArrayList<String> to collect results.
  2. Loop over every element of wordList.
  3. Check whether that element is at least as long as target and its first target.length() characters equal target.
  4. If so, add the remainder of the string (everything after that prefix) to the result.
  5. After the loop, return the result.

The Code

public ArrayList<String> createList(String target)
{
    ArrayList<String> result = new ArrayList<String>();

    for (int i = 0; i < wordList.size(); i++)
    {
        String word = wordList.get(i);

        if (word.length() >= target.length() && word.substring(0, target.length()).equals(target))
        {
            result.add(word.substring(target.length()));
        }
    }

    return result;
}

Why Each Piece Matters

  • word.length() >= target.length(), checked first — this guards against calling substring(0, target.length()) on a word shorter than target, which would throw a StringIndexOutOfBoundsException. Java's && short-circuits, so the substring call never runs unless the length check already passed.
  • word.substring(0, target.length()).equals(target) — pulls out exactly the first target.length() characters and compares them for an exact prefix match.
  • word.substring(target.length()) — the one-argument form of substring returns everything from that index to the end, which is exactly "the word with the leading target removed."

Tracing the Example

Using wordList = ["catch", "bobcat", "catchacat", "cat", "at"] and createList("cat") (target length 3):

Word Length ≥ 3? Starts with "cat"? Added
"catch" yes yes "ch"
"bobcat" yes no (skipped)
"catchacat" yes yes "chacat"
"cat" yes yes ""
"at" no (length 2) (skipped before checking) (skipped)

Result: ["ch", "chacat", ""] — matches the question's expected output exactly, including the empty string for "cat" matching itself completely.

Common Mistakes to Avoid

  • Skipping the length check entirely. Calling substring(0, target.length()) on a word shorter than target (like "at" against "cat") crashes with an exception rather than just failing the comparison.
  • Using contains-style logic instead of a prefix check. The rule is specifically "starts with," not "contains anywhere" — "bobcat" does contain "cat", but not at the start, so it's correctly excluded.
  • Forgetting to strip the prefix before adding to the result — the returned list holds the remainder of each matching word, not the original word.

Key Takeaways

  • Comparing "this element to the previous one" always starts the loop at index 1, since the first element has nothing to compare against.
  • indexOf(x) == -1 is the standard "does not contain" check; a prefix check instead uses substring(0, x.length()).equals(x), guarded by a length check first.
  • Filtering into a new ArrayList while transforming each kept element is a three-step pattern: check a condition, compute the transformed value, add it — all inside the same loop.

Related FRQs