3
\$\begingroup\$

I managed to make this work without breaking the program if you enter some weird numbers in the range. The thing is I know this could have been done simpler and smarter. I can't really see how right now so I'm hoping you'll enlighten me. Thanks

print("Perfect Square Finder!")
print("Enter a range to find perfect squares")
square = 1
odd = 3
valid = False
while valid == False:
    low = int(round(float(input("Low End: "))))
    high = int(round(float(input("High End: "))))
    if low > high or high < 0:
        valid = False
        print("Not a valid range")
    else:
        valid = True
while square <= high:
    if square >= low:
        present = True
        print(str(int(square ** 0.5)) + "^2 = " + str(square))
    else:
        present = False
    square = square + odd
    odd = odd + 2
if present == False:
    print("No perfect squares in the range")
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Organization

Blank lines between sections of code would make your code a little easier to read. Add a blank line before both while loops as wall as the final if statement.


Move you perfect square finder into a function. This would allow you reuse the function multiple times, and even write test code to make sure it works.

Bugs

You function will not find the perfect square zero (0² == 0).


If asked for a perfect square between 81129638414606699710187514626000 and 81129638414606699710187514626100, it can report:

9007199254740992^2 = 81129638414606699710187514626049

which is obviously wrong, since an even number squared will not be odd. The issue is int(square ** 0.5), which uses inexact floating point math to determine the square root. Using math.isqrt(square) does the calculation entirely using integer math, and is exact.

Bizarre validation

You use int(round(float(input(...)))) to get the low and high limits. Why are you using float() and round()? Are you allowing the user to enter in floating point limits, like 1.25 to 5.8? If not, you could simply have int(input(...)).

Efficiency

Your algorithm of computing perfect squares by summing odd integers is not the most efficient algorithm. But let's assume you wanted to do it that way anyway.

The code is not as efficient as it could be.

Every step until you reach a square >= low, you repeatedly set present = False. When searching for the perfect square 81129638414606699710187514626049, this ends up happening 9007199254740992 times, which is a bit excessive. You could simply set it present = False once before the loop starts.


The statements square = square + odd and odd = odd + 2 are not efficient, due to vulgarities of the Python interpreter. (It must look up the variable in the dictionary each time it is mentioned in the statement!) Using square += odd and odd += 2 eliminates one variable lookup each, so will be faster.

Reworked code

The following code uses f-strings to simplify the print statements. It also further reworks the perfect square finding loop into two separate loops for additional efficiency eliminating the present variable altogether. It further uses a "main guard", allowing the code to be imported without being executed. Finally, all code has been moved inside of functions, eliminating the global variables.

from math import isqrt

def print_perfect_squares_in_range(low, high):

    square = 0  # Since zero is a perfect square,
    odd = 1     # we need to start the search 1 step earlier

    # Skip over all perfect square below lower limit
    while square < low:
        square += odd
        odd += 2

    # If the next perfect square has not exceeded the upper limit ...
    if square <= high:
        # ... then there are perfect squares in the given range
        while square <= high:
            print(f"{isqrt(square)}^2 = {square}")
            square += odd
            odd += 2
    else:
        # ... otherwise, there are not any.
        print("No perfect squares in range")


def main():

    valid = False

    while not valid:
        low = int(input("Low End: "))
        high = int(input("High End: "))

        if low > high or high < 0:
            print("Not a valid range")
        else:
            valid = True

    print_perfect_squares_in_range(low, high)


if __name__ == '__main__':
    main()
\$\endgroup\$
2
  • \$\begingroup\$ I forgot to use the augmented assignment operators, I should've done += you're right. I read about people using math.methods for square rooting but I thought (square ** 0.5) would do the same job; I guess I need to learn the limitations of float math. I'll practice spacing my lines for readability. The only place I'll defend myself is with allowing decimal inputs: I was sure someone would test my code with that and I was ready. A lot of what you mentioned was new to me and I need to familiarize myself with: f- strings, def operator, "main guard", and creating a 'function'. Thanks for your help \$\endgroup\$ Commented Jul 4, 2022 at 7:05
  • 1
    \$\begingroup\$ Perfect squares are integers, so a range for searching for perfect squares should also be integers. A better way to “be ready” would be to use try: … except ValueError: … construct around the user input, which would handle all non-integer values like 4.75 and fubar … something else for you to study. Hope to see future posts from you here. Welcome to the wonderful world of Python! \$\endgroup\$
    – AJNeufeld
    Commented Jul 4, 2022 at 15:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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