Step 1: Given a list of numbers, generate all possible groupings (in order) given only the final number of desired groups.
For example, if my list of numbers were 1 to 4, and I wanted 2 final groups, the possibilities would be:
[1], [2,3,4]
[1,2], [3,4]
[1,2,3], [4]
Step 2: Perform arithmetic operations on those groups.
For example, if we chose addition, the final results would be:
1 + 234 = 235
12 + 34 = 46
123 + 4 = 127
Prior Research and Similar Problems
I've seen numerous examples on SO and elsewhere for problems involving variable amounts of groups, which utilize ranges and for loops, a la:
print [num_list[i:i+groups] for i in range(0,len(num_list),groups)]
But that's kind of the reverse of what I want - there, the lengths of the groups themselves are fixed save for the final one, and the number of groups oscillates.
This isn't homework, just an interesting problem I came across. Ideally, I'd need to be able to iterate over those separate sublists in order to perform the mathematical operations, so they'd need to be captured as well.
I have a feeling the solution will involve itertools, but I can't seem to figure out the combinatorics with grouping aspect.
Edit/Extension of Step 2
If I want to perform different operations on each of the partitions, can I still approach this the same way? Rather than specifiying just int.add, can I somehow perform yet another combination of all the main 4 operations? I.e.:
symbol_list = ['+','-','*','/']
for op in symbol_list:
#something
I'd wind up with possibilities of:
1 + 2 * 34
1 * 2 - 34
1 / 2 + 34
etc.
Order of operations can be ignored.
#!/usr/bin/env python
import sys
from itertools import combinations, chain, product
# fixed vars
num_list = range(_,_) # the initial list
groups = _ # number of groups
target = _ # any target desired
op_dict = {'+': int.__add__, '-': int.__sub__,
'*': int.__mul__, '/': int.__div__}
def op_iter_reduce(ops, values):
op_iter = lambda a, (i, b): op_dict[ops[i]](a, b)
return reduce(op_iter, enumerate(values[1:]), values[0])
def split_list(data, n):
for splits in combinations(range(1, len(data)), n-1):
result = []
prev = None
for split in chain(splits, [None]):
result.append(data[prev:split])
prev = split
yield result
def list_to_int(data):
result = 0
for h, v in enumerate(reversed(data)):
result += 10**h * v
return result
def group_and_map(data, num_groups):
template = ['']*(num_groups*2 - 1) + ['=', '']
for groups in split_list(data, num_groups):
ints = map(list_to_int, groups)
template[:-2:2] = map(str, ints)
for ops in product('+-*/', repeat=num_groups-1):
template[1:-2:2] = ops
template[-1] = str(op_iter_reduce(ops, ints))
if op_iter_reduce(ops, ints) == target:
print ' '.join(template)
group_and_map(num_list, groups)
Step 1: The easiest way I have found to think of splitting lists into groups like that is to try to get combinations of split locations. Here is an implementation:
def split_list(data, n):
from itertools import combinations, chain
for splits in combinations(range(1, len(data)), n-1):
result = []
prev = None
for split in chain(splits, [None]):
result.append(data[prev:split])
prev = split
yield result
>>> list(split_list([1, 2, 3, 4], 2))
[[[1], [2, 3, 4]], [[1, 2], [3, 4]], [[1, 2, 3], [4]]]
>>> list(split_list([1, 2, 3, 4], 3))
[[[1], [2], [3, 4]], [[1], [2, 3], [4]], [[1, 2], [3], [4]]]
Step 2: First you need to convert a list like [[1], [2, 3, 4]]
, to one like [1, 234]
. You can do this with the following function:
def list_to_int(data):
result = 0
for i, v in enumerate(reversed(data)):
result += 10**i * v
return result
>>> map(list_to_int, [[1], [2, 3], [4, 5, 6]])
[1, 23, 456]
Now you can perform your operation on the resulting list using reduce()
:
>>> import operator
>>> reduce(operator.add, [1, 23, 456]) # or int.__add__ instead of operator.add
480
Complete solution: Based on edit referencing need for different operators:
def op_iter_reduce(ops, values):
op_dict = {'+': int.__add__, '-': int.__sub__,
'*': int.__mul__, '/': int.__div__}
op_iter = lambda a, (i, b): op_dict[ops[i]](a, b)
return reduce(op_iter, enumerate(values[1:]), values[0])
def group_and_map(data, num_groups):
from itertools import combinations_with_replacement
op_dict = {'+': int.__add__, '-': int.__sub__,
'*': int.__mul__, '/': int.__div__}
template = ['']*(num_groups*2 - 1) + ['=', '']
op_iter = lambda a, (i, b): op_dict[ops[i]](a, b)
for groups in split_list(data, num_groups):
ints = map(list_to_int, groups)
template[:-2:2] = map(str, ints)
for ops in combinations_with_replacement('+-*/', num_groups-1):
template[1:-2:2] = ops
template[-1] = str(op_iter_reduce(ops, ints))
print ' '.join(template)
>>> group_and_map([1, 2, 3, 4], 2)
1 + 234 = 235
1 - 234 = -233
1 * 234 = 234
1 / 234 = 0
12 + 34 = 46
12 - 34 = -22
12 * 34 = 408
12 / 34 = 0
123 + 4 = 127
123 - 4 = 119
123 * 4 = 492
123 / 4 = 30
If you are on Python 2.6 or below and itertools.combinations_with_replacement()
is not available, you can use the recipe linked here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With