Search code examples
pythonmathtypeerror

A typeerror I face when writing a Python program in order to solve a maths problem


I've been trying to solve this:

Find positive integer solutions for a,b,c that 1≤a<b<c≤30 and b/a+c/b+a/c is a positive integer.

So I worte this, in python:

for i,j,m in range(1,31):
    if i<=j and j<=m and j/i+m/j+i/m>=0:
        try:
            print(int(j/i+m/j+i/m))
        except TypeError:
            continue

However it didn't work.

And the error message:

for i,j,m in range(1,31):
TypeError: cannot unpack non-iterable int object 

Solution

  • Iterating over a triplet

    range returns an iterable over single integers. To iterate over triplets, you can either nest three loops, or use itertools:

    # using nested loops
    for a in range(1, 29):
        for b in range(a+1, 30):
            for c in range(b+1, 31):
                ...
    
    # using itertools
    from itertools import combinations
    
    for a,b,c in combinations(range(1,31), 3):
       ...
    

    Note that in both cases, I wrote the loops to iterate directly over triplets (a,b,c) satisfying a < b < c. This is much more efficient than iterating over all triplets (a,b,c), then filtering for the condition a < b < c.

    Testing whether b/a+c/b+a/c is an integer

    When dealing with fractions with different denominators, the first thing to do is usually to put everything to a common denominator. In this case, the sum of three fractions b/a+c/b+a/c is equal to fraction (b*b*c + c*a*c + a*a*b) / (a*b*c). Testing whether this fraction is an integer or not is exactly testing whether its numerator is divisible by its denominator. This can be easily achieved with the modulo operator %.

    if (b*b*c + c*a*c + a*a*b) % (a*b*c) == 0:
        ...
    

    This is actually better than using floating-point division and testing if the result looks like an integer. Floating-point division results in a binary floating-point number. Not all numbers can be represented exactly in binary floating-point: just like fraction 1/3 cannot be represented exactly in decimal, many numbers cannot be represented exactly in binary. Imagine testing whether 1/3 + 1/3 + 1/3 is an integer: if 1/3 is rounded to 0.333, then we'll get 0.333 + 0.333 + 0.333 == 0.999, which is not an integer. This is an approximation error. If possible, when dealing with integer arithmetic, always use only integers, and never use floating-point division. Protect yourself from floating-point approximations!

    Separating algorithm logic from input/output

    It is very good practice to separate the parts of code that deal with algorithms and arithmetic, on the one side, from parts of code that deal with printing to screen, reading from and writing to files, etc.

    You could append all the found triplets to a list, then return the list:

    def get_triplet_list(n):
        l = []
        for a,b,c in combinations(range(1, n+1), 3):
            if (b*b*c + c*a*c + a*a*b) % (a*b*c) == 0:
                l.append((a,b,c))
        return l
    
    def main():
        l = get_triplet_list(30)
        for a,b,c in l:
            print(a,b,c)
    
    if __name__ == '__main__':
        main()
    # 2 9 12
    # 3 4 18
    # 4 18 24
    

    Or use a list comprehension:

    def get_triplet_list(n):
        return [(a,b,c) for a,b,c in combinations(range(1, n+1), 3) if (b*b*c + c*a*c + a*a*b) % (a*b*c) == 0]
    
    def main():
        l = get_triplet_list(30)
        for a,b,c in l:
            print(a,b,c)
    
    if __name__ == '__main__':
        main()
    

    Or use a generator function, with keyword yield instead of return:

    def gen_triplets(n):
        for a,b,c in combinations(range(1, n+1), 3):
            if (b*b*c + c*a*c + a*a*b) % (a*b*c) == 0:
                yield (a,b,c)
    
    def main():
        for a,b,c in gen_triplets(30):
            print(a,b,c)
    
    if __name__ == '__main__':
        main()