CompSci.rocks
FRQcsapa

WeatherData: 2023 FRQ 3

A step-by-step solution to the 2023 AP CSA FRQ 3 (WeatherData), covering safely removing elements from an ArrayList while iterating and tracking the longest run of consecutive values in Java.

Cleaning up a season's worth of daily high temperatures and then hunting for the longest hot streak inside them is what drives this AP Computer Science A free-response question — one method trims bad data out of an ArrayList, the other tracks a running streak across it.

What This FRQ Tests

  • AP CSA units: Unit 6 (Array) and Unit 7 (ArrayList)
  • Core skill: removing elements from an ArrayList while looping over it, without skipping elements or crashing
  • Secondary skill: tracking a running streak length across consecutive elements, and remembering the best streak seen so far
  • Official category: "Array/ArrayList" — always FRQ 3 on the AP CSA exam

The Setup

  • WeatherData holds one field: private ArrayList<Double> temperatures — guaranteed non-null, with no null entries.
  • You're asked to write two methods:
    • void cleanData(double lower, double upper) — removes every value below lower or above upper, keeping the remaining values in their original order
    • int longestHeatWave(double threshold) — returns the length of the longest run of consecutive days all above threshold (a "heat wave" needs at least 2 such days in a row; the data is guaranteed to contain at least one)

Part (a): Writing cleanData(double lower, double upper)

The Rule, Broken Down

  1. Any value strictly less than lower gets removed.
  2. Any value strictly greater than upper gets removed.
  3. Everything else stays, in its original relative order.

Step-by-Step Approach

  1. Loop over the ArrayList's indices — but from the last index down to 0, not the usual 0 up to size() - 1.
  2. At each index, get that value and check whether it's out of range.
  3. If it's out of range, remove it at that index right away.
  4. Let the loop keep going until every index (checked in this backwards order) has been visited.

The Code

public void cleanData(double lower, double upper)
{
    for (int i = temperatures.size() - 1; i >= 0; i--)
    {
        double temp = temperatures.get(i);

        if (temp < lower || temp > upper)
        {
            temperatures.remove(i);
        }
    }
}

Why Looping Backwards Is Required

  • ArrayList.remove(index) shifts every later element one position to the left, and shrinks size() by 1.
  • If you loop forward and remove at index i, the element that used to be at i + 1 slides into index i — but the loop's next iteration checks index i + 1, skipping right over it.
  • Looping from the last index down to 0 sidesteps this entirely: removing index i only ever shifts elements at indices greater than i, all of which the backwards loop has already finished checking. Every remaining, not-yet-visited index still lines up with the value it's supposed to hold.

Tracing the Example

Using the question's sample data — indices 0–9 hold 99.1, 142.0, 85.0, 85.1, 84.6, 94.3, 124.9, 98.0, 101.0, 102.5 — with cleanData(85.0, 120.0):

Index checked (backwards) Value In range [85.0, 120.0]? Action
9 102.5 yes keep
8 101.0 yes keep
7 98.0 yes keep
6 124.9 no (> 120.0) remove
5 94.3 yes keep
4 84.6 no (< 85.0) remove
3 85.1 yes keep
2 85.0 yes (not less than 85.0) keep
1 142.0 no (> 120.0) remove
0 99.1 yes keep

Final contents: 99.1, 85.0, 85.1, 94.3, 98.0, 101.0, 102.5 — matches the question's expected result exactly, with the order of the surviving values preserved.

Common Mistakes to Avoid

  • Looping forward with i++ while removing elements. This is the single most common bug on this style of problem — it silently skips checking every element that shifts into a just-vacated spot.
  • Using <= instead of < for the lower bound, or >= instead of > for the upper bound. The rule removes values less than lower and greater than upper — a value exactly equal to either bound must be kept, as 85.0 is in the traced example.
  • Calling temperatures.size() fresh inside the loop condition instead of capturing it once (or, as here, counting down from a value computed before removals start). It isn't wrong to call size() again in a backwards loop, but many students write i < temperatures.size() out of habit — which only makes sense for a forward loop, and would misbehave here.

