Search code examples
pythondate-arithmeticinteger-arithmetic

How to perform all possible combinations of arithmetic operations on 3 integers?


Suppose I have three integers. I want to get a list of all possible values obtained by performing all 16 (4x4 of *, /, +, - ) operations among between them.

Like if I have 3 4 1, we should get values 1, 2, 6, 7, 8, 9, 11, 12, 13, 15 and 16. That is,

res = num1 (op1) num2 (op2) num3 where operators are:

["**", "*/", "*+", "*-", "/*", "//", "/+", "/-", "+*", "+/", "++", "+-", "-*", "-/", "-+", "--"]

However, the catch is that division can only be possible if x%y==0 i.e. divisor is factor of dividend.

So, far I've been able to brute force every operation but am missing some answers. I also defined a custom divide operation to take the catch into account.

I'm required to return a list containing unique values that are all positive.


My current code is a mess but here it is for the sake of it. This is missing out some values too.

def div(x, y):
    if x!=0 and y!=0:
        if x%y==0:return x/y
        else:return None


ops_lis = ["**", "*/", "*+", "*-", "/*", "//", "/+", "/-", "+*", "+/", "++", "+-", "-*", "-/", "-+", "--"]

d1, d2, d3 = map(int, input().split())
cal_lis, res_lis = [], []
for op in ops_lis:
    if op[0] == "*" and op[1] == "*":cal_lis.append(d1*d2*d3)
    if op[0] == "*" and op[1] == "/":
        if div(d1*d2, d3) != None:cal_lis.append(div(d1*d2, d3))
        cal_lis.append(div(d1*d2, d3))
    if op[0] == "*" and op[1] == "+":
        cal_lis.append(d1*(d2+d3))
        cal_lis.append((d1*d2)+d3)
    if op[0] == "*" and op[1] == "-":
        cal_lis.append(d1*d2-d3)
        cal_lis.append(d1*(d2-d3))
        cal_lis.append((d1*d3)-d2)
    if op[0] == "/" and op[1] == "*":cal_lis.append(div(d1, d2*d3))
    if op[0] == "/" and op[1] == "/":
        if div(d1, d2) == None or div(d2, d3) == None:
            cal_lis.append(None)
        else:
            cal_lis.append(div(div(d1, d2), d3))
    if op[0] == "/" and op[1] == "+":
        if div(d1, d2) == None:
            cal_lis.append(None)
        else:
            cal_lis.append(div(d1, d2)+d3)
    if op[0] == "/" and op[1] == "-":
        if div(d1, d2) == None:
            cal_lis.append(None)
        else:
            cal_lis.append(div(d1, d2)-d3)

    if op[0] == "+" and op[1] == "*":
        cal_lis.append(d1+d2*d3)
        cal_lis.append((d1+d2)*d3)
        cal_lis.append((d1+d3)*d2)
    if op[0] == "+" and op[1] == "/":
        if div(d2, d3) == None:
            cal_lis.append(None)
        else:
            cal_lis.append(d1+div(d2, d3))
    if op[0] == "+" and op[1] == "+":cal_lis.append(d1+d2+d3)
    if op[0] == "+" and op[1] == "-":cal_lis.append(d1+d2-d3)

    if op[0] == "-" and op[1] == "*":
        cal_lis.append(d1-d2*d3)
        cal_lis.append((d1-d2)*d3)
        cal_lis.append((d1-d3)*d2)
    if op[0] == "-" and op[1] == "/":
        if div(d2, d3) == None:cal_lis.append(None)
        else: cal_lis.append(d1-div(d2, d3))
        if div(d1-d2, d3) == None:cal_lis.append(None)    
        else: cal_lis.append(div(d1-d2, d3))
    if op[0] == "-" and op[1] == "+":cal_lis.append(d1-d2+d3)
    if op[0] == "-" and op[1] == "-":cal_lis.append(d1-d2-d3)

# print(cal_lis)
cal_lis = [int(cal) for cal in cal_lis if cal!=None]
for res in cal_lis:
    if (res > 0 and res not in res_lis):
        res_lis.append(int(res))
for a in sorted(res_lis):
    print(a, end=" ")
print()

Is there an efficient way to perform this task (using trees maybe ?) given that the division condition is also true ?

Any help would be appreciated...


EDIT: Added Constraints

The calculation must conform to the following format: number = dice1 op1 dice2 op2 dice3

where:

  • op1 and op2 can be +, -, * or /. E.g. op1 = + and op2 = /
  • dice1, dice2 and dice3 can be any combination of the numbers rolled at the current round. The value of a die can be used once in the calculation.
  • For the division, the divisor must be a factor of the dividend
  • Parentheses can be added to override the precedence of operators op1 and op2. i.e. (dice1 op1 dice2) op2 dice3 or dice1 op1 (dice2 op2 dice3)

