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