2
\$\begingroup\$

I wrote a piece of code to generate a list of \$n\$ Happy Numbers, would love to see how I can write it better. More information below.

#a happy number is defined is found by taking a number, and adding the sum of its digits, and repeating the steps to add the sum of square
#of the resultant digits until we reach 1, if the result never reaches 1 then it is not a happy number

# example: number = 7
# 7^2 = 49
# 4^2 + 9^2 = 16+81 = 97
# 9^2 + 7^2 = 81 + 49 = 130
# 1^2 + 3^2 + 0 = 1+9 = 10
# 1^2 + 0 = 1 HAPPY number
#defining a emtpy list that will be populate as soon as we encounter a happy number
happy_numbers = []
sqdict={str(i):i**2 for i in range(10)}

#lets define our adder
def adder3(num):
    return sum(sqdict[i] for i in str(num))

#now we need to take a number and keep adding until we get a 1 or the loop keeps repeating
# lets define a function for that

def happynum (num, counter):
    i_sum = adder3(num) # storing the value from the adder fucntion to i_sum
    if i_sum == 1: # check for happy number
        happy_numbers.append(number)
        # print("The given number {} is a happy number.".format(number))
        return 1
    else: # we continue to keep splitting and adding until we reach 1 or attain infinite loop
        counter +=1
        if counter > 50:
             # print("The number {} isnt happy :(.".format(number))
            return False
        else:
            happynum(i_sum,counter)

counter = 0 # initializing a counter to keep keep track of the infinite loop if it does reach that.
# user_num = 7
for i in range (0, 1000):
    number = i
    happynum(number, counter)
print(happy_numbers[10])

The code above is working well for what I am trying to achieve. However, I tried defining the counter as a global variable before all the methods, but when called in the method I was not able to increment. It threw aa error "local variable referenced before assignment".

Additionally, I am not sure using a for loop in the end to generate 1000 Happy Numbers is an efficient way, it seems somewhat limiting. Would appreciate some help on this.

\$\endgroup\$
1
\$\begingroup\$

Coding style

There is too little white space at many places, for example

sqdict={str(i):i**2 for i in range(10)}

This and other PEP8 coding style violations (most of them related to spacing and too long comment lines) can be detected by checking your code at PEP8 online.

Unnecessary comments

There are some comments which add no information to the code and can safely be removed, such as

#lets define our adder
def adder3(num):

or

i_sum = adder3(num) # storing the value from the adder fucntion to i_sum

Naming

def adder3(num):

Is is impossible to guess from the function name what its purpose might be. I'd suggest something like square_digit_sum.

def happynum (num, counter):

Here we know what the function is about, what does it do? Compute a happy number, check for happy number, ...?

Code structure:

The global variable counter = 0 is not used at all and can be removed.

A global variable number = i is used to "remember" the argument of the inital call to the recursive happynum function, and the function (as a "side effect") appends to the global happy_numbers array. This is error-prone and not very elegant.

I'd suggest to define a function is_happy instead, which takes a number and recursion limit, and returns True or False. The default recursion limit can be defined as a default parameter value:

def is_happy(num, reclimit=50):
    if num == 1:
        return True
    if reclimit <= 0:
        return False
    return is_happy(square_digit_sum(num), reclimit - 1)

Note also that no else: block is needed if the if: block has a return statement. This saves indenting levels.

Then you can use list comprehension

happy_numbers = [num for num in range(1000) if is_happy(num)]

or a filter

happy_numbers = filter(is_happy, range(1000))

to create an array with the first 1000 happy numbers.

Also it is a good habit to put add a "main guard" so that the source file can be imported as a module:

if __name__ == "__main__":
    happy_numbers = filter(is_happy, range(1000))
    print(happy_numbers)

Performance improvements:

Computing the square digit sum can be made slightly faster by using integer arithmetic only, without string conversions:

def square_digit_sum(num):
    sum = 0
    while num > 0:
        digit = num % 10
        sum += digit * digit
        num //= 10
    return sum

(This makes the sqdict hash obsolete.)

The recursion limit of 50 is somewhat arbitrary, and might not be sufficient for large numbers. A possible alternative is to remember all numbers seen so far in a set:

def is_happy(num):
    seen = set()
    while num > 1:
        if num in seen:
            return False
        seen.add(num)
        num = square_digit_sum(num)
    return True

More suggestions:

Add docstring comments to all functions.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.