Part (b): Writing longestHeatWave(double threshold)

The Rule, Broken Down

  1. A heat wave is 2 or more consecutive days with a temperature strictly greater than threshold.
  2. The answer is the length of the longest such run anywhere in the data.
  3. The data is guaranteed to contain at least one heat wave, so the true answer is always at least 2 — nothing special has to be done to rule out a lone single-day spike being reported as the answer.

Step-by-Step Approach

  1. Keep two counters: current, the length of the streak in progress, and longest, the best streak seen so far. Both start at 0.
  2. Loop through every temperature in order.
  3. If the current temperature is above threshold, extend the streak: current++.
  4. If it's not, the streak is broken: reset current back to 0.
  5. After updating current each iteration, check whether it's now the new best, and update longest if so.
  6. After the loop, longest holds the answer.

The Code

public int longestHeatWave(double threshold)
{
    int longest = 0;
    int current = 0;

    for (int i = 0; i < temperatures.size(); i++)
    {
        if (temperatures.get(i) > threshold)
        {
            current++;
        }
        else
        {
            current = 0;
        }

        if (current > longest)
        {
            longest = current;
        }
    }

    return longest;
}

Why Each Piece Matters

  • current resets to 0, not 1, when a day fails the threshold. A broken streak means the next qualifying day starts a brand-new streak of length 1 on its own next iteration, not a continuation of anything.
  • longest is updated every iteration, not just when a streak ends. Checking current > longest right after updating current correctly captures a streak's length even if it's still growing when the data runs out.
  • The precondition (there's guaranteed to be at least one real heat wave) is what makes this simple loop sufficient — since a heat wave requires length 2+, and the longest run overall can never be shorter than any heat wave that exists, the maximum run length this loop finds is guaranteed to be an actual heat wave's length, not a stray single day.

Tracing the Example

Using the question's sample data — 100.5, 98.5, 102.0, 103.9, 87.5, 105.2, 90.3, 94.8, 109.1, 102.1, 107.4, 93.2 — with longestHeatWave(100.5):

Value > 100.5? current after longest after
100.5 no 0 0
98.5 no 0 0
102.0 yes 1 1
103.9 yes 2 2
87.5 no 0 2
105.2 yes 1 2
90.3 no 0 2
94.8 no 0 2
109.1 yes 1 2
102.1 yes 2 2
107.4 yes 3 3
93.2 no 0 3

Final result: 3 — matching the question exactly (the two-day run at 102.0, 103.9 and the three-day run at 109.1, 102.1, 107.4 are the two heat waves, and 3 is the longer one). Re-running the same trace with longestHeatWave(95.2) extends the very first run to four days (100.5, 98.5, 102.0, 103.9 are all above 95.2), correctly producing 4, also matching the question.

Common Mistakes to Avoid

  • Resetting current to 1 instead of 0 on a failed day. This overcounts every streak by one and miscounts consecutive short streaks as connected.
  • Only checking current > longest after the streak ends (e.g., inside the else branch) instead of every iteration. This misses a streak that's still in progress when the loop reaches the last element.
  • Using >= instead of > when comparing against threshold. The rule is strictly "greater than," so a temperature exactly equal to the threshold does not extend a heat wave.
  • Forgetting a heat wave requires at least 2 days and trying to special-case single-day spikes explicitly — the precondition already guarantees this isn't necessary, since the true longest run can't be shorter than 2.

Key Takeaways

  • Removing elements from an ArrayList while iterating over it means looping backwards, from the last index to 0 — forward loops skip elements after a removal.
  • "Longest run of consecutive elements meeting a condition" is a two-variable pattern: a current counter that grows or resets each iteration, and a longest counter that's checked (and possibly updated) every single time current changes.
  • A precondition that guarantees a result exists (like "there's at least one heat wave") can simplify a solution — it means you don't have to defend against an edge case that's already ruled out.

Related FRQs