CompSci.rocks
FRQcsapa

Scramble: 2014 FRQ 1

A step-by-step solution to the 2014 AP CSA FRQ 1 (scrambleWord/scrambleOrRemove), covering character-by-character string scrambling and removing unchanged entries from a List in Java.

Rearranging letters according to a strict left-to-right scanning rule is the heart of this AP Computer Science A free-response question — first on a single word, then across an entire list of them, where the "unscrambled" leftovers get dropped entirely.

What This FRQ Tests

  • AP CSA units: Unit 3 (Boolean Expressions and if Statements), Unit 4 (Iteration), and Unit 7 (ArrayList)
  • Core skill: walking through a String one position at a time, deciding whether the current position and the next one form a special pair, and skipping ahead by a different amount depending on that decision
  • Secondary skill: modifying a List in place — replacing some entries and removing others — while keeping the surviving entries in their original relative order
  • Official category: "Methods and Control Structures" — always FRQ 1 on the AP CSA exam

The Setup

  • Both methods belong to the same class, which isn't shown — only the two method signatures matter.
  • scrambleWord(String word) — takes a word made only of uppercase letters (or an empty string) and returns a scrambled version, following these rules:
    • Scan the word from left to right, one position at a time.
    • Whenever the current letter is "A" and the very next letter is not "A", swap those two letters in the result.
    • Once two positions have been swapped, neither one can be swapped again — scanning continues from the position right after the pair.
  • scrambleOrRemove(List<String> wordList) — replaces every entry in wordList with its scrambled version, then removes any entry that came back identical to what it started as. The relative order of whatever survives stays the same.

Part (a): Writing scrambleWord

The Rule, Broken Down

  1. Look at the word two letters at a time, starting from the very first letter.
  2. If the current letter is "A" and the next one is not, those two letters swap places in the output.
  3. If a swap just happened, the next comparison starts after both swapped letters — never re-examine a letter that was just involved in a swap.
  4. If the current letter isn't "A", or there's no next letter to compare against, or the next letter is also "A", it's copied through unchanged, and scanning moves ahead by just one position.

Step-by-Step Approach

  1. Build the result one piece at a time in a String variable, starting empty.
  2. Track a position i, starting at 0.
  3. While i is still a valid index into word:
    • Check whether word's letter at i is "A", there's a letter at i + 1, and that next letter is not "A".
    • If all three are true, append the letter at i + 1 followed by the letter at i, then jump i ahead by 2.
    • Otherwise, append just the letter at i, then move i ahead by 1.
  4. Once i reaches the end of the word, return what's been built.

The Code

public static String scrambleWord(String word)
{
    String result = "";
    int i = 0;

    while (i < word.length())
    {
        if (word.substring(i, i + 1).equals("A") && i + 1 < word.length()
                && !word.substring(i + 1, i + 2).equals("A"))
        {
            result = result + word.substring(i + 1, i + 2) + word.substring(i, i + 1);
            i = i + 2;
        }
        else
        {
            result = result + word.substring(i, i + 1);
            i = i + 1;
        }
    }

    return result;
}

Why Each Piece Matters

  • word.substring(i, i + 1) — pulls out a single character as a one-character String. charAt would grab it more directly, but it isn't on the Quick Reference sheet, so this solution sticks with substring to stay within what's guaranteed to be there for you to look up.
  • i + 1 < word.length() — this check has to come before looking at word.substring(i + 1, i + 2). Skipping it would try to read one character past the end of the word on the very last letter, throwing an exception.
  • i = i + 2 after a swap, i = i + 1 otherwise — this is what enforces "once swapped, neither position can be involved in a future swap." Advancing by only 1 after a swap would let the just-swapped letter be re-examined.
  • Building result with +, not modifying wordStrings in Java are immutable, so the original word is never changed; a brand-new String is assembled and returned instead.

Tracing the Example

Walking through "ABRACADABRA" position by position (11 letters, indices 010):

i Letter at i Next letter Swap? Appended Next i
0 A B yes B, A 2
2 R A no R 3
3 A C yes C, A 5
5 A D yes D, A 7
7 A B yes B, A 9
9 R A no R 10
10 A (none) no A 11

Concatenating every "Appended" column top to bottom gives "BARCADABARA" — matching the table's expected result exactly.

A couple of the shorter examples highlight the edge cases:

  • "AARDVARK": at i = 0 the letter is "A", but the next letter (i = 1) is also "A" — so no swap happens there, and scanning only moves ahead by 1. The very next check, at i = 1, does find "A" followed by "R" and swaps. Final result: "ARADVRAK", matching the table.
  • "A" and "": with only zero or one letters, i + 1 < word.length() is never true, so no swap is ever possible — both are returned unchanged, exactly as the table shows.

Common Mistakes to Avoid

  • Checking word.substring(i + 1, i + 2) before confirming i + 1 < word.length(). On the last letter of the word, this throws a StringIndexOutOfBoundsException.
  • Advancing i by only 1 after a swap. This lets the second letter of a just-completed swap be examined again, which can trigger an extra, unintended swap and produce the wrong result on words like "AARDVARK".
  • Swapping when the next letter is also "A". The rule specifically requires the second letter to be something other than "A" — two As in a row are left alone.
  • Forgetting the empty-string and single-letter cases. Both are valid input per the precondition and should simply return unchanged, which falls out naturally as long as the bounds check is written correctly.

