【问题标题】:How to calculate a cumulative product of a list using list comprehension如何使用列表理解计算列表的累积乘积
【发布时间】:2020-09-26 02:12:12
【问题描述】:

我正在尝试将以下循环转换为理解。

问题给出了input_list = [1, 2, 3, 4, 5] 返回一个列表,其中每个元素都是所有元素的倍数,直到该索引从左到右开始。

因此返回列表将是[1, 2, 6, 24, 120]

我拥有的正常循环(并且正在工作):

l2r = list()
for i in range(lst_len):
    if i == 0:
        l2r.append(lst_num[i])
    else:
        l2r.append(lst_num[i] * l2r[i-1])

【问题讨论】:

标签: python list list-comprehension


【解决方案1】:

嗯,你可以这样做(a)

import math

orig = [1, 2, 3, 4, 5]
print([math.prod(orig[:pos]) for pos in range(1, len(orig) + 1)])

这会产生你想要的:

[1, 2, 6, 24, 120]

并且基本上通过运行从1 到列表大小的计数器来工作,在每个点计算该位置之前所有术语的乘积:

pos   values    prod
===  =========  ====
 1   1             1
 2   1,2           2
 3   1,2,3         6
 4   1,2,3,4      24
 5   1,2,3,4,5   120

(a) 请记住,这在运行时效率较低,因为它会计算每个元素的完整乘积(而不是缓存最近获得的乘积)。您可以在仍然使您的代码更紧凑(通常是使用列表推导的原因)的同时避免这种情况,例如:

def listToListOfProds(orig):
    curr = 1
    newList = []
    for item in orig:
        curr *= item
        newList.append(curr)
    return newList

print(listToListOfProds([1, 2, 3, 4, 5]))

这显然不是列表推导式,但仍具有优势,因为它不会在需要计算的地方弄乱代码。

人们似乎经常对 Python 中的函数解决方案不屑一顾,这仅仅是因为该语言的表现力如此之强,并且允许列表推导式之类的东西在最少的源代码中完成大量工作。

但是,除了函数本身之外,这个解决方案具有与单行列表理解相同的优点,因为它占用了一行:-)

此外,您可以随时更改函数(例如,如果您在以后的 Python 版本中找到更好的方法),更改调用它的代码中的所有不同位置。

