CompSci.rocks
FRQcsapa

ClimbInfo / ClimbingClub: 2012 FRQ 1

A step-by-step solution to the 2012 AP CSA FRQ 1 (ClimbingClub), covering inserting into a sorted List and analyzing why a duplicate-counting algorithm depends on sorted order in Java.

A mountain climbing club's logbook drives this AP Computer Science A free-response question, and it asks for something a little different from most FRQs — two separate implementations of the same method, followed by a switch into reading someone else's code and deciding whether it actually works.

What This FRQ Tests

  • AP CSA units: Unit 7 (ArrayList), Unit 4 (Iteration), and Unit 2 (comparing and using objects)
  • Core skill: inserting a new element into a List at the position that keeps it sorted, rather than always appending to the end
  • Secondary skill: reading an existing loop and recognizing that its correctness secretly depends on an assumption about how the data is arranged
  • Official category: "Array/ArrayList" by content — this year's exam printed it as FRQ 1, not the FRQ 3 slot Array/ArrayList questions occupy on more recent exams. The fixed FRQ 1–4 category order (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, so a 2012 question's own printed number — not its content — is what determines the "FRQ 1" label here.

The Setup

  • The given ClimbInfo class (you don't write this one) has:
    • A constructor: ClimbInfo(String peakName, int climbTime)
    • String getName() and int getTime() — getters for the peak's name and the climb time
  • The ClimbingClub class holds:
    • private List<ClimbInfo> climbList — guaranteed not null, containing only non-null references
  • You're asked to write two different implementations of the same method:
    • addClimb(String peakName, int climbTime) — part (a) keeps entries in the order they were added; part (b) keeps entries in alphabetical order by name
  • Then, in part (c), you're given a working implementation of a third method, distinctPeakNames(), and asked whether it behaves correctly under each of the two orderings above — no code to write, just reasoning.

Part (a): Writing addClimb to Append in Insertion Order

The Rule, Broken Down

  1. Create a new ClimbInfo object from the given name and time.
  2. Add it to the very end of climbList, regardless of what's already there.
  3. The relative order of every existing entry stays exactly the same.

Step-by-Step Approach

  1. Build the new ClimbInfo object first.
  2. Call climbList.add(...) with no index — appending is the default behavior for a single-argument add.

The Code

public void addClimb(String peakName, int climbTime)
{
    ClimbInfo newClimb = new ClimbInfo(peakName, climbTime);
    climbList.add(newClimb);
}

Why It Matters

  • add(E obj) with one argument always appends to the end of a List — that's exactly the "in the order they were added" behavior the postcondition describes, with no extra logic needed.
  • A brand-new ClimbInfo object every call — the method's job is to store a climb, not look one up, so there's no need to check whether a matching peak name already exists.

Tracing the Example

Using the question's own code segment:

ClimbingClub hikerClub = new ClimbingClub();
hikerClub.addClimb("Monadnock", 274);
hikerClub.addClimb("Whiteface", 301);
hikerClub.addClimb("Algonquin", 225);
hikerClub.addClimb("Monadnock", 344);
Call climbList after the call
addClimb("Monadnock", 274) [Monadnock/274]
addClimb("Whiteface", 301) [Monadnock/274, Whiteface/301]
addClimb("Algonquin", 225) [Monadnock/274, Whiteface/301, Algonquin/225]
addClimb("Monadnock", 344) [Monadnock/274, Whiteface/301, Algonquin/225, Monadnock/344]

This matches the question's table exactly — including the repeated "Monadnock" entry landing wherever it was added, with no attempt to group it near the first one.

Common Mistakes to Avoid

  • Trying to insert in some "smart" position. Part (a) explicitly wants insertion order — sorting or grouping logic belongs in part (b), not here.
  • Modifying an existing ClimbInfo object instead of creating a new one. Each call to addClimb represents a brand-new climb record.
  • Calling add(0, newClimb) (inserting at the front) instead of the no-index add(newClimb) — this would reverse the order entirely.

Part (b): Writing addClimb to Insert in Alphabetical Order

The Rule, Broken Down

  1. climbList is guaranteed to already be in alphabetical order (by name, using String's compareTo) before this method runs.
  2. The new entry has to be inserted at whatever position keeps that order true afterward.
  3. If the new entry's name matches an existing one, it can land anywhere within that group of matching names — order within a group of duplicates doesn't matter.

Step-by-Step Approach

  1. Build the new ClimbInfo object first, same as part (a).
  2. Walk forward through climbList, starting at index 0.
  3. Keep advancing as long as the current entry's name is alphabetically less than or equal to the new name — that means the new entry still belongs after it.
  4. Stop as soon as an entry is found that's alphabetically greater than the new name, or the end of the list is reached.
  5. Insert the new ClimbInfo at whatever index the walk stopped on.

The Code

public void addClimb(String peakName, int climbTime)
{
    ClimbInfo newClimb = new ClimbInfo(peakName, climbTime);
    int index = 0;

    while (index < climbList.size() && climbList.get(index).getName().compareTo(peakName) <= 0)
    {
        index++;
    }

    climbList.add(index, newClimb);
}

Why It Matters

  • compareTo(...) <= 0, not < 0 — allowing equality to keep advancing the search is what makes the loop walk past an entire group of matching names, rather than stopping in the middle of one. Either resulting position is valid per the rule, but this way naturally lands the new entry at the end of its group.
  • add(int index, E obj) is the two-argument version of add — it inserts at a specific position and shifts everything after it one step to the right, instead of appending to the end.
  • The index < climbList.size() check comes first in the while condition (short-circuit evaluation) — if it were written second, calling .get(index) once index already equals climbList.size() would throw an exception.

Tracing the Example

Using the same four calls as part (a), starting from an empty list:

Call Search stops at index climbList after insertion
addClimb("Monadnock", 274) 0 (list empty) [Monadnock/274]
addClimb("Whiteface", 301) 1 ("Monadnock" <= "Whiteface", then end of list) [Monadnock/274, Whiteface/301]
addClimb("Algonquin", 225) 0 ("Monadnock" > "Algonquin" immediately) [Algonquin/225, Monadnock/274, Whiteface/301]
addClimb("Monadnock", 344) 2 (passes "Algonquin", passes the existing "Monadnock", stops at "Whiteface") [Algonquin/225, Monadnock/274, Monadnock/344, Whiteface/301]

The final list — "Algonquin"/225, "Monadnock"/274, "Monadnock"/344, "Whiteface"/301 — matches one of the two orders the question accepts, with the original "Monadnock"/274 entry staying ahead of the newly-inserted "Monadnock"/344.

Common Mistakes to Avoid

  • Using < instead of <= in the search condition. This still produces a valid alphabetical order, but it would insert new entries ahead of any existing entries with the same name instead of after them — either is accepted, but it's easy to get inconsistent about which one you intended.
  • Forgetting the precondition that climbList starts sorted. This method never needs to sort the whole list — it only needs to find one correct insertion point, because everything before it is already guaranteed to be in order.
  • Using climbList.add(newClimb) (no index) by copy-pasting part (a)'s code. That always appends to the end, which only works if the new name happens to be alphabetically last.

Part (c): Analyzing distinctPeakNames()

The question gives you a finished implementation of a third method and asks you to determine — not fix — whether it behaves correctly, depending on which version of addClimb built the list it's running on.

public int distinctPeakNames()
{
    if (climbList.size() == 0)
    {
        return 0;
    }

    ClimbInfo currInfo = climbList.get(0);
    String prevName = currInfo.getName();
    String currName = null;
    int numNames = 1;

    for (int k = 1; k < climbList.size(); k++)
    {
        currInfo = climbList.get(k);
        currName = currInfo.getName();

        if (prevName.compareTo(currName) != 0)
        {
            numNames++;
            prevName = currName;
        }
    }

    return numNames;
}

What the Algorithm Actually Assumes

This method never checks a name against every other name in the list — it only ever compares each entry to the one immediately before it. That only counts distinct names correctly if every group of matching names is already adjacent — otherwise the same name showing up again later, separated by other entries, looks like a brand-new name the second time around.

Testing Part (a)'s Ordering (Insertion Order)

Using part (a)'s list — "Monadnock", "Whiteface", "Algonquin", "Monadnock" — there are really only 3 distinct names, but trace the loop anyway:

k currName Compared to prevName Result
prevName = "Monadnock", numNames = 1
1 "Whiteface" differs from "Monadnock" numNames = 2, prevName = "Whiteface"
2 "Algonquin" differs from "Whiteface" numNames = 3, prevName = "Algonquin"
3 "Monadnock" differs from "Algonquin" numNames = 4, prevName = "Monadnock"

The method returns 4, not 3 — it's wrong. The second "Monadnock" isn't next to the first one, so the "compare only to the previous entry" trick never notices they're the same name. The answer to part (c)(i) is NO.

Testing Part (b)'s Ordering (Alphabetical Order)

Using part (b)'s list — "Algonquin", "Monadnock", "Monadnock", "Whiteface" — the two "Monadnock" entries are guaranteed to sit next to each other, because the list is sorted:

k currName Compared to prevName Result
prevName = "Algonquin", numNames = 1
1 "Monadnock" differs from "Algonquin" numNames = 2, prevName = "Monadnock"
2 "Monadnock" same as prevName no change
3 "Whiteface" differs from "Monadnock" numNames = 3, prevName = "Whiteface"

The method returns 3, the correct count. Since alphabetical order always keeps identical names grouped together, this works for any list built with part (b)'s addClimb, not just this specific example. The answer to part (c)(ii) is YES.

Key Takeaways

  • A loop that only compares an element to its immediate neighbor is only as reliable as the assumption that matching elements are already adjacent — verify that assumption before trusting the algorithm.
  • Sorting isn't just for display: it's also what makes "count runs of duplicates" algorithms like this one correct in the first place.
  • Inserting into a sorted List means searching for the first position where the new value would come before what's already there, then using the two-argument add(index, obj) — not the single-argument version, which only ever appends.

Related FRQs