CompSci.rocks
FRQcsapa

Review / ReviewAnalysis: 2022 FRQ 3

A step-by-step solution to the 2022 AP CSA FRQ 3 (ReviewAnalysis), covering array traversal, accumulation, and building an ArrayList from filtered data in Java.

Summarizing an array of custom objects two different ways is the challenge in this AP Computer Science A free-response question — first as a plain numeric average, then as a filtered, reformatted ArrayList of text.

What This FRQ Tests

  • AP CSA units: Unit 6 (Array) and Unit 7 (ArrayList)
  • Core skill: looping through an array of objects to accumulate a numeric result
  • Secondary skill: building a new ArrayList by selectively picking and transforming elements from an existing array
  • Official category: "Array/ArrayList" — always FRQ 3 on the AP CSA exam

The Setup

  • The given Review class has:
    • private int rating
    • private String comment — either an empty string, or text ending in a letter, a period (.), or an exclamation point (!)
    • int getRating() and String getComment() getters
  • The ReviewAnalysis class holds:
    • private Review[] allReviews — a plain array, not an ArrayList
  • You're asked to write two methods:
    • getAverageRating() — the arithmetic mean of every rating in the array
    • collectComments() — a filtered, reformatted ArrayList<String> built from the array

Part (a): Writing getAverageRating()

Step-by-Step Approach

  1. Set up a running total, starting at 0.
  2. Loop over every index of allReviews.
  3. On each iteration, get that Review's rating and add it to the total.
  4. After the loop, divide the total by the number of reviews — as a double, not an int.

The Code

public double getAverageRating()
{
    int total = 0;

    for (int i = 0; i < allReviews.length; i++)
    {
        total += allReviews[i].getRating();
    }

    return (double) total / allReviews.length;
}

Why the (double) Cast Is Required

  • total and allReviews.length are both declared as int.
  • Java's / operator performs integer division when both operands are ints — it truncates any decimal, so 7 / 2 evaluates to 3, not 3.5.
  • Casting total to double before the division forces Java to treat the whole expression as floating-point math, so the decimal part is preserved.
  • Casting the result instead — (double) (total / allReviews.length) — would not work, because the truncation already happened before the cast was applied.

Common Mistakes to Avoid

  • Casting in the wrong place(double)(total / allReviews.length) truncates first, then converts; the .0 you get back is meaningless.
  • Casting allReviews.length instead of total (or not casting at all) — only one side of a division needs to be a double for Java to perform floating-point division, but skipping the cast entirely gives you truncated integer math.
  • Declaring total as a double from the start. This technically also works, but mixing an int accumulator with a single explicit cast at the return statement is the more common, rubric-friendly style.

Part (b): Writing collectComments()

The Rule, Broken Down

  1. Look at every comment in allReviews.
  2. Keep only the ones that contain an exclamation point (!) anywhere in the text.
  3. Format each kept comment as "index-comment" (the review's array index, a hyphen, then the original comment).
  4. If the comment doesn't already end in a period or exclamation point, add a period to the end.
  5. Collect all the formatted strings into an ArrayList<String> and return it.

Step-by-Step Approach

  1. Create an empty ArrayList<String> to hold the results.
  2. Loop over every index of allReviews.
  3. Pull out that review's comment.
  4. Check whether the comment contains "!" — skip it entirely if not.
  5. If it does, build the "index-comment" string.
  6. Check the comment's last character. If it isn't . or !, append a period.
  7. Add the finished string to the ArrayList.
  8. After the loop, return the ArrayList.

The Code

public ArrayList<String> collectComments()
{
    ArrayList<String> comments = new ArrayList<String>();

    for (int i = 0; i < allReviews.length; i++)
    {
        String comment = allReviews[i].getComment();

        if (comment.indexOf("!") != -1)
        {
            String formatted = i + "-" + comment;
            String lastChar = comment.substring(comment.length() - 1);

            if (!lastChar.equals(".") && !lastChar.equals("!"))
            {
                formatted = formatted + ".";
            }

            comments.add(formatted);
        }
    }

    return comments;
}

Why Each Piece Matters

  • comment.indexOf("!") != -1indexOf returns the position of the first match, or -1 if the character never appears anywhere in the string. Comparing against -1 is the standard way to ask "does this string contain X?" using a method that's actually on the AP Quick Reference sheet (contains isn't listed there, though using it wouldn't be wrong — see the Notes section below).
  • comment.substring(comment.length() - 1)charAt would return the last character more directly, but it isn't on the Quick Reference sheet either, so this solution sticks to substring to stay within what's guaranteed to be there for you to look up. It's a common substitute pattern worth memorizing.
  • !lastChar.equals(".") && !lastChar.equals("!") — both conditions have to be true (the last character is neither a period nor an exclamation point) before a period gets appended.
  • The i in i + "-" + comment — Java automatically converts the int index to a String when it's concatenated with +, so no explicit conversion is needed.

Tracing the Example

Using the sample data from the question — five reviews with comments "Good! Thx", "OK site", "Great!", "Poor! Bad.", "":

Index Comment Contains !? Last char Result
0 "Good! Thx" yes x (not . or !) "0-Good! Thx."
1 "OK site" no (skipped)
2 "Great!" yes ! "2-Great!"
3 "Poor! Bad." yes . "3-Poor! Bad."
4 "" no (skipped)

Final result: ["0-Good! Thx.", "2-Great!", "3-Poor! Bad."] — matches the question's expected output exactly.

Common Mistakes to Avoid

  • Checking for ! with equals("!") instead of indexOf. equals checks the entire string, not whether it contains a character — this would only match a comment that is exactly "!".
  • Appending a period unconditionally. Comments already ending in . or ! should be left alone — check first.
  • Forgetting the empty-string case. An empty comment ("") has no !, so it's correctly skipped by the indexOf check — but calling substring(comment.length() - 1) on an empty string would throw an exception, which is why that line only ever runs after the ! check has already confirmed the comment isn't empty.
  • Off-by-one on the index. Use the loop variable i directly — don't add or subtract 1.

Notes: Methods Not on the AP CSA Quick Reference Sheet

A first-year (non-AP) Java course often teaches a few String methods that read a bit more naturally. They're not on the AP Quick Reference sheet, but that's not the same as being disallowed — any correct Java is accepted on the real exam:

public ArrayList<String> collectComments()
{
    ArrayList<String> comments = new ArrayList<String>();

    for (int i = 0; i < allReviews.length; i++)
    {
        String comment = allReviews[i].getComment();

        if (comment.contains("!"))
        {
            String formatted = i + "-" + comment;
            char lastChar = comment.charAt(comment.length() - 1);

            if (lastChar != '.' && lastChar != '!')
            {
                formatted = formatted + ".";
            }

            comments.add(formatted);
        }
    }

    return comments;
}
  • contains("!") replaces the indexOf("!") != -1 check.
  • charAt(...) grabs the last character directly as a char, instead of pulling out a one-character String with substring.
  • Both are completely valid Java, and using them on the real exam is fine if you're confident in them — the only real tradeoff is that you can't look their exact behavior up on the reference sheet if you second-guess yourself mid-exam, the way you could with indexOf or substring.

Key Takeaways

  • When a method on the AP Quick Reference sheet isn't quite what you're used to (no contains, no charAt), there's almost always an indexOf/substring-based equivalent.
  • Integer division truncates — cast to double before dividing, not after.
  • Filtering-and-transforming into a new ArrayList is a three-step loop pattern: check a condition, build the new value, add it.

Related FRQs