Solution

  • To get all possible results, you will need to include grouping of operations in you combinations. I would suggest a recursive function for this:

    def calcAll(*values,seen=None):
        seen = seen or set()
        if len(values) == 2:
            a,b  = values
            a,sa = (a[0],f"({a[1]})") if isinstance(a,tuple) else (a,str(a))
            b,sb = (b[0],f"({b[1]})") if isinstance(b,tuple) else (b,str(b))
            if a>b : a,sa, b,sb = b,sb, a,sa
            if (a,b) in seen or seen.add((a,b)) :return                
            yield a+b, f"{sa}+{sb}"
            yield a*b, f"{sa}*{sb}"
            yield a-b, f"{sa}-{sb}"
            yield b-a, f"{sb}-{sa}"
            if b != 0 and a%b==0: yield a//b, f"{sa}/{sb}"
            if a != 0 and b%a==0: yield b//a, f"{sb}/{sa}"
            return
        pairs = ((i,j) for i in range(len(values)-1) for j in range(i+1,len(values)))
        for i,j in pairs:
            rest = [*values]
            a,b  = rest.pop(j),rest.pop(i)
            for paired in calcAll(a,b,seen=seen):            
                for result in calcAll(paired,*rest):
                   if result in seen or seen.add(result): continue
                   yield result
    

    output:

    # distinct positive solutions sorted by result
    for r,sr in sorted(calcAll(3,4,1)):
        if r>0: print(sr,"=",r)
    
    (1*4)-3 = 1
    4-(3/1) = 1
    (4/1)-3 = 1
    (4-3)*1 = 1
    4/(1+3) = 1
    4-(1*3) = 1
    (4-3)/1 = 1
    1/(4-3) = 1
    3/(4-1) = 1
    (1+3)/4 = 1
    (4-1)/3 = 1
    1-(3-4) = 2
    4-(3-1) = 2
    (1+4)-3 = 2
    (1-3)+4 = 2
    (4-3)+1 = 2
    4/(3-1) = 2
    (4-1)+3 = 6
    (3-1)+4 = 6
    3-(1-4) = 6
    4-(1-3) = 6
    (4+3)-1 = 6
    (1*4)+3 = 7
    (3/1)+4 = 7
    (4/1)+3 = 7
    (1*3)+4 = 7
    (4+3)/1 = 7
    (4+3)*1 = 7
    (1+4)+3 = 8
    (1+3)+4 = 8
    (3-1)*4 = 8
    (4+3)+1 = 8
    (4-1)*3 = 9
    (4*3)-1 = 11
    (1*4)*3 = 12
    (4*3)*1 = 12
    (4*3)/1 = 12
    (1*3)*4 = 12
    (3/1)*4 = 12
    (4/1)*3 = 12
    (4*3)+1 = 13
    (1+4)*3 = 15
    (1+3)*4 = 16
    

    If you only want the distinct positive results:

    print( set(r for r,_ in calcAll(3,4,1) if r>0) )
    {1, 2, 6, 7, 8, 9, 11, 12, 13, 15, 16}
    

    The function also works for larger lists of numbers:

    # one solution for each positive result of operations between 4 numbers
    for r,sr in sorted(dict(calcAll(1,2,3,4)).items()):
        if r>0: print(sr,"=",r)
    (2/1)+(3-4) = 1
    (2-1)-(3-4) = 2
    (2/1)-(3-4) = 3
    (4*3)/(2+1) = 4
    ((4*3)/2)-1 = 5
    (4*3)/(2/1) = 6
    ((4*3)/2)+1 = 7
    (4+3)-(1-2) = 8
    (4*3)-(2+1) = 9
    (4*3)-(2/1) = 10
    (1-2)+(4*3) = 11
    (4*3)/(2-1) = 12
    (4*3)-(1-2) = 13
    (2/1)+(4*3) = 14
    (2+1)+(4*3) = 15
    (1+(4+3))*2 = 16
    (3*(4+2))-1 = 17
    (3/1)*(4+2) = 18
    (3*(4+2))+1 = 19
    ((3*2)-1)*4 = 20
    (2+1)*(4+3) = 21
    ((4*3)-1)*2 = 22
    (2*(4*3))-1 = 23
    (2/1)*(4*3) = 24
    (2*(4*3))+1 = 25
    (1+(4*3))*2 = 26
    (1+(4*2))*3 = 27
    (1+(3*2))*4 = 28
    (4+1)*(3*2) = 30
    (3+1)*(4*2) = 32
    (2+1)*(4*3) = 36        
    

    And also for duplicate numbers:

    for r,sr in sorted(dict(calcAll(3,3,3)).items()):
        if r>0: print(sr,"=",r)
    
    3-(3/3) = 2
    3/(3/3) = 3
    (3/3)+3 = 4
    (3*3)-3 = 6
    (3+3)+3 = 9
    (3*3)+3 = 12
    (3+3)*3 = 18
    (3*3)*3 = 27