【问题标题】:When would you use reduce() instead of sum()?什么时候使用 reduce() 而不是 sum()?
【发布时间】:2015-11-18 04:53:17
【问题描述】:

我最近开始学习函数式编程,并在尝试计算我的班级测验平均成绩时想出了这个例子。

我想出的例子是:

scores = [90, 91, 92, 94, 95, 96, 97, 99, 100]

def add(num1, num2):
    '''returns the sum of the parameters'''
    return num1 + num2

import operator 

timeit reduce(add, scores) / len(scores)  #--> 1000000 loops, best of 3: 799 ns per loop

timeit sum(scores) / len(scores)  #--> 1000000 loops, best of 3: 207 ns per loop

timeit reduce(operator.add, scores) / len(scores) #--> 1000000 loops, best of 3: 485 ns per loop

在上面的例子中,使用高阶函数似乎慢了近 4 倍。

所以我的问题是,什么时候是使用高阶函数的好时机,因为显然上面的例子不是?

【问题讨论】:

  • 使用operator.add 代替自定义函数怎么样?另外,有两点: 1. sum 可能看起来更具可读性,具体取决于代码的作用(在您的情况下会这样); 2.Guido does not like reduce
  • @repzero 你说的功能细节是什么意思?
  • @fjarri 使用 operator.add 添加大小写,sum 仍然更快。我同意 sum 在大多数情况下更具可读性,但我想知道高阶函数何时会产生任何性能优势或其他类型的优势。

标签: python functional-programming profiling


【解决方案1】:

reduce() 在您需要对数据列表进行任意 操作时才有意义,而不是当您已经拥有一个经过高度优化的库函数时,它不仅在小列表上的性能优于 reduce(),而且大大在较大的情况下优于它。

reduce() 为您提供了创建任意折叠的灵活性,但这种灵活性是以牺牲一些性能开销为代价的,尤其是在大多数基本函数构造都被认为略微超出主流的语言中。

Python 是“函数式”的,因为它具有一流的函数,但它主要不是函数式语言。它提供了大量用于循环的迭代器,并具有使显式循环易于编写的各种语言特性,但并不专注于递归定义的列表操作(尽管它确实在有限的程度上允许它们——缺乏 TCO例如,阻止我直接在 Python 中解释我的 Erlang 或 Guile 代码,但确实让我可以灵活地执行诸如 benchmark competing approaches that adhere to similar interfaces 之类的事情。

【讨论】:

    【解决方案2】:

    reducesum 做非常不同的事情。考虑一个问题,例如“我有一个嵌套字典......

    d = {'foo': {'bar': {'baz': 'qux'}}}
    

    我想获取与键列表关联的值:['foo', 'bar', 'baz']"。这可以调用reduce(如果您是函数式编程类型的人) :

    >>> reduce(lambda subdict, k: subdict[k], ['foo', 'bar', 'baz'], d)
    'qux'
    

    注意,sum 不能这样做。恰好求和是一个简单的例子来展示 reduce 发生了什么(因为你可以用括号把它写出来,而且大多数程序员都熟悉括号是如何对数学运算进行分组的)。

    【讨论】:

    • 虽然这是一个很好的例子,但这里不需要自定义 lambda,因为您基本上已经重新定义了自己的 dict.get:您可以编写 reduce(dict.get, ['foo', 'bar', 'baz'], d)
    • 真的,应该是reduce(dict.__getitem__, ["foo", "bar", "baz"], d)。然后,如果密钥没有丢失,则恢复 KeyError 行为,而不是变成可能的 TypeError: NoneType is not subscriptable :)。
    【解决方案3】:

    抛开性能问题不谈,我不得不说:使用sum() 一点问题都没有,而且在风格上你应该选择sum() 而不是reduce()reduce() 更通用,因此可用于编写其他归约,而不仅仅是求和。 sum() 是一种很常见的简化,它值得拥有自己的名称和定义。

    如果您查看函数式编程语言,您会发现例如它们具有用于处理序列的大型通用实用函数库,例如 Haskell 的 Data.List 或 Scheme 的 SRFI-1。这些库中的很多函数可以用其他函数来编写;例如,Haskell 中的map 函数可以写成foldr(类似于reduce()):

    map :: (a -> b) -> [a] -> [b]
    map f = foldr go []
      where f a bs = f a : bs
    

    但是没有人争辩说foldr 从而使map 变得不必要或需要避免。相反,foldrreduce() 等更通用的操作被视为构建块,用于构建更专业的函数,使程序更易于编写和理解。

    reduce()sum() 具有相同的关系。 reduce() 是一个构建块,当您还没有像 sum() 这样的功能时,您可以使用它。

    【讨论】:

      【解决方案4】:

      代替总和?从不。

      但是当通过自定义方法进行聚合时,reduce 调用将是一种可行的方法。

      例如product 可以定义为:

      product = lambda iterable: reduce(operator.mul, iterable)
      

      sum 也是用 C 实现的。

      【讨论】:

      • 如果您添加的类型添加速度很慢并且您想避免添加一个怎么办?
      • 有一个重要区别:sum 始终使用起始值(“从左到右对可迭代的项目求和并返回总数。”)。 reduce(operator.add, ...) 仅在提供时才使用初始化程序(“如果存在可选初始化程序,则将其放置在计算中的可迭代项之前”)。
      • 在 python>=3.8 我们有math.prod 来避免reduce(operator.mul, )
      猜你喜欢
      • 1970-01-01
      • 2010-09-23
      • 1970-01-01
      • 1970-01-01
      • 2019-06-15
      • 2012-06-15
      • 2011-06-26
      • 2011-06-22
      • 2020-03-24
      相关资源
      最近更新 更多