Problem overview
A magical number comes from a positive integer by adding its digits, then adding the digits of that sum, and so on until one digit is left. The method magicNumber(_ n: Int) -> Int returns that digit.
For 39: 3 + 9 = 12, then 1 + 2 = 3, so the answer is 3. For 928435: 9 + 2 + 8 + 4 + 3 + 5 = 31, then 3 + 1 = 4, so the answer is 4.
Approach: split the work into two functions
There are two jobs here: adding the digits of a number, and repeating that until the number is small. Each is easy alone, so the lesson writes two functions. digitSum adds the digits of a number. magicNumber calls it in a loop while the number still has two or more digits.
To take digits off a number, use two integer operations. n % 10 is the last digit, and n / 10 is the number without it, because integer division drops the remainder. Repeating both until nothing is left visits every digit once. For 928435: the digits come out as 5, 3, 4, 8, 2, 9 and the sum is 31.
The loop in magicNumber always ends. A number with two or more digits is larger than its digit sum, so the value shrinks on each pass until it falls to a single digit.
Step by step
The lesson first writes magicNumber, assuming digitSum exists, and then writes digitSum.
- Make a mutable copy of the parameter. A Swift parameter is a constant, so
ncannot be reassigned. Declarevar n = n; the newnshadows the parameter for the rest of the function. - Loop while
nhas two digits or more. That meansn > 9. - Replace
nby its digit sum withn = digitSum(n). - Return
nafter the loop, when it is a single digit. - Declare
digitSum. It takes anIntwith no argument label (_) and returns anInt, so callers writedigitSum(n). - Copy the parameter with
var n = n, as before, because the function will strip digits fromn. - Declare
sumas avarstarting at 0. - Loop while a digit remains, that is, while
n > 0. - Add the last digit with
sum += n % 10. - Remove the last digit with
n /= 10. - Return
sumonce the loop ends.
The solution in Swift
class Solution {
func digitSum(_ n: Int) -> Int {
var n = n
var sum = 0
while n > 0 {
sum += n % 10
n /= 10
}
return sum
}
func magicNumber(_ n: Int) -> Int {
var n = n
while n > 9 {
n = digitSum(n)
}
return n
}
}
The Swift details worth a closer look:
- Shadowing a parameter.
var n = nis legal and common: the right side is the parameter, and the left side is a new variable with the same name. Assigning to the parameter directly,n = 3, is a compile error ("cannot assign to value: 'n' is a 'let' constant"). - Integer division. With two
Intoperands,/truncates toward zero, so928435 / 10is92843. The remainder operator%takes the sign of the left operand. Both are exact for the positive numbers in this lesson. - The label
_. Without it, the call would have to readdigitSum(n: n). - Method order. Inside a class, methods can call each other in any order, so
magicNumbermay calldigitSumwhichever comes first in the file. Both are called on the same instance without any prefix.
A shortcut, and why the lesson does not use it
Repeated digit sums have a known pattern: for a positive n the result is 1 + (n - 1) % 9. It works because a number and its digit sum leave the same remainder when divided by 9, and it needs no loop at all. The lesson writes the loops on purpose, to practise splitting a task into functions and taking digits with % and /. Both give 3 for 39 and 4 for 928435.
Complexity
Let d be the number of digits of n.
Time: O(d), which is O(log n). The first call to digitSum takes d steps. After it, the number is at most 9 times d, so it has about log d digits, and every later pass is far cheaper. The first pass dominates.
Space: O(1). Only a few integers are used, and there is no recursion.
Edge cases and common mistakes
- Already one digit.
9skips the loop and returns9.0returns0. - Multiples of ten.
10becomes 1, and99becomes 18 and then 9. - The largest
Int.9223372036854775807has 19 digits and returns7. The digit sum is far smaller than the number, so nothing overflows. - Using
n >= 9orn > 10. The first never ends, because the digit sum of 9 is 9, sonstays 9 and the condition stays true. The second skips 10 and returns10instead of1. - Using
n > 1indigitSum. A leading digit of 1 would never be added, so 10 would give 0. The loop must run whilen > 0. - Returning inside a loop. The
returnbelongs after the loop, or the function ends after one pass. - Negative input. The lesson guarantees positive numbers. For a negative
nneither loop runs, and the method returns the number unchanged.
Testing
39gives3.928435gives4.9gives9.10gives1.99gives9.
