Problem overview
The function magic_number takes a positive i32 and adds its digits. If the sum still has more than one digit, it adds the digits of the sum, and it repeats until a single digit remains. That digit is the result.
For n = 39: 3 + 9 = 12, then 1 + 2 = 3, so the answer is 3. For n = 928435: 9 + 2 + 8 + 4 + 3 + 5 = 31, then 3 + 1 = 4, so the answer is 4.
Approach: split the work in two
The task has two layers. The inner layer is "add up the digits of a number". The outer layer is "do that again and again until one digit is left". Keeping them in separate functions makes each one small.
The outer loop is safe because a number with two or more digits always shrinks when its digits are added. 39 becomes 12, and 99 becomes 18. The value gets smaller on every round, and it can't go below 1 for a positive number, so the loop must stop.
For the inner layer, two integer operations peel digits off a number. n % 10 is the last digit, and n / 10 is the number with that last digit removed, because dividing two integers in Rust drops the fractional part. Repeating both until n reaches 0 visits every digit once.
Step by step
The lesson writes magic_number first, assumes the helper exists, and then writes the helper.
- Loop while
nhas at least two digits. A number has two or more digits exactly when it is greater than 9, so the condition isn > 9. The parameter is declaredmut nso the function may reassign it. - Replace
nby its digit sum. Inside the loop, callSelf::digit_sum(n)and assign the result back ton. The helper lives in the sameimplblock and takes noself, so it is called throughSelf::. - Return
nafter the loop. It is the function's last expression, with no semicolon. Returning inside the loop would stop after the first round. - Write
digit_sumabovemagic_number. It takes ani32and returns ani32. The parameter is declaredmut nbecause the function strips digits off its own copy of the number. The caller's variable is not affected. - Declare
sumwithlet mut, starting at 0. Rust infersi32from how it is used. - Loop while digits remain. A number still has a digit left exactly when it is greater than 0.
- Add the last digit:
sum += n % 10. - Remove the last digit:
n /= 10. - Return
sumafter the loop, again as a final expression.
Tracing digit_sum(39): n is 39, so sum becomes 9 and n becomes 3. Then sum becomes 12 and n becomes 0, and the loop ends. The result is 12. A second round, digit_sum(12), gives 3.
The solution in Rust
impl Solution {
fn digit_sum(mut n: i32) -> i32 {
let mut sum = 0;
while n > 0 {
sum += n % 10;
n /= 10;
}
sum
}
fn magic_number(mut n: i32) -> i32 {
while n > 9 {
n = Self::digit_sum(n);
}
n
}
}
Rust details worth knowing:
muton a parameter. Parameters are immutable by default. Withoutmut,n /= 10fails with "cannot assign to immutable argumentn". The keyword only affects the function's own copy: ani32is copied into the call, so the caller's value stays the same.- The final expression.
sumandnon their own lines have no semicolon, so they are the return values. Writingsum;turns the line into a statement, the function then ends with no value, and the compiler reports mismatched types: it expectedi32and found(). Self::digit_sum. Calling a function of the sameimplblock by its bare name does not compile: the compiler says it cannot find the function in this scope and suggestsSelf::.- Integer division.
/on twoi32values gives ani32, discarding the remainder, so12 / 10is 1. No casting or rounding call is needed. - No overflow here. The digit sum of any
i32is below 100 (1999999999 gives 82), so the sums are always small.
Complexity
Let d be the number of digits of n.
Time: O(d) for the first round. digit_sum removes one digit per pass, so it makes d passes. After that the number is at most 9 * d, so the later rounds are far cheaper. For an i32, d is at most 10, so the whole function does a handful of operations.
Space: O(1). A few integers, no recursion and no collections.
Edge cases and common mistakes
- Already one digit.
n = 7fails the conditionn > 9, so the loop never runs and the function returns 7. - Exactly 10.
digit_sum(10)is 1, so the answer is 1. The loop condition isn > 9, notn > 10. - A sum that is still two digits. 99 gives 18, which gives 9. This is why the outer function loops instead of calling
digit_sumonce. - The largest
i32. 2147483647 becomes 46, then 10, then 1. - Zero and negative numbers. The problem promises a positive number. For 0 the function returns 0, and for a negative number both loops are skipped, so it returns the number unchanged. In
digit_sum, the conditionn > 0would also stop at once for a negative input. - Returning inside the loop. It would stop after the first digit sum, so 99 would give 18.
- Using
n >= 0indigit_sum. Becausenreaches 0 and 0 / 10 is 0, the loop would never end.
There is also a closed form for positive numbers, 1 + (n - 1) % 9, which gives the same answers in constant time. The loop version is the one this lesson practises.
Testing
39gives3.928435gives4.7gives7.10gives1.99gives9.2147483647gives1.
