CompSci.rocks
FRQcsapa

HorseBarn: 2012 FRQ 3

A step-by-step solution to the 2012 AP CSA FRQ 3 (HorseBarn), covering linear search through an array of interface references and compacting an array in place while preserving order in Java.

Managing numbered stalls in a horse barn gives this AP Computer Science A free-response question its shape — one method searches a plain array for a horse by name, and the other squeezes every gap out of that same array without disturbing the order anyone's left in.

What This FRQ Tests

  • AP CSA units: Unit 6 (Array), Unit 3 (Boolean Expressions and if Statements), and Unit 4 (Iteration)
  • Core skill: a linear search through an array that has to skip over empty (null) slots
  • Secondary skill: shifting elements within an array in place to remove gaps, while keeping every remaining element in its original relative order
  • Official category: "Array/ArrayList" — specifically array-based here, since spaces is declared as a plain Horse[] rather than an ArrayList. This happens to land in the FRQ 3 slot that Array/ArrayList questions occupy on today's exams, though that fixed numbering pattern wasn't standardized until the 2019–2020 redesign, so it's coincidence rather than a rule that applied in 2012.

The Setup

  • The given Horse interface (implemented elsewhere, not written here) declares:
    • String getName()
    • int getWeight()
  • The HorseBarn class holds:
    • private Horse[] spaces — each element either references a Horse occupying that stall, or is null for an empty stall. No two horses in the barn share a name.
  • You're asked to write two unrelated methods:
    • findHorseSpace(String name) — returns the index holding the horse with that name, or -1 if none exists
    • consolidate() — moves every horse toward the front of the array, closing all gaps, without changing their relative order

Part (a): Writing findHorseSpace(String name)

The Rule, Broken Down

  1. Search every index of spaces for a horse whose name matches the one given.
  2. Empty (null) stalls never match anything — they have to be skipped, not compared.
  3. Return the index of the first (and, since names are unique, only) match.
  4. If nothing matches after checking every stall, return -1.

Step-by-Step Approach

  1. Loop over every index of spaces.
  2. At each index, first check whether the stall is occupied at all (!= null) — only then is it safe to call a method on it.
  3. If it's occupied, compare its horse's name to the target name with .equals().
  4. On a match, return that index immediately.
  5. If the loop finishes with no match found, return -1.

The Code

public int findHorseSpace(String name)
{
    for (int i = 0; i < spaces.length; i++)
    {
        if (spaces[i] != null && spaces[i].getName().equals(name))
        {
            return i;
        }
    }

    return -1;
}

Why Each Piece Matters

  • spaces[i] != null && comes first, before .getName() is ever called — Java's && short-circuits, so the second half never runs when the first half is false. Without this order (or without the null check at all), calling .getName() on an empty slot would throw a NullPointerException.
  • .equals(name), never == — two separately-built Strings with identical characters aren't guaranteed to be the same object in memory, so == risks a false negative even when the names genuinely match.
  • Returning immediately inside the loop, rather than storing the result and returning after the loop, works cleanly here because the precondition guarantees at most one match exists — there's nothing left to find once one is located.

Tracing the Example

Using the question's own sweetHome barn — spaces holding "Trigger" (0), empty (1), "Silver" (2), "Lady" (3), empty (4), "Patches" (5), "Duke" (6):

Call What happens Returned
sweetHome.findHorseSpace("Trigger") matches immediately at index 0 0
sweetHome.findHorseSpace("Silver") skips index 0 (no match), skips index 1 (null), matches at index 2 2
sweetHome.findHorseSpace("Coco") checks every index, no match anywhere -1

All three match the question's table exactly.

Common Mistakes to Avoid

  • Skipping the null check entirely. Any barn with even one empty stall throws a NullPointerException the moment the loop reaches it.
  • Comparing names with == instead of .equals(). This is one of the most common AP CSA point losses anywhere String comparison shows up.
  • Continuing to search after finding a match, or using a separate "found" flag instead of returning right away — unnecessary complexity given the "no two horses share a name" precondition.

Part (b): Writing consolidate()

The Rule, Broken Down

  1. Every horse currently in spaces has to end up packed into the front of the array — indices 0, 1, 2, ... with no empty stall in between any two horses.
  2. The horses' relative order must stay exactly the same as it was before consolidating.
  3. Whatever stalls are left over at the end become empty (null).

Step-by-Step Approach

  1. Keep a second index, dest, tracking the next open "front" position to fill — start it at 0.
  2. Scan through spaces from the beginning with a regular loop index i.
  3. Whenever spaces[i] holds a horse, that horse belongs at dest next. If dest and i aren't already the same position, move it there and clear the old slot.
  4. Either way, advance dest by one every time a horse is placed — that's the next open front slot for the next horse found.
  5. Empty slots (spaces[i] == null) are simply skipped; dest doesn't move for them.

The Code

public void consolidate()
{
    int dest = 0;

    for (int i = 0; i < spaces.length; i++)
    {
        if (spaces[i] != null)
        {
            if (dest != i)
            {
                spaces[dest] = spaces[i];
                spaces[i] = null;
            }

            dest++;
        }
    }
}

Why Each Piece Matters

  • dest only ever moves forward, and only when a horse is placed — since horses are scanned left to right and always dropped in the same left-to-right order at the front, their relative order is automatically preserved without any extra bookkeeping.
  • dest can never get ahead of i. dest only increases when a horse is found, and i always increases every iteration regardless — so dest <= i at all times, meaning spaces[i] is always read before it could possibly be overwritten by an assignment to spaces[dest].
  • The dest != i check avoids a pointless self-assignment (and, more importantly, avoids nulling out a slot that hadn't actually moved) whenever a horse is already sitting exactly where it needs to be.
  • spaces[i] = null after moving a horse — without this, the horse would appear to occupy two stalls at once until a later horse happens to overwrite the old slot.

Tracing the Example

Using the question's own barn — "Trigger" (0), empty (1), "Silver" (2), empty (3), empty (4), "Patches" (5), "Duke" (6):

i spaces[i] Action dest after
0 "Trigger" dest == i (0 == 0), no move needed 1
1 empty skipped 1
2 "Silver" move to index 1, clear index 2 2
3 empty skipped 2
4 empty skipped 2
5 "Patches" move to index 2, clear index 5 3
6 "Duke" move to index 3, clear index 6 4

Final array: "Trigger", "Silver", "Patches", "Duke", null, null, null — matching the question's expected result exactly, with all four horses still in their original relative order.

Common Mistakes to Avoid

  • Forgetting to null out the old slot after moving a horse. Without it, a horse can appear to occupy its old stall and its new one simultaneously until something else overwrites the leftover reference.
  • Reordering the horses accidentally, e.g. by scanning backward or using a nested loop that repeatedly finds "the next horse" without tracking where the last one landed — the straightforward single forward pass with a dest pointer is what guarantees order is preserved for free.
  • Advancing dest even for empty slots. dest should only move when a horse is actually placed — advancing it for every index would leave gaps in the front of the array instead of closing them.

Key Takeaways

  • A linear search through an array of possibly-null references always needs the null check before calling a method on that element, combined with &&'s short-circuit behavior so the method call never happens on a null reference.
  • "Compact an array while preserving order" is a classic two-pointer pattern: a scanning index that visits every position, and a separate destination index that only advances when something is actually placed.
  • Because the destination pointer in a compaction pass never moves faster than the scanning pointer, elements are always read before they could be overwritten — the whole operation is safe to do in place, without needing a second array.

Related FRQs