3

I'm trying to use itertools to iterate through mathematical operators. Typically with an array of [1, 2, 3], using combinations I can get the results:

1
1,2
1,3
2,3
1,2,3

etc.

I want to use this on an array of [1, 2, 3] in such a way that:

1+2+3
1+2-3
1+2/3
1+2*3
1-2+3
1-2-3
1-2/3
1-2*3
...

arises and gives results of the equation.

How might I go about doing this?

  • 3
    There are functions that perform the standard arithmetic operations in the [operator](https://docs.python.org/3/library/operator.html) module. – PM 2Ring Jun 05 '17 at 14:00
  • 2
    There are two possible approaches: one is creating your formula as a string, then using `eval` to evaluate it; and another, to use functions from `operator` module. – Błotosmętek Jun 05 '17 at 14:00
  • Take a look at `__add__`, `__sub__`, `__mul__` and `__div__` methods of `int`. – cs95 Jun 05 '17 at 14:01
  • 1
    is it bound to only starting with addition and subtraction? – gold_cy Jun 05 '17 at 14:03
  • It's not bound for starting with anything in particular, it could go in any order. – Namey McNamo Jun 05 '17 at 14:07
  • One important question: do you want the operators to follow normal rules of precedence or just left-to-right? Ie., should the result of 1+2*3 be (1+2)*3=9 or 1+(2*3)=7? – Błotosmętek Jun 05 '17 at 14:26
  • @Błotosmętek I hadn't actually though that out. That certainly adds a layer of complexity to the use of the operators. In my case I would say I'm trying to achieve both, or as many answers as possible. But for the sake of this question, let's follow the normal rules of precedence. – Namey McNamo Jun 05 '17 at 14:37

4 Answers4

1

Here is how I would approach it:

import itertools
import operator

First make the list of all possible combinations:

funcs = [operator.add, operator.sub, operator.mul, operator.div]

combos = list(itertools.product(funcs, repeat=2))

>>[(<function operator.add>, <function operator.add>),
 (<function operator.add>, <function operator.sub>),
 (<function operator.add>, <function operator.mul>),
 (<function operator.add>, <function operator.div>),
 (<function operator.sub>, <function operator.add>),
 (<function operator.sub>, <function operator.sub>),
 (<function operator.sub>, <function operator.mul>),
 (<function operator.sub>, <function operator.div>),
 (<function operator.mul>, <function operator.add>),
 (<function operator.mul>, <function operator.sub>),
 (<function operator.mul>, <function operator.mul>),
 (<function operator.mul>, <function operator.div>),
 (<function operator.div>, <function operator.add>),
 (<function operator.div>, <function operator.sub>),
 (<function operator.div>, <function operator.mul>),
 (<function operator.div>, <function operator.div>)]

Then we will iterate through this list solving every possible outcome:

for fn in combos:
    print 'This combo {} yielded this result {}'.format(fn, fn[1](fn[0](*seq[:2]), seq[-1]))

This combo (<built-in function add>, <built-in function add>) yielded this result 6
This combo (<built-in function add>, <built-in function sub>) yielded this result 0
This combo (<built-in function add>, <built-in function mul>) yielded this result 9
This combo (<built-in function add>, <built-in function div>) yielded this result 1
This combo (<built-in function sub>, <built-in function add>) yielded this result 2
This combo (<built-in function sub>, <built-in function sub>) yielded this result -4
This combo (<built-in function sub>, <built-in function mul>) yielded this result -3
This combo (<built-in function sub>, <built-in function div>) yielded this result -1
This combo (<built-in function mul>, <built-in function add>) yielded this result 5
This combo (<built-in function mul>, <built-in function sub>) yielded this result -1
This combo (<built-in function mul>, <built-in function mul>) yielded this result 6
This combo (<built-in function mul>, <built-in function div>) yielded this result 0
This combo (<built-in function div>, <built-in function add>) yielded this result 3
This combo (<built-in function div>, <built-in function sub>) yielded this result -3
This combo (<built-in function div>, <built-in function mul>) yielded this result 0
This combo (<built-in function div>, <built-in function div>) yielded this result 0

Edit: Here is a way that follows the rules of operations

ops = ['+','-','*','/']

combos = list(itertools.product(ops, repeat=2))

for tup in list(itertools.product(combos, [seq])):
    print 'These operations {} evaluate to this ---> {}'.format(tup[0],eval(''.join(*zip(seq[0],tup[0][0],seq[1],tup[0][1],seq[-1]))))

These operations ('+', '+') evaluate to this ---> 6
These operations ('+', '-') evaluate to this ---> 0
These operations ('+', '*') evaluate to this ---> 7
These operations ('+', '/') evaluate to this ---> 1
These operations ('-', '+') evaluate to this ---> 2
These operations ('-', '-') evaluate to this ---> -4
These operations ('-', '*') evaluate to this ---> -5
These operations ('-', '/') evaluate to this ---> 1
These operations ('*', '+') evaluate to this ---> 5
These operations ('*', '-') evaluate to this ---> -1
These operations ('*', '*') evaluate to this ---> 6
These operations ('*', '/') evaluate to this ---> 0
These operations ('/', '+') evaluate to this ---> 3
These operations ('/', '-') evaluate to this ---> -3
These operations ('/', '*') evaluate to this ---> 0
These operations ('/', '/') evaluate to this ---> 0
gold_cy
  • 13,648
  • 3
  • 23
  • 45
1

Not an elegant one (made on knee) but works, just to point out my logic. The idea is to reduce the list one by one in correct order. E.g.:

Data: 1 * 2 + 3 * 4

  • After Step 1 ( first * evaluated ): 2 + 3 * 4
  • After Step 2 ( second * evaluated ): 2 + 12
  • After Step 3 ( + evaluated ): 14

The code:

import operator
import itertools

data = [1.0, 2.0, 3.0, 4.0]
operators_1 = [operator.mul, operator.div] # this operators have priority over that below
operators_2 = [operator.add, operator.sub]

def processOps(formula, data, operators):
    res_formula = list(formula)
    result = list(data)
    for op in formula:
        if op not in operators: continue

        i = res_formula.index(op)
        result = result[:i] + [op(result[i], result[i + 1])] + result[i + 2:]
        res_formula.remove(op)

        if len(result) == 1:
            break

    return (res_formula, result)

for f in itertools.product(operators_1 + operators_2, repeat=len(data)-1):
    result = list(data)
    formula = list(f)
    formula, result = processOps(formula, result, operators_1)
    formula, result = processOps(formula, result, operators_2)
    print f, result

UDP This updated logic handles cases like (1 * 2) + (3 / 4) correctly.

Mikhail Churbanov
  • 4,436
  • 1
  • 28
  • 36
0

Use the corresponding functions in the operator module and iterate over pairs of them.

import itertools
import operator

ops = [operator.add, operator.sub, operator.mul, operator.div]

for f1, f2 in itertools.product(*ops, repeat=2):
    print f1(array[0], f2(array[1], array[2]))

Now, if array can have an arbitrary length, it gets a bit tricker.

for operations in itertools.product(*ops, repeat=len(array)-1):
  result = operations[0](array[0], array[1])
  for op, operand in zip(operations[1:], array[2:]):
      result = op(result, operand)
  print(result)

The structure above avoids having to know the appropriate identity element for each operation.

If you want to obey precedence (as seems likely), you'll want to create an expression and evaluate it with eval (standard warnings apply).

for ops in itertool.product("+", "-", "*", "/", repeat=len(array)-1):
  expr = "%s%s%s" % (array[0], ops[0], array[1])
  for op, operand in zip(ops[1:], array[2:]):
    expr = "%s%s%s" % (expr, op, operand)
  result = eval(expr)

I leave it as an exercise to expand this to produce parenthesized expressions like (1+2)*3 in addition to 1+2*3.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Replace `len(array)` with `len(array)-1` - number of operators is one less than number of operands. – Błotosmętek Jun 05 '17 at 14:24
  • Thanks; something about `operations` and `array` being sliced with different starting indices in the call to `zip` made me thing something was wrong, but I didn't stop to think what. – chepner Jun 05 '17 at 14:25
  • I try running these but it says: TypeError: 'int' object is not iterable I think it refers to the 2. – Namey McNamo Jun 05 '17 at 14:33
  • I always forget; `repeat` has to be specified as a keyword argument. – chepner Jun 05 '17 at 14:58
0

A solution generalized to any number of operands, and preserving normal precedence of operators:

from itertools import product

operands = [1, 2, 3, 4]
operators = [ '+', '*', '-', '//' ] # change '//' to '/' for floating point division
for opers in product(operators, repeat=len(operands)-1):
    formula = [ str(operands[0]) ]
    for op, operand in zip(opers, operands[1:]):
        formula.extend([op, str(operand)])
    formula = ' '.join(formula)
    print('{} = {}'.format(formula, eval(formula)))
Błotosmętek
  • 12,717
  • 19
  • 29
  • This is really good. However by changing the '//' to a '/' it ends up with the same answer as'//'. So the division is slightly off. – Namey McNamo Jun 05 '17 at 15:03
  • Erm, do you use Python 2.x? In this case, yes, there is no difference between `/` and `//`, unless you add this: `from __future__ import division` – Błotosmętek Jun 05 '17 at 15:06