In python,reduce
takes a function that accepts only two arguments.
Is there any elegant way to have a reduce
that can take more than two arguments?
For example:
from operator import add
list = [1,add,2,add,3,add,4]
func = lambda operand1,operator,operand2: operator(operand1,operand2)
reduce(func,list) # mock reduce. Expected result is 10(1 2 3 4=10)
CodePudding user response:
This gives the exact result without 3 arguments.
It creates a partial function on each argument if arguments are like 1, add
and calls the function if arguments are like partial,2
from operator import add
from functools import reduce, partial
lst = [1,add,2,add,3,add,4]
def operation_reduce(x, y):
if callable(x):
return x(y)
else:
return partial(y, x)
print(reduce(operation_reduce, lst)) #10
CodePudding user response:
reduce
can't be changed, but you could provide a callable that handles the differing inputs:
from functools import reduce
from operator import add, sub
L = [1, add, 2, add, 3, add, 4, sub]
class Reducer:
def __init__(self):
self.pending = 0
def __call__(self, total, value):
if callable(value):
total = value(total, self.pending)
else:
self.pending = value
return total
func = Reducer()
result = reduce(func, L, 0)
print(result)
Or you could batch the inputs in tuples, and use a callable that could handle those
def reducer(total, next_):
value, operation = next_
return operation(total, value)
result = reduce(reducer, zip(L[::2], L[1::2]), 0)
print(result)
CodePudding user response:
I'd be interested to hear what is your desired use case as I believe reduce taking more than 2 arguments is not elegant, period. Here's my rationale (though it's a bit hard arguing in abstractness, with no concrete use case):
Assume the reducing operation takes 3 arguments as in your example. Let's also make initial accumulator explicit, reduce3(func,list,acc). Then in every call to func:
- first argument is accumulator as usual
- second argument is always an element at the odd position in the list
- third argument is always at an even position in the list
Even positioned elements from the list are treated by the func differently than odd positioned elements. We can ever really need such a reduce3 function if the elements at odd and even positions are of different type!* It is the case in the example. Mixing elements of different type in one list requires more caution not to make a mistake (mess up the order of some 2 elements and boom the reduce3 breaks) and IMO should be avoided in general.
Modifying your example:
list = [2,3,4] # we'l include 1 as initial acc
ops = [add, add, add]
func = lambda acc, pair : pair[0](acc, pair[1])
reduce(func, zip(ops, list), 1) # result is 10(1 2 3 4=10)
Reducing over a list of tuples is a general solution to your question. If mathematical expressions is what you want to evaluate you should consider their tree structure, which gets lost when collapsed to a list.
*Not necessarily of different type in a programming sense, but certainly of different type or meaning to us.