CompSci.rocks
FRQcsapa

SelfDivisor: 2007 FRQ 1

A step-by-step solution to the 2007 AP CSA FRQ 1 (SelfDivisor), covering digit extraction with % and /, and building an array of results one match at a time in Java.

Whether every digit of a number evenly divides that same number is the puzzle behind this AP Computer Science A free-response question — first testing a single number, then hunting through consecutive integers to collect a whole array of numbers that pass the test.

What This FRQ Tests

  • AP CSA units: Unit 3 (Boolean Expressions and if Statements) and Unit 4 (Iteration)
  • Core skill: extracting individual digits from an int using % and /, instead of converting the number to a String
  • Secondary skill: filling an array one element at a time as matching values are found, rather than knowing in advance exactly which values will end up in it
  • Official category: "Methods and Control Structures" — always FRQ 1 on the AP CSA exam

The Setup

  • No class skeleton beyond two method stubs to fill in on the SelfDivisor class:
    • static boolean isSelfDivisor(int number) — is number a self-divisor?
    • static int[] firstNumSelfDivisors(int start, int num) — an array of the first num self-divisors that are >= start
  • A number is a self-divisor if every one of its decimal digits evenly divides the number itself (128 qualifies: it's divisible by 1, 2, and 8).
  • A digit of 0 automatically disqualifies a number — 0 is never considered a divisor of anything, so any number containing a 0 digit fails immediately.

Part (a): Writing isSelfDivisor(int number)

The Rule, Broken Down

  1. Look at every digit of number, one at a time.
  2. If any digit is 0, the number is not a self-divisor — stop immediately.
  3. If any digit doesn't evenly divide number (number % digit != 0), the number is not a self-divisor.
  4. If every digit passes both checks, the number is a self-divisor.

Step-by-Step Approach

  1. Copy number into a separate loop variable — it needs to shrink as digits get peeled off, but the original value is still needed for every divisibility check.
  2. While that loop variable is still positive, pull off its last digit with % 10.
  3. Check that digit: if it's 0, or if number % digit isn't 0, return false right away.
  4. Otherwise, strip the digit off with integer division by 10, and repeat.
  5. If the loop finishes without ever returning false, every digit passed — return true.

The Code

public static boolean isSelfDivisor(int number)
{
    int remaining = number;

    while (remaining > 0)
    {
        int digit = remaining % 10;

        if (digit == 0 || number % digit != 0)
        {
            return false;
        }

        remaining = remaining / 10;
    }

    return true;
}

Why Each Piece Matters

  • remaining % 10 — pulls off the current last digit of a number (128 % 10 is 8).
  • remaining / 10 (integer division) — drops that last digit (128 / 10 is 12), so the next loop pass looks at the digit before it.
  • number % digit, not remaining % digit — the divisibility rule always compares against the original full number; only the digit-peeling itself walks through the shrinking remaining copy.
  • digit == 0 checked first, with || short-circuiting — this skips ever evaluating number % digit when digit is 0, which would otherwise throw an ArithmeticException (division by zero).

Tracing the Example

  • 128: peel 8128 % 8 == 0 ✓; peel 2128 % 2 == 0 ✓; peel 1128 % 1 == 0 ✓; loop ends → returns true, matching the question's own example.
  • 26: peel 626 % 6 is 2, not 0 → returns false immediately, exactly as the question states ("not evenly divisible by the digit 6"). The digit 2 is never even reached.
  • 105 (illustrating the "any 0 digit disqualifies" rule from the prompt): peel 5105 % 5 == 0 ✓; peel 0digit == 0 is true → returns false immediately, without ever computing 105 % 0.

Common Mistakes to Avoid

  • Checking divisibility against remaining instead of number on later iterations — after digits have been stripped away, remaining is a smaller, different number, and the rule is specifically about dividing the original number.
  • Forgetting to special-case a 0 digit before computing number % digit, which throws an ArithmeticException instead of correctly returning false.
  • Using remaining >= 0 as the loop condition instead of remaining > 0. Once every real digit has been peeled off, integer division leaves remaining at exactly 0. An extra pass with >= 0 would then compute digit = 0 % 10, which is 0 — and the digit == 0 check would incorrectly return false for every number, self-divisor or not.
  • Mixing up % and / — swapping them breaks both the digit-extraction step and the digit-removal step at once.

Part (b): Writing firstNumSelfDivisors(int start, int num)

The Rule, Broken Down

  1. Search integers starting at start and counting upward, one at a time.
  2. Test each candidate with isSelfDivisor — assume it works correctly, regardless of what was written in part (a).
  3. Collect the first num candidates that pass, in increasing order, into an array of exactly that size.

Step-by-Step Approach

  1. Create an int[] of length num to hold the results.
  2. Track two separate things: how many self-divisors have been found so far (starting at 0), and the current candidate number being tested (starting at start).
  3. Loop until the results array is completely full.
  4. Each pass: test the candidate. If it's a self-divisor, store it in the next open array slot and advance the found-count.
  5. Move to the next integer either way, whether or not the candidate qualified.
  6. Once the array is full, return it.

The Code

public static int[] firstNumSelfDivisors(int start, int num)
{
    int[] result = new int[num];
    int count = 0;
    int candidate = start;

    while (count < num)
    {
        if (isSelfDivisor(candidate))
        {
            result[count] = candidate;
            count = count + 1;
        }

        candidate = candidate + 1;
    }

    return result;
}

Why Each Piece Matters

  • new int[num] — the array's size is fixed at exactly num elements up front, since that's the size the method is required to return.
  • count, kept separate from candidate — two different counters are needed: candidate walks upward through every integer in order, but count only advances when a self-divisor is actually found, since most candidates won't qualify.
  • candidate = candidate + 1 sits outside the if — every candidate gets tested and moved past, whether or not it turned out to be a self-divisor.
  • Calling isSelfDivisor(candidate), not re-checking digits here — the question explicitly allows (and expects) reusing the already-specified method rather than duplicating its logic.

Tracing the Example

Using the question's own example, firstNumSelfDivisors(10, 3):

Candidate isSelfDivisor? Why Stored at index
10 false contains digit 0
11 true 11 % 1 == 0 twice 0
12 true 12 % 1 == 0, 12 % 2 == 0 1
13 false 13 % 3 is 1
14 false 14 % 4 is 2
15 true 15 % 1 == 0, 15 % 5 == 0 2

count reaches 3 right after 15 is stored, so the loop stops there. Final result: {11, 12, 15} — matching the question's stated answer exactly.

Common Mistakes to Avoid

  • Looping over a fixed range of candidates (e.g. for (int c = start; c < start + num; c++)) instead of looping until num self-divisors are actually found. Self-divisors aren't evenly spaced, so a fixed-size range starting at start can easily come up short of num matches.
  • Advancing count on every iteration, instead of only when a self-divisor is found — this leaves gaps of 0 (an int array's default value) inside the result instead of packing matches in order.
  • Getting the array size wrong — it must be exactly num, not num + 1 or num - 1.
  • Reimplementing the digit-checking logic inline instead of calling isSelfDivisor. It can produce the same result, but it's exactly the kind of "significant amounts of code that can be replaced by a call to one of these methods" the question's own directions warn may not receive full credit.

Key Takeaways

  • Peeling digits off an int with % 10 and / 10 is the standard pattern for digit-by-digit work — no String conversion required.
  • A rule that depends on the whole original number needs every check to reference that original value, never a shrinking copy used for iteration.
  • "Find the first N results that satisfy a condition" always needs two independent counters: one that scans every candidate in order, and one that only advances when a match is actually found.

Related FRQs