【问题标题】:How to make reduce function take three parameters in Python?如何使reduce函数在Python中接受三个参数?
【发布时间】:2022-01-10 13:40:42
【问题描述】:

在 python 中,reduce 接受一个只接受两个参数的函数。
有没有什么优雅的方法可以让reduce 接受两个以上的参数?
例如:

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)

编辑:

reduce3函数用于计算抽象语法树(AST)的结果。
AST 的每个孩子都可以是一个节点或另一棵树。 为简单起见,假设叶子节点只能是数字,加法符号(+)或减法符号(-)。计算结果,一个直观的想法是首先计算出每个节点的结果(可以实现通过递归调用方法),然后将它们组合在一起。 例如,这是一个简单的 AST:

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

递归后得到如下结果。

如果有一个reduce 取3 个参数,我们可以很容易地组合结果。

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

【问题讨论】:

  • 它很特别,你可能找不到一个功能。
  • 没有。当然,您可以构建一个,但它不再与reduce 相关。请注意,如果reduce/add 是柯里化形式,您可以改写[add(1), add(2), add(3)],这与reduce 再次兼容。

标签: python functional-programming


【解决方案1】:

我很想听听您想要的用例是什么,因为我认为 reduce 采用 2 个以上的参数并不优雅。这是我的理由(虽然在抽象上争论有点困难,没有具体的用例):

假设归约操作采用 3 个参数,如您的示例所示。我们也让初始累加器显式化,reduce3(func,list,acc)。然后在每次调用 func 时:

  • 第一个参数是像往常一样的累加器
  • 第二个参数始终是列表中奇数位置的元素
  • 第三个参数始终位于列表中的偶数位置

即使是列表中的定位元素,func 对奇数定位元素的处理也是不同的。如果奇数和偶数位置的元素类型不同,我们真的需要这样的 reduce3 函数!* 示例中就是这种情况。在一个列表中混合不同类型的元素需要更加小心,不要犯错误(弄乱一些 2 个元素的顺序并繁荣 reduce3 中断),一般应避免使用 IMO。

修改你的例子:

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)

减少元组列表是您问题的一般解决方案。如果您要评估数学表达式,则应考虑它们的树结构,当折叠到列表时会丢失。

编辑:

您确实想要评估数学表达式。考虑更改解析这些表达式的方式,以便示例中的 AST 如下所示:

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

也就是说,内部节点总是用二进制操作标记,只有叶子携带数字。现在将树简化为一个值是微不足道的,但代价是解析完成大部分工作。

# 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))

如果您允许非二叉树节点,则将“else”分支与 reduce 交换,例如 reduce(strToOpt[expr.op], expr.children)

我知道这是非常不同的方法。如果您想坚持下去,Reduce3 应该不会那么难实现,对吧?有关在 python 中从头开始解析数学的一些帮助,您可以查看我的旧项目,但它是由学生(我)编写的业余代码。另一方面,不分享是不公平的,给你:parsing

*在编程意义上不一定是不同的类型,但对我们来说肯定是不同的类型或意义。

【讨论】:

  • 感谢您的回答。我已经为这个问题添加了一个补充
【解决方案2】:

reduce 无法更改,但您可以提供处理不同输入的可调用对象:

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)

或者你可以批量输入元组,并使用可以处理这些的可调用对象

def reducer(total, next_):
    value, operation = next_
    return operation(total, value)


result = reduce(reducer, zip(L[::2], L[1::2]), 0)
print(result)

【讨论】:

    【解决方案3】:

    这给出了没有 3 个参数的确切结果。 如果参数类似于1, add,它将在每个参数上创建一个偏函数,如果参数类似于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
    

    【讨论】:

    • 部分+1很好用
    • 优雅的解决方案!
    • 谢谢大家:)
    猜你喜欢
    • 1970-01-01
    • 2013-11-04
    • 2012-11-02
    • 2021-09-09
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多