Happy numbers (medium)
Resources for checking if a number is a happy number.
Dry-run templates
This is an implementation of the solution provided for the problem posed in the lesson:
def find_happy_number(num):slow, fast = num, numwhile True:slow = find_square_sum(slow) # move one stepfast = find_square_sum(find_square_sum(fast)) # move two stepsif slow == fast: # found the cyclebreakreturn slow == 1 # see if the cycle is stuck on the number '1'def find_square_sum(num):_sum = 0while (num > 0):digit = num % 10_sum += digit * digitnum //= 10return _sumdef main():print(find_happy_number(23))print(find_happy_number(12))main()
Students may be encouraged to run through the provided solution with the following sample inputs. The while
loop in the find_square_sum
adds the squares of the digits of the number and returns the final sum. The sum column in the table below indicates the final number that is returned.
Sample input #1
Input: 23
Iteration | Line number | sum (slow) | slow | sum (fast) | fast |
- | 2 | - | 23 | - | 23 |
1 | 4 | 13 | 13 | - | 23 |
1 | 5 | - | 13 | 13 | 23 |
1 | 5 | - | 13 | 10 | 10 |
2 | 4 | 10 | 10 | - | 10 |
2 | 5 | - | 10 | 1 | 10 |
2 | 5 | - | 10 | 1 | 1 |
3 | 4 | 1 | 1 | - | 1 |
3 | 5 | - | 1 | 1 | 1 |
3 | 5 | - | 1 | 1 | 1 |
Sample input #2
Input: 12
Iteration | Line number | sum (slow) | slow | sum (fast) | fast |
- | 2 | - | 12 | - | 12 |
1 | 4 | 5 | 5 | - | 12 |
1 | 5 | - | 5 | 5 | 12 |
1 | 5 | - | 5 | 25 | 25 |
2 | 4 | 25 | 25 | - | 25 |
2 | 5 | - | 25 | 29 | 25 |
2 | 5 | - | 25 | 85 | 85 |
3 | 4 | 29 | 29 | - | 85 |
3 | 5 | - | 29 | 89 | 85 |
3 | 5 | - | 29 | 145 | 145 |
4 | 4 | 85 | 85 | - | 145 |
4 | 5 | - | 85 | 42 | 145 |
4 | 5 | - | 85 | 20 | 20 |
5 | 4 | 89 | 89 | - | 20 |
5 | 5 | - | 89 | 4 | 20 |
5 | 5 | - | 89 | 16 | 16 |
6 | 4 | 145 | 145 | - | 16 |
6 | 5 | - | 145 | 37 | 16 |
6 | 5 | - | 145 | 58 | 58 |
7 | 4 | 42 | 42 | - | 58 |
7 | 5 | - | 42 | 89 | 58 |
7 | 5 | - | 42 | 145 | 145 |
8 | 4 | 20 | 20 | - | 145 |
8 | 5 | - | 20 | 42 | 145 |
8 | 5 | - | 20 | 20 | 20 |
Step-by-step solution construction
The first step in our algorithm is the find_square_sum()
function. The function returns a variable _sum
with the sum of the squares of the given numbers.
The function returns a variable _sum
with the square sum of the given number. For example, for 23
, the _sum
will be:
.
Let’s visualise this function below:
def find_square_sum(num):_sum = 0i=1while (num > 0):print("Loop iteration: ", i)digit = num % 10print("Digit: ", digit)_sum += digit * digitprint("Sum: ", _sum)num //= 10print("Updated number: ", num, "\n")i+=1return _sumdef main():input = [23, 12]for i in input:print("Input number: ", i, "\n")print("Sum of squares: ", find_square_sum(i))print("-"*100 + "\n")main()
This code works as follows:
- Line 6: Take modulus of the number with 10. This is to get the last digit of the input number.
- Line 8: Add the square of the digit to the
_sum
. - Line 10: Divide the input number by 10 to remove the last digit of the number since we’ve already added the square of its value to
_sum
.
Now that we know how _sum
is being calculated, let’s use it to run through the sum of squares sequence generated by a couple of input numbers:
def find_happy_number(num):slow, fast = num, numi=1while i < 11: # for now, let's calculate the sum of squares 10 timesprint("\tLoop iteration: ", i)print("\tSum returned: ", find_square_sum(slow))slow = find_square_sum(slow) # move one stepprint("\tSlow: ", slow, "\n")i += 1return False # returning false for now, as solution is not completedef find_square_sum(num):_sum = 0while (num > 0):digit = num % 10_sum += digit * digitnum //= 10return _sumdef main():input = [12, 23]for i in input:print("Input number: ", i)print("is a happy number? ", find_happy_number(i))print("-"*100 + "\n")main()
We initially set the slow
pointer to num
as shown in line 2. This is later updated by the value returned by the find_square_sum()
function. Since we move the slow
pointer by a factor of 1, the function is called only once per iteration.
Lastly, let’s see how the fast pointer is updated. Since it moves two steps each time, the find_square_sum
function is called twice and the pointer is updated with the final _sum
value returned. The initial value is set to the input number.
def find_happy_number(num):slow, fast = num, num# print("Fast: ", fast,"\n")i=1while i < 11: # for now, let's calculate the sum of squares 10 timesprint("\tLoop iteration: ", i)slow = find_square_sum(slow) # move one stepprint("\tSlow:", slow)print("\tCalculating fast:\n\t\tSum returned by the first call:", find_square_sum(fast))print("\t\tSum returned by the second call:", find_square_sum(find_square_sum(fast)))fast = find_square_sum(find_square_sum(fast)) # move two stepsprint("\tFast:", fast)i+=1return False # returning false for now, as solution is not completedef find_square_sum(num):_sum = 0while (num > 0):digit = num % 10_sum += digit * digitnum //= 10return _sumdef main():input = [12, 23]for i in input:print("Input number: ", i)print("is a happy number? ", find_happy_number(i))print("-"*100 + "\n")main()
Line 10 can also be split into two separate function calls as:
fast = find_square_sum(fast)
fast = find_square_sum(fast)
Now that we know how our fast
and slow
pointers are being updated, the last step is to detect when we are in a cycle and to break out of the while
loop at that point. Students may be encouraged to examine the output of the code above and note whether and when slow
and fast
become equal for the two inputs, 12
and 23
.
If both slow
and fast
become 1
, this means that the sequence converges to 1
and our input number is a happy number. However, if slow
becomes equal to fast
and they’re not 1
, this implies that there is a cycle and our input number is not a happy number.
In the last step, we change the condition of the while
loop to let it run forever but add a condition to break out of the loop when slow
and fast
become equal.
def find_happy_number(num):slow, fast = num, numprint("Slow: ", slow)print("Fast: ", fast,"\n")i=1while True:print("Loop iteration: ", i)slow = find_square_sum(slow) # move one stepfast = find_square_sum(find_square_sum(fast)) # move two stepsprint("Slow: ", slow)print("Fast: ", fast, "\n")i+=1if slow == fast: # found the cyclebreakif slow == 1 and fast == 1:print("Since both slow and fast are equal to 1,", num, "is a happy number!")else:print("Since slow and fast are equal, but not equal to 1, we've found a cycle.", num, "is not a happy number!")return slow == 1 # see if the cycle is stuck on the number '1'def find_square_sum(num):_sum = 0while (num > 0):digit = num % 10_sum += digit * digitnum //= 10return _sumdef main():input = [12, 23]for i in input:print("Input number: ", i)print("is a happy number? ", find_happy_number(i))print("-"*100 + "\n")main()