I am used to the FP/haskell-like concept of reduce, already implemented as a built-in library in Python:
from functools import reduce
lst = [1,2,3,4,5]
reduce(lambda x, y: x*y, lst)
# result: 120
However this doesn't work for, e.g., a lambda with three arguments:
from functools import reduce
lst = [1,2,3,4,5]
reduce(lambda x, y, z: x*y z, lst)
# expected result: (1*2 3) * 4 5
# actual result:
# TypeError: <lambda>() missing 1 required positional argument: 'z'
The rationale is to keep the rule "the previous result is the next first argument".
Is there a built-in implementation, or clever/simple combination of functional constructs to achieve such goal?
ps. Beware of pseudo-duplicates
CodePudding user response:
A reasonably simple implementation would be
import inspect
def nreduce(fn, lst):
nargs = len(inspect.signature(fn).parameters)
args = list(lst)
while len(args) >= nargs:
next_args = [args.pop(0) for x in range(nargs)]
args.insert(0, fn(*next_args))
return args
lst = [1, 2, 3, 4, 5]
print(nreduce(lambda x, y, z: x * y z, lst))
If you want to be fancy, you can use a deque to make the insertion of the just-reduced value to the left of the list faster:
import collections
def nreduce(fn, lst):
nargs = len(inspect.signature(fn).parameters)
arg_queue = collections.deque(lst)
while len(arg_queue) >= nargs:
next_args = [arg_queue.popleft() for x in range(nargs)]
arg_queue.appendleft(fn(*next_args))
return list(arg_queue)
If the length of lst isn't divisible by the arity of fn, the remaining elements will be returned too.
Yet another implementation which works with any iterable without copying into other lists or queues (which won't return the rest of the lst, just the values it consumed out of it):
def nreduce(fn, lst):
nargs = len(inspect.signature(fn).parameters)
lst_iter = iter(lst)
next_args = []
while True:
try:
while len(next_args) < nargs:
next_args.append(next(lst_iter))
except StopIteration:
break
next_args = [fn(*next_args)]
return next_args
