CompSci.rocks
FRQcsapa

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 FuelTank interface provides:
    • int getFuelLevel() — a value from 0 (empty) to 100 (full)
  • The given FuelRobot interface provides:
    • int getCurrentIndex() — the robot's current position
    • boolean isFacingRight() — is it facing toward larger indexes?
    • void changeDirection() — flips which way it's facing
    • void moveForward(int numLocs) — moves it forward in its current direction (precondition: numLocs > 0)
  • FuelDepot holds two fields:
    • private FuelRobot filler
    • private List<FuelTank> tanks
  • You're asked to write two methods:
    • nextTankToFill(int threshold) — finds the index of the tank that should be filled next
    • moveToLocation(int locIndex) — moves the robot to a specific tank's location

Part (a): Writing nextTankToFill(int threshold)

The Rule, Broken Down

  1. Among tanks with a fuel level less than or equal to threshold, return the index of the one with the lowest fuel level.
  2. If more than one tank ties for that lowest level, any of their indexes is an acceptable answer.
  3. If no tank qualifies at all, return the robot's own current index instead.
  4. The robot's state must not change — this method only looks, it never moves anything.

Step-by-Step Approach

  1. Track the best qualifying index found so far, starting with "nothing found yet."
  2. Loop over every tank by index.
  3. Skip any tank whose fuel level is above threshold.
  4. Among the tanks that qualify, keep whichever has a strictly lower fuel level than the best found so far.
  5. 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 == -1 as a sentinel represents "no qualifying tank found yet," distinct from a real, legitimate index 0. 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 calling changeDirection() or moveForward(...) 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 <= against threshold, which would wrongly exclude a tank whose level is exactly equal to the threshold.
  • Initializing bestLevel to a fixed guess like 100 without a "found" flag. This happens to work numerically here since 100 is the maximum possible fuel level, but it's a fragile habit — the -1 sentinel 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 -1 or 0 directly instead of filler.getCurrentIndex().
  • Calling any FuelRobot method that changes state (changeDirection() or moveForward(...)) inside this method — the postcondition explicitly forbids it.

Part (b): Writing moveToLocation(int locIndex)

The Rule, Broken Down

  1. The robot can only move forward, in whichever direction it's currently facing — reaching a tank behind it means turning around first.
  2. locIndex might be to the right (a larger index) or the left (a smaller index) of the robot's current position.
  3. moveForward requires numLocs > 0, so the robot must never be told to move when it's already at the destination.

Step-by-Step Approach

  1. Get the robot's current index.
  2. 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.
  3. 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.
  4. 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 calling changeDirection() avoids flipping the robot's direction unnecessarily when it's already facing the way it needs to.
  • else if, not a separate if, for the "move left" case means the exact-match case (locIndex == current) falls through both branches and does nothing — safely respecting moveForward's numLocs > 0 precondition instead of ever calling moveForward(0).
  • Subtracting in the direction-appropriate order (locIndex - current when moving right, current - locIndex when moving left) keeps the distance passed to moveForward positive 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, so changeDirection() 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, so changeDirection() 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, and moveForward is never called with an invalid 0.

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 else instead of else if, which risks calling moveForward(0) when locIndex == current, violating the stated numLocs > 0 precondition.
  • Subtracting in the wrong order (e.g. locIndex - current even when moving left), which produces a negative distance and also violates the precondition.
  • Forgetting the exact-match case entirely, assuming moveToLocation will 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 index 0.
  • Whenever an action has a precondition like "must be positive," structure the surrounding logic (with if/else if, not separate ifs) so it's impossible to call that action with an invalid value — including the "we're already there" case.

Related FRQs