Part (b): Writing scrambleOrRemove

The Rule, Broken Down

  1. Every entry in wordList gets replaced by calling scrambleWord on it.
  2. If an entry's scrambled version is identical to what it started as, that entry is removed entirely instead of being replaced.
  3. Whatever entries remain keep the same relative order they had before the call.

Step-by-Step Approach

  1. Track a position i, starting at 0not a simple for-loop, since removing an entry changes where the next entry to check ends up.
  2. While i is still a valid index into wordList:
    • Grab the entry currently at position i, and compute its scrambled version by calling scrambleWord on it.
    • If the scrambled version equals the original, remove the entry at i — and leave i where it is, since the next entry has now slid into that same position.
    • Otherwise, replace the entry at i with its scrambled version, then move i ahead by 1.
  3. Once i reaches the end of the (possibly shrunken) list, the method is done — there's nothing to return.

The Code

public static void scrambleOrRemove(List<String> wordList)
{
    int i = 0;

    while (i < wordList.size())
    {
        String original = wordList.get(i);
        String scrambled = scrambleWord(original);

        if (scrambled.equals(original))
        {
            wordList.remove(i);
        }
        else
        {
            wordList.set(i, scrambled);
            i++;
        }
    }
}

Why Each Piece Matters

  • Comparing scrambled to original before overwriting anything — once wordList.set(i, scrambled) runs, the original value is gone. The comparison has to happen while both versions are still available.
  • i doesn't advance after a removeremove(index) shifts every later element one position to the left, so the entry that used to be at i + 1 is now sitting at i. Advancing i anyway would skip over it without ever checking it.
  • A while loop instead of a for loop — a for loop's i++ runs unconditionally every iteration, which is exactly wrong here: after a removal, i needs to stay put, not increase.
  • wordList.size() re-checked every iteration — since removals shrink the list, the loop bound can't be captured once at the start; it has to be re-evaluated as written in the while condition.

Tracing the Example

Starting list (5 entries): "TAN", "ABRACADABRA", "WHOA", "APPLE", "EGGS".

i Entry checked Scrambled version Unchanged? Action List afterward
0 "TAN" "TNA" no replace, i → 1 TNA, ABRACADABRA, WHOA, APPLE, EGGS
1 "ABRACADABRA" "BARCADABARA" no replace, i → 2 TNA, BARCADABARA, WHOA, APPLE, EGGS
2 "WHOA" "WHOA" yes remove, i stays 2 TNA, BARCADABARA, APPLE, EGGS
2 "APPLE" "PAPLE" no replace, i → 3 TNA, BARCADABARA, PAPLE, EGGS
3 "EGGS" "EGGS" yes remove, i stays 3 TNA, BARCADABARA, PAPLE

At this point i = 3 and the list's size has shrunk to 3, so the loop ends. Final list: "TNA", "BARCADABARA", "PAPLE" — matching the "After the call" table exactly, both in contents and order.

Common Mistakes to Avoid

  • Using a for (int i = 0; i < wordList.size(); i++) loop. This is the single most common bug in this kind of problem: after a remove, the unconditional i++ skips checking the element that just slid into position i.
  • Capturing wordList.size() in a variable before the loop starts. Since removals shrink the list, using a stale, pre-computed size either loops too many times (crashing with an index error) or stops too early.
  • Comparing against the original list entry after already overwriting it with set. Save the original value in a variable first, exactly as original does here.
  • Removing by value instead of by index (e.g. some equivalent of wordList.remove(original)). List's remove(int index) and a value-based remove(Object) are different overloads entirely — this solution needs the index-based one.

Notes: A Method Not on the AP CSA Quick Reference Sheet

charAt reads more naturally than pulling out a one-character substring every time, and it's a common thing to reach for once you're used to it:

public static String scrambleWord(String word)
{
    String result = "";
    int i = 0;

    while (i < word.length())
    {
        if (word.charAt(i) == 'A' && i + 1 < word.length() && word.charAt(i + 1) != 'A')
        {
            result = result + word.charAt(i + 1) + word.charAt(i);
            i = i + 2;
        }
        else
        {
            result = result + word.charAt(i);
            i = i + 1;
        }
    }

    return result;
}
  • word.charAt(i) returns the individual char at a position directly, instead of a one-character String produced by substring(i, i + 1).
  • Comparing chars uses == and != (comparing primitive values), which is different from comparing Strings with .equals() — that's a completely normal and correct thing to do here, not a mixup.
  • charAt isn't listed on the real exam's Java Quick Reference sheet, but that doesn't make it off-limits — AP CSA graders accept any correct Java. The only actual tradeoff is not having its exact behavior available to look up on the reference sheet if you second-guess yourself mid-exam.

Key Takeaways

  • A scan that sometimes consumes one element and sometimes consumes two needs a while loop with a manually-advanced index — a for loop's fixed i++ can't express "advance by a different amount depending on what just happened."
  • Removing from a List while iterating over it is one of the most common sources of subtle bugs in AP CSA: only advance the loop index when nothing was removed, since a removal shifts every later element down by one.
  • Save any value you'll need to compare before overwriting it — once set (or a plain reassignment) runs, the original is gone for good.

Related FRQs