【问题标题】:List comprehension as substitute for reduce() in Python列表推导代替 Python 中的 reduce()
【发布时间】:2016-04-07 18:27:36
【问题描述】:

下面的python教程说:

列表推导完全替代了 lambda 函数以及函数 map()filter()reduce()

http://python-course.eu/python3_list_comprehension.php

但是,它没有提到列表理解如何替代reduce() 的示例,我想不出一个示例应该如何实现。

能否请人解释一下如何通过列表理解实现类似 reduce 的功能,或者确认这是不可能的?

【问题讨论】:

  • "删除了 reduce()。如果你真的需要它,请使用 functools.reduce();但是,99% 的时间显式 for 循环更具可读性。" (source)
  • 我建议您为您的学习寻找更好的资源。引用的页面说“发电机理解”,这不是正确的术语,给出的解释也不是那么令人满意。

标签: python list python-3.x list-comprehension


【解决方案1】:

理想情况下,列表理解是创建一个新列表。引用official documentation

列表推导式提供了一种创建列表的简洁方法。 常见的应用是创建新列表,其中每个元素是应用于另一个序列或可迭代的每个成员的某些操作的结果,或者创建一个满足一定条件的元素的子序列。

reduce 用于将可迭代对象减少为单个值。引用functools.reduce

将两个参数的函数从左到右累积应用于序列项,以将序列减少为单个值

因此,列表推导不能用作reduce 的直接替代品。

【讨论】:

    【解决方案2】:

    起初我很惊讶地发现 Python 的创建者 Guido van Rossum 反对reduce。他的推理是,除了求和、乘法、and-ing 和 or-ing 之外,使用 reduce 会产生一个不可读的解决方案,该解决方案更适合迭代和更新累加器的函数。他关于此事的文章是here。所以不,没有列表解析替代reduce,而是“pythonic”方式是用老式方式实现一个累加函数:

    代替:

    out = reduce((lambda x,y: x*y),[1,2,3])

    用途:

    def prod(myList):
        out = 1
        for el in myList:
            out *= el
        return out
    

    当然,没有什么能阻止你继续使用 reduce (python 2) 或 functools.reduce (python 3)

    【讨论】:

      【解决方案3】:

      列表推导应该返回列表。如果你的 reduce 应该返回一个列表,那么是的,你可以用列表理解替换它。

      但这并不是提供“类reduce 功能”的障碍。 Python 列表可以包含任何对象。如果您接受包含在单项列表中的结果,那么有一个 [...][0] 列表理解表单可以替换任何 reduce()

      这应该是显而易见的,但这种形式是

      [x for x in [reduce(function, sequence, initial)]][0]
      

      对于一些二进制 function 和一些可迭代的 sequence 和一些 initial 值。或者,如果您想要第一个可迭代对象中的 initial

      [x for x in [reduce(function, sequence)]][0]
      

      可以说,以上内容是作弊,而且毫无意义,因为您可以在没有理解的情况下使用reduce。所以让我们在没有reduce 的情况下尝试一下。

      [stack.append(function(stack.pop(), e)) or stack[0]
       for stack in ([initial],)
       for e in sequence][-1]
      

      这会生成所有中间值的列表,我们想要最后一个。 [-1][0] 一样简单。我们需要一个累加器来减少,但不能在理解中使用赋值语句,因此stack(这只是一个列表),但我们可以在这里使用许多其他数据结构。 .append() 始终返回 None,因此我们使用 or stack[0] 将到目前为止的值放入结果列表中。

      没有initial会有点困难,

      [stack.append(function(stack.pop(), e)) or stack[0]
       for it in [iter(sequence)]
       for stack in [[next(it)]]
       for e in it][-1]
      

      真的,此时您不妨使用for 语句。


      但这会占用中间值列表的内存。对于很长的序列,这可能是个问题。但是我们也可以通过使用生成器表达式来避免这种情况。

      这样做很棘手,所以让我们从一个更简单的示例开始并逐步完成。

      stack = [initial]
      [stack.append(function(stack.pop(), e)) for e in sequence]
      stack.pop()  # returns the answer
      

      它会计算答案,但也会创建一个无用的Nones 列表。我们可以通过将其转换为列表推导式中的生成器表达式来避免这种情况。

      stack = [initial]
      [_ for _s in (stack.append(function(stack.pop(), e)) or ()
                    for e in sequence)
       for _ in _s]
      stack.pop()
      

      列表推导耗尽了更新堆栈的生成器,但本身返回一个空列表。这是可能的,因为内部循环总是有零次迭代,因为_s 总是一个空元组。

      如果最后一个_s 有一个元素,我们可以将stack.pop() 移动到里面。不过,那个元素是什么并不重要。因此,我们将[None] 链接为最终的_s

      from itertools import chain
      stack = [initial]
      [stack.pop()
       for _s in chain((stack.append(function(stack.pop(), e)) or ()
                        for e in sequence),
                       [[None]])
       for _ in _s][0]
      

      同样,我们有一个单项列表理解。我们还可以将chain 实现为生成器表达式。您已经了解了如何使用单项列表将 stack 变量移到内部。

      [stack.pop()
       for stack in [[initial]]
       for _s in (
               x
               for xs in [
                       (stack.append(function(stack.pop(), e)) or ()
                        for e in sequence),
                       [[None]],
               ]
               for x in xs)
       for _ in _s][0]
      

      我们还可以从两个参数reduce的序列中获取初始值。

      [stack.pop()
       for it in [iter(sequence)]
       for stack in [[next(it)]]
       for _s in (
               x
               for xs in [
                       (stack.append(function(stack.pop(), e)) or ()
                        for e in it),
                       [[None]],
               ]
               for x in xs)
       for _ in _s][0]
      

      这太疯狂了。但它有效。所以是的,可能通过理解获得“类似减少的功能”。这并不意味着您应该。七个fors太难了!

      【讨论】:

        【解决方案4】:

        您可以通过使用我命名为lastcofold 的几个辅助函数来完成类似reduce 的操作:

        >>> last(r(a+b) for a, b, r in cofold(range(10)))
        45
        

        这在功能上等同于

        >>> reduce(lambda a, b: a+b, range(10))
        45
        

        请注意,与 reduce() 不同,理解没有使用 lambda

        诀窍是使用带有回调的生成器来“返回”运算符的结果。 cofold 是减少(或折叠)函数的 corecursive dual

        _sentinel = object()
        def cofold(it, initial=_sentinel):
            if initial is _sentinel:
                it = iter(it)
                accumulator = next(it)
            else:
                accumulator = initial
            def callback(result):
                nonlocal accumulator 
                accumulator = result
                return result
            for element in it:
                yield accumulator, element, callback
        

        这是列表理解中的cofold

        >>> [r(a+b) for a, b, r in cofold(range(10))]
        [1, 3, 6, 10, 15, 21, 28, 36, 45]
        

        元素代表对偶归约中的每个步骤。最后一个是我们的答案。 last 函数很简单。

        def last(it):
            for e in it:
                pass
            return e
        

        reduce 不同,cofold 是一个惰性生成器,因此在生成器表达式中使用时它可以安全地作用于无限迭代。

        >>> from itertools import islice, count
        >>> lazy_results = (r(a+b) for a, b, r in cofold(count()))
        >>> [*islice(lazy_results, 0, 9)]
        [1, 3, 6, 10, 15, 21, 28, 36, 45]
        >>> next(lazy_results)
        55
        >>> next(lazy_results)
        66
        

        【讨论】:

          猜你喜欢
          • 2012-05-04
          • 2021-05-02
          • 2016-02-13
          • 2012-02-22
          • 1970-01-01
          • 2010-11-07
          • 1970-01-01
          • 1970-01-01
          • 2010-10-28
          相关资源
          最近更新 更多