Sam Chiu
Sam Chiu

Reputation: 143

How to make reduce function take three parameters in Python?

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)

EDIT:

The reduce3 function is used to calculate the result of the abstract syntax tree(AST).
Every children of AST can be either a node or another tree. For simplicity, assume that leaf nodes can only be number,addition symbol(+) or subtraction symbol(-).To calculate the result, one intuitive idea is to firstly work out the result of every nodes(can be implemented by recursively calls the method), and then combine them together. For example, here's a simple AST:

Tree(
    Node(1),
    Node("+"),
    Tree(
        Node(2),
        "+",
        Node(3)
    ),
    Node("-"),
    Node(4)
)

After recursion, we get the following result.

If there is a reduce take 3 arguments, we can easily combine the result.

Tree(
    Node(1),
    Node("+"),
    Node(5)
    Node("-"),
    Node(4)
)

Upvotes: 1

Views: 1102

Answers (3)

zaabson
zaabson

Reputation: 187

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.

Edit:

Indeed you want to evaluate mathematical expressions. Consider changing the way you are parsing these expressions so that for the example the AST looks like:

Tree("-", 
    Tree("+",
        Node(1), 
        Tree("+", 
            Node(2), 
            Node(3))), 
    Node(4))
#       -
#      / \
#     +   4
#    / \
#   1   +
#      / \
#     2   3

That is internal nodes are always tagged with binary operation and it is only leafs that carry numbers. Now reducing tree to a value is trivial but at a cost of parsing doing most of the work.

# Here assuming some specifics of the Tree and Node classes.
# strToOpt is mapping of operation symbols to functions.
def evaluate(expr):              # expr : Tree or Node
    if (isinstance(expr, Node)): # expr : Node 
        return expr.x
    else:                        # expr : Tree 
        return strToOp[expr.op](evaluate(expr.left), evaluate(expr.right))

If you allow for non-binary tree nodes then swap the "else" branch with reduce like reduce(strToOpt[expr.op], expr.children).

I understand that this is very different approach. Reduce3 shouldn't be that hard to implement if you want to stick to it, right? For some help on parsing math from scratch in python you could look at the old project of mine BUT it's amateur code written by a student (me). On the other hand would be unfair not to share, here you go: parsing

*Not necessarily of different type in a programming sense, but certainly of different type or meaning to us.

Upvotes: 1

snakecharmerb
snakecharmerb

Reputation: 55679

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)

Upvotes: 2

Albin Paul
Albin Paul

Reputation: 3419

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

Upvotes: 2

Related Questions