Home > Back-end >  Which is the pythonic way to 'reduce' a list applying a three-parameter function?
Which is the pythonic way to 'reduce' a list applying a three-parameter function?

Time:10-22

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
  • Related