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
Listat 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
ClimbInfoclass (you don't write this one) has:- A constructor:
ClimbInfo(String peakName, int climbTime) String getName()andint getTime()— getters for the peak's name and the climb time
- A constructor:
- The
ClimbingClubclass holds:private List<ClimbInfo> climbList— guaranteed notnull, containing only non-nullreferences
- 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
- Create a new
ClimbInfoobject from the given name and time. - Add it to the very end of
climbList, regardless of what's already there. - The relative order of every existing entry stays exactly the same.
Step-by-Step Approach
- Build the new
ClimbInfoobject first. - Call
climbList.add(...)with no index — appending is the default behavior for a single-argumentadd.
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 aList— that's exactly the "in the order they were added" behavior the postcondition describes, with no extra logic needed.- A brand-new
ClimbInfoobject 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
ClimbInfoobject instead of creating a new one. Each call toaddClimbrepresents a brand-new climb record. - Calling
add(0, newClimb)(inserting at the front) instead of the no-indexadd(newClimb)— this would reverse the order entirely.
Part (b): Writing addClimb to Insert in Alphabetical Order
The Rule, Broken Down
climbListis guaranteed to already be in alphabetical order (by name, usingString'scompareTo) before this method runs.- The new entry has to be inserted at whatever position keeps that order true afterward.
- 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
- Build the new
ClimbInfoobject first, same as part (a). - Walk forward through
climbList, starting at index0. - 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.
- Stop as soon as an entry is found that's alphabetically greater than the new name, or the end of the list is reached.
- Insert the new
ClimbInfoat 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 ofadd— 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 thewhilecondition (short-circuit evaluation) — if it were written second, calling.get(index)onceindexalready equalsclimbList.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
climbListstarts 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
Listmeans searching for the first position where the new value would come before what's already there, then using the two-argumentadd(index, obj)— not the single-argument version, which only ever appends.