CompSci.rocks
FRQcsapa

Feeder: 2024 FRQ 1

A step-by-step solution to the 2024 AP CSA FRQ 1 (Feeder), covering weighted random simulation and using a helper method to count successful trials in Java.

A bird feeder that occasionally gets raided by a bear sits behind this AP Computer Science A free-response question — you simulate a single day of eating first, then run that simulation across many days and count how often there was actually food to find.

What This FRQ Tests

  • AP CSA units: Unit 3 (Boolean Expressions and if Statements), Unit 4 (Iteration), and Unit 5 (Writing Classes/methods)
  • Core skill: using Math.random() to model two different kinds of randomness in the same method — a rare "abnormal" event, and a normal, evenly-distributed amount
  • Secondary skill: calling a helper method inside a loop and using its side effect (rather than its return value) to decide what to count
  • Official category: "Methods and Control Structures" — always FRQ 1 on the AP CSA exam

The Setup

  • Feeder holds:
    • private int currentFood — grams of food currently in the feeder, never negative
  • You're asked to write two methods:
    • void simulateOneDay(int numBirds) — simulates one day of eating and updates currentFood
    • int simulateManyDays(int numBirds, int numDays) — runs simulateOneDay for numDays days and counts how many of them found food

Part (a): Writing simulateOneDay(int numBirds)

The Rule, Broken Down

  1. 5% of the time (abnormal conditions): a bear empties the feeder completely — currentFood becomes 0.
  2. 95% of the time (normal conditions): every bird eats the same random amount, somewhere from 10 to 50 grams inclusive, with every whole-gram value equally likely.
  3. The total eaten is numBirds times that per-bird amount.
  4. If that total is more than what's currently in the feeder, the birds simply empty it (currentFood becomes 0) rather than going negative.
  5. Otherwise, subtract the total from currentFood as normal.

Step-by-Step Approach

  1. Roll a single random check for the 5% bear scenario: Math.random() < 0.05.
  2. If that's true, set currentFood to 0 and stop — nothing else needs to happen this day.
  3. Otherwise, generate one random per-bird amount from 10 to 50 inclusive (41 possible whole values).
  4. Multiply that by numBirds to get the total eaten.
  5. Compare the total to currentFood: if it's more, set currentFood to 0; if not, subtract it.

The Code

public void simulateOneDay(int numBirds)
{
    if (Math.random() < 0.05)
    {
        currentFood = 0;
    }
    else
    {
        int perBird = (int) (Math.random() * 41) + 10;
        int totalEaten = perBird * numBirds;

        if (totalEaten > currentFood)
        {
            currentFood = 0;
        }
        else
        {
            currentFood = currentFood - totalEaten;
        }
    }
}

Why Each Piece Matters

  • Math.random() < 0.05 — since Math.random() is uniform over [0.0, 1.0), the sub-range [0.0, 0.05) covers exactly 5% of that interval, giving the bear scenario exactly a 5% chance.
  • (int) (Math.random() * 41) + 10Math.random() * 41 produces a value in [0.0, 41.0); truncating gives a whole number from 0 to 40 (41 possibilities); adding 10 shifts that range to 10 through 50, matching "10 to 50 grams inclusive" exactly.
  • One random amount shared by every bird — the problem says each bird eats the same amount that day, so perBird is generated once and multiplied by numBirds, not generated separately per bird.
  • totalEaten > currentFood, not >= — if the total eaten exactly equals what's left, the feeder ends the day at exactly 0 either way; the > vs >= distinction only matters for correctly landing on 0 instead of going negative when the total exceeds what's available.

Common Mistakes to Avoid

  • Checking the 5% condition with the wrong comparison direction (e.g. Math.random() > 0.05 gives the bear a 95% chance instead of 5%).
  • Generating a new random amount per bird instead of one shared amount for the whole day — the problem is explicit that every bird eats the same amount on a given day.
  • Using the wrong random range, e.g. (int) (Math.random() * 50) + 10, which would allow values up to 59 instead of stopping at 50.
  • Letting currentFood go negative by subtracting totalEaten unconditionally instead of checking against currentFood first.

Part (b): Writing simulateManyDays(int numBirds, int numDays)

The Rule, Broken Down

  1. Simulate numDays days in a row using simulateOneDay.
  2. A day counts as one where "food was found" if there was food in the feeder before that day's simulation ran.
  3. Return how many of the numDays days met that condition.

Step-by-Step Approach

  1. Track a running count, starting at 0.
  2. Loop numDays times.
  3. Each iteration, check currentFood before calling simulateOneDay — if it's greater than 0, increment the count.
  4. Call simulateOneDay(numBirds) to advance to the next day.
  5. After the loop, return the count.

The Code

public int simulateManyDays(int numBirds, int numDays)
{
    int count = 0;

    for (int day = 0; day < numDays; day++)
    {
        if (currentFood > 0)
        {
            count++;
        }

        simulateOneDay(numBirds);
    }

    return count;
}

Why Each Piece Matters

  • Checking currentFood before calling simulateOneDay — once the feeder is empty, no later call can "find" food that isn't there, so the only meaningful moment to check is right before that day's simulation runs.
  • Calling simulateOneDay once per iteration, unconditionally, after the check — the day still happens (and food amounts still change) regardless of whether it counted, since simulateOneDay is what actually advances the simulation.
  • Not checking currentFood again after the call — the problem only cares whether food was available going into the day, not what's left afterward.

Tracing the Example

Using the second example from the question — currentFood starts at 250, simulateManyDays(10, 5):

Day currentFood before Counted? currentFood after
1 250 yes 150
2 150 yes 0
3 0 no 0
4 0 no 0
5 0 no 0

Final count: 2 — matches the question exactly, and once currentFood hits 0 on day 2, every remaining day correctly stops counting even though simulateOneDay still runs on each of them.

Common Mistakes to Avoid

  • Checking currentFood after calling simulateOneDay instead of before — this would incorrectly count a day where the feeder started at 0 and stayed at 0, or miss counting the very last day the feeder still had food before being emptied.
  • Not calling simulateOneDay at all when currentFood is already 0. The day still needs to run (in case a future part of a larger simulation depended on it), even though it won't count.
  • Off-by-one on the loop, running numDays + 1 or numDays - 1 iterations instead of exactly numDays.

Key Takeaways

  • Modeling "two different random outcomes with different probabilities" is a nested pattern: check the rare case first with its own probability, and only compute the common case's details in the else branch.
  • "Did something happen on this step" almost always needs to be checked based on the state before the step runs, not after.
  • Calling a helper method inside a loop and using its side effect (the state it changes) rather than a return value is a common pattern once a method's job is to update, not compute.

Related FRQs