FuelDepot: 2011 FRQ 3
A step-by-step solution to the 2011 AP CSA FRQ 3 (FuelDepot), covering scanning a list for the lowest value under a threshold and directing a robot's movement along an indexed line in Java.
A line of fuel tanks and a robot that can only move forward in whatever direction it's currently facing set up this AP Computer Science A free-response question — one method searches a list for the right tank to fill next, and the other has to reason about direction before it can move at all.
What This FRQ Tests
- AP CSA units: Unit 7 (ArrayList/List) and Unit 3 (Boolean Expressions and if Statements)
- Core skill: scanning a list to find the index of the smallest value that also satisfies a filter condition
- Secondary skill: translating a target position into "which way do I need to turn, and how far do I move" logic based on relative position
- Official category: "Array/ArrayList" — always FRQ 3 on the AP CSA exam
The Setup
- The given
FuelTankinterface provides:int getFuelLevel()— a value from 0 (empty) to 100 (full)
- The given
FuelRobotinterface provides:int getCurrentIndex()— the robot's current positionboolean isFacingRight()— is it facing toward larger indexes?void changeDirection()— flips which way it's facingvoid moveForward(int numLocs)— moves it forward in its current direction (precondition:numLocs > 0)
FuelDepotholds two fields:private FuelRobot fillerprivate List<FuelTank> tanks
- You're asked to write two methods:
nextTankToFill(int threshold)— finds the index of the tank that should be filled nextmoveToLocation(int locIndex)— moves the robot to a specific tank's location
Part (a): Writing nextTankToFill(int threshold)
The Rule, Broken Down
- Among tanks with a fuel level less than or equal to
threshold, return the index of the one with the lowest fuel level. - If more than one tank ties for that lowest level, any of their indexes is an acceptable answer.
- If no tank qualifies at all, return the robot's own current index instead.
- The robot's state must not change — this method only looks, it never moves anything.
Step-by-Step Approach
- Track the best qualifying index found so far, starting with "nothing found yet."
- Loop over every tank by index.
- Skip any tank whose fuel level is above
threshold. - Among the tanks that qualify, keep whichever has a strictly lower fuel level than the best found so far.
- After the loop, if nothing ever qualified, return
filler.getCurrentIndex(); otherwise return the best index found.
The Code
public int nextTankToFill(int threshold)
{
int bestIndex = -1;
int bestLevel = 0;
for (int i = 0; i < tanks.size(); i++)
{
int level = tanks.get(i).getFuelLevel();
if (level <= threshold)
{
if (bestIndex == -1 || level < bestLevel)
{
bestIndex = i;
bestLevel = level;
}
}
}
if (bestIndex == -1)
{
return filler.getCurrentIndex();
}
return bestIndex;
}
Why Each Piece Matters
bestIndex == -1as a sentinel represents "no qualifying tank found yet," distinct from a real, legitimate index0. Without it, there'd be no clean way to tell "tank 0 qualified" apart from "nothing has qualified so far."- Strict
level < bestLevel, not<=, when updating the best means the first tank found at a given minimum level is the one kept if there's a tie — a valid choice, since the rule allows returning any tied index. - Only reading
filler.getCurrentIndex(), never callingchangeDirection()ormoveForward(...)is what satisfies the postcondition that the robot's state doesn't change.
Tracing the Example
Using the question's own tanks (indexes 0-6, levels 20 30 80 55 50 75 20) and a robot at index 2:
threshold |
Walking the tanks | Result |
|---|---|---|
| 50 | Index 0 (level 20) qualifies and becomes the best; index 6 (also level 20) ties but doesn't beat it since 20 < 20 is false |
0 (the question accepts 0 or 6) |
| 15 | No tank has a level <= 15 (the lowest is 20), so bestIndex stays -1 |
2 — the robot's current index |
Both results match the question's table exactly.
Common Mistakes to Avoid
- Comparing with
<instead of<=againstthreshold, which would wrongly exclude a tank whose level is exactly equal to the threshold. - Initializing
bestLevelto a fixed guess like100without a "found" flag. This happens to work numerically here since 100 is the maximum possible fuel level, but it's a fragile habit — the-1sentinel approach generalizes more safely to problems where you don't know the value's range in advance. - Forgetting the "nothing qualified" case and returning something like
-1or0directly instead offiller.getCurrentIndex(). - Calling any
FuelRobotmethod that changes state (changeDirection()ormoveForward(...)) inside this method — the postcondition explicitly forbids it.
Part (b): Writing moveToLocation(int locIndex)
The Rule, Broken Down
- The robot can only move forward, in whichever direction it's currently facing — reaching a tank behind it means turning around first.
locIndexmight be to the right (a larger index) or the left (a smaller index) of the robot's current position.moveForwardrequiresnumLocs > 0, so the robot must never be told to move when it's already at the destination.
Step-by-Step Approach
- Get the robot's current index.
- If the destination is to the right and the robot isn't already facing right, turn it around; then move forward by the distance to the right.
- Otherwise, if the destination is to the left and the robot is facing right, turn it around; then move forward by the distance to the left.
- If the destination is exactly where the robot already is, do nothing.
The Code
public void moveToLocation(int locIndex)
{
int current = filler.getCurrentIndex();
if (locIndex > current)
{
if (!filler.isFacingRight())
{
filler.changeDirection();
}
filler.moveForward(locIndex - current);
}
else if (locIndex < current)
{
if (filler.isFacingRight())
{
filler.changeDirection();
}
filler.moveForward(current - locIndex);
}
}
Why Each Piece Matters
- Checking
isFacingRight()before conditionally callingchangeDirection()avoids flipping the robot's direction unnecessarily when it's already facing the way it needs to. else if, not a separateif, for the "move left" case means the exact-match case (locIndex == current) falls through both branches and does nothing — safely respectingmoveForward'snumLocs > 0precondition instead of ever callingmoveForward(0).- Subtracting in the direction-appropriate order (
locIndex - currentwhen moving right,current - locIndexwhen moving left) keeps the distance passed tomoveForwardpositive either way, since it always moves in whatever direction the robot is currently facing.
Tracing the Example
The released FRQ doesn't provide a numbered before/after table for moveToLocation the way it does for nextTankToFill — so this trace uses the depot pictured at the start of the question (6 tanks, robot at index 2, facing right) as an illustrative walk-through rather than an official worked example:
moveToLocation(5):5 > 2, so this is a rightward move. The robot is already facing right, sochangeDirection()is skipped.moveForward(5 - 2)→moveForward(3)moves it to index 5.moveToLocation(0):0 < 2, so this is a leftward move. The robot is facing right, sochangeDirection()flips it to facing left first.moveForward(2 - 0)→moveForward(2)moves it to index 0.moveToLocation(2):2 == 2, so neither branch runs — the robot stays exactly where it is, andmoveForwardis never called with an invalid0.
Common Mistakes to Avoid
- Always calling
changeDirection()regardless of the robot's current facing — this flips it the wrong way on every other call instead of only when needed. - Using a bare
elseinstead ofelse if, which risks callingmoveForward(0)whenlocIndex == current, violating the statednumLocs > 0precondition. - Subtracting in the wrong order (e.g.
locIndex - currenteven when moving left), which produces a negative distance and also violates the precondition. - Forgetting the exact-match case entirely, assuming
moveToLocationwill never be called with the robot's current index.
Key Takeaways
- Scanning a list for an extreme value that also passes a filter condition is a two-check loop pattern: test the filter first, then compare against the best result found so far.
- A sentinel value that no real index can ever be (like
-1) is the standard way to represent "nothing found yet," distinct from a legitimate index0. - Whenever an action has a precondition like "must be positive," structure the surrounding logic (with
if/else if, not separateifs) so it's impossible to call that action with an invalid value — including the "we're already there" case.