【讨论】:

    【解决方案2】:

    Python 3.8+ 解决方案:

    lst = [1, 2, 3, 4, 5]
    
    curr = 1
    out = [(curr:=curr*v) for v in lst]
    print(out)
    

    打印:

    [1, 2, 6, 24, 120]
    

    其他解决方案(itertools.accumulate):

    from itertools import accumulate
    
    out = [*accumulate(lst, lambda a, b: a*b)]
    print(out)
    

    【讨论】:

    • 我对海象还是有点陌生​​,这里没有运行 3.8。 ( ) 有必要吗?
    • @MarkMeyer 不,在这种情况下他们不是。我只是出于习惯添加它们。
    • 这种风格真的很糟糕,你不应该依赖列表理解中的副作用。
    • @paxdiablo 列表推导是函数式编程结构,旨在表达声明性映射/过滤操作。这会以函数式风格创建一个奇怪的命令式代码混合体,并且它依赖于副作用。这是令人困惑和意想不到的。如果你想这样做,你应该只使用一个 for 循环。仅仅因为你可以在某些情况下使用赋值并不意味着你应该
    • @juanpa:但至少你已经回答了我的问题,为什么你认为它不是最佳的。我只是不确定你是否说服了我:-)
    【解决方案3】:

    对于您的列表,数字从 1 开始连续可能不是有意的。但对于有意使用该模式的情况,您可以使用内置方法 factorial()

    from math import factorial
    
    input_list = [1, 2, 3, 4, 5]
    l2r = [factorial(i) for i in input_list]
    
    print(l2r)
    

    输出:

    [1, 2, 6, 24, 120]
    

    【讨论】:

    • 我认为,如果列表的内容始终是连续整数1..n,那么拥有列表绝对没有意义,您只需提供@ 987654325@.
    • @paxdiablo,这也适用于非顺序列表。你用非顺序列表检查了吗?这给出了正确的答案,即使列表更改为非顺序。
    • @Srinika,它可能会为您提供列表中数字的阶乘,无论是顺序的还是其他的,但它不会像所要求的问题那样给出“多个直到该索引的所有元素从左到右开始”。示例:[2, 5, 3] 应该给出 [2, 10, 30] 但这个答案给出了 [2, 120, 6]
    【解决方案4】:

    如果一个迭代依赖于前一个迭代的状态,那么这个不应该变成一个列表推导式!

    如果目标是单线,那么有很多解决方案,@AndrejKesely 的itertools.accumulate() 是一个很好的解决方案 (+1)。这是我的滥用functools.reduce()

    from functools import reduce
    
    lst = [1, 2, 3, 4, 5]
    
    print(reduce(lambda x, y: x + [x[-1] * y], lst, [lst.pop(0)]))
    

    但就列表推导而言,@AndrejKesely 的基于赋值表达式的解决方案是错误的 (-1)。这是一个更独立的理解,不会泄漏到周围的范围内:

    lst = [1, 2, 3, 4, 5]
    
    seq = [a.append(a[-1] * b) or a.pop(0) for a in [[lst.pop(0)]] for b in [*lst, 1]]
    
    print(seq)
    

    但这仍然是错误的做法!这是基于 similar problem 也因为错误的原因而获得投票。

    【讨论】:

    • 由于意外泄漏,我假设您在谈论 Andre 的第一个建议中的 curr 变量(累积缓存的乘法)。如果这真的令人担忧,可以在理解后用简单的del curr 解决)。但我通常不会担心这一点,特别是因为这在一个可以很容易防止泄漏的函数中做得更好,而且大多数 Python 开发人员非常不整洁,他们只是把变量散落在地板上:-)
    【解决方案5】:

    numpy 内置了许多快速的列表解析实现。例如,要获得累积产品:

    >>> import numpy as np
    >>> np.cumprod([1, 2, 3, 4, 5])
    array([  1,   2,   6,  24, 120])
    

    上面返回一个numpy 数组。如果你不熟悉 numpy,你可能更喜欢只获取一个普通的 python 列表:

    >>> list(np.cumprod([1, 2, 3, 4, 5]))
    [1, 2, 6, 24, 120]
    

    【讨论】:

      【解决方案6】:

      递归函数会有所帮助。

      input_list = [ 1, 2, 3, 4, 5]
      
      def cumprod(ls, i=None):
          i = len(ls)-1 if i is None else i
          if i == 0:
              return 1
          return ls[i] * cumprod(ls, i-1)
      
      output_list = [cumprod(input_list, i) for i in range(len(input_list))]
      

      output_list 的值为 [1, 2, 6, 24, 120]


      此方法在python3.8中可以使用海象算子进行压缩

      input_list = [ 1, 2, 3, 4, 5]
      
      def cumprod_inline(ls, i=None):
          return 1 if (i := len(ls)-1 if i is None else i) == 0 else ls[i] * cumprod_inline(ls, i-1)
      
      output_list = [cumprod_inline(input_list, i) for i in range(len(input_list))]
      

      output_list 的值为 [1, 2, 6, 24, 120]


      因为您打算在列表理解中使用它,所以无需为 i 参数提供默认值。这样就无需检查 i 是否为 None

      input_list = [ 1, 2, 3, 4, 5]
      
      def cumprod_inline_nodefault(ls, i):
          return 1 if i == 0 else ls[i] * cumprod_inline_nodefault(ls, i-1)
      
      output_list = [cumprod_inline_nodefault(input_list, i) for i in range(len(input_list))]
      

      output_list 的值为 [1, 2, 6, 24, 120]


      最后,如果您真的想将其保留为单个、自包含的列表理解行,您可以按照方法说明 here 使用递归 lambda 调用

      input_list = [ 1, 2, 3, 4, 5]
      
      output_list = [(lambda func, x, y: func(func,x,y))(lambda func, ls, i: 1 if i == 0 else ls[i] * func(func, ls, i-1),input_list,i) for i in range(len(input_list))]
      

      output_list 的值为 [1, 2, 6, 24, 120]

      它完全是过度设计的,几乎看不懂,但是,嘿!它有效,只是为了好玩。

      【讨论】:

      • 这很好。为了更好,您应该解释 如何*/*为什么 代码解决了 OP 的问题。提供洞察力有助于访问者学习如何应用基本逻辑将任何循环转换为递归函数或列表理解。教某人如何思考三个问题和代码结构才是真正有价值的地方,并且最多的投票将来自于此。你是个好作家。考虑编辑以突出解决方案的重要方面。工作代码是必不可少的。知道如何思考它是无价的。
      【解决方案7】:

      使用迭代工具和操作符:

      from itertools import accumulate
      import operator as op
      
      ip_lst = [1,2,3,4,5]
      print(list(accumulate(ip_lst, func=op.mul)))
      
      

      【讨论】:

        猜你喜欢
        • 2016-07-23
        • 2020-12-17
        • 1970-01-01
        • 2017-10-05
        • 2013-01-26
        • 2020-07-18
        • 2013-02-24
        • 2019-07-01
        • 2018-03-26
        相关资源
        最近更新 更多