Retornando o produto de uma lista


157

Existe uma maneira mais concisa, eficiente ou simplesmente pitônica de fazer o seguinte?

def product(list):
    p = 1
    for i in list:
        p *= i
    return p

EDITAR:

Na verdade, acho que isso é marginalmente mais rápido do que usar operator.mul:

from operator import mul
# from functools import reduce # python3 compatibility

def with_lambda(list):
    reduce(lambda x, y: x * y, list)

def without_lambda(list):
    reduce(mul, list)

def forloop(list):
    r = 1
    for x in list:
        r *= x
    return r

import timeit

a = range(50)
b = range(1,50)#no zero
t = timeit.Timer("with_lambda(a)", "from __main__ import with_lambda,a")
print("with lambda:", t.timeit())
t = timeit.Timer("without_lambda(a)", "from __main__ import without_lambda,a")
print("without lambda:", t.timeit())
t = timeit.Timer("forloop(a)", "from __main__ import forloop,a")
print("for loop:", t.timeit())

t = timeit.Timer("with_lambda(b)", "from __main__ import with_lambda,b")
print("with lambda (no 0):", t.timeit())
t = timeit.Timer("without_lambda(b)", "from __main__ import without_lambda,b")
print("without lambda (no 0):", t.timeit())
t = timeit.Timer("forloop(b)", "from __main__ import forloop,b")
print("for loop (no 0):", t.timeit())

me dá

('with lambda:', 17.755449056625366)
('without lambda:', 8.2084708213806152)
('for loop:', 7.4836349487304688)
('with lambda (no 0):', 22.570688009262085)
('without lambda (no 0):', 12.472226858139038)
('for loop (no 0):', 11.04065990447998)

3
Há uma diferença funcional entre as opções fornecidas aqui, pois para uma lista vazia as reducerespostas aumentam a TypeError, enquanto a forresposta do loop retorna 1. Esse é um erro na forresposta do loop (o produto de uma lista vazia não é mais 1 do que 17) ou 'tatu').
Scott Griffiths

5
Por favor, tente evitar o uso de nomes de built-ins (como lista) para os nomes de suas variáveis.
Mark Byers

2
Velha resposta, mas estou tentado a edição para que ele não usar listcomo um nome de variável ...
Beroe

13
O produto de uma lista vazia é 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley

1
@ ScottGriffiths Eu deveria ter especificado que quis dizer uma lista de números. E eu diria que a soma de uma lista vazia é o elemento de identidade +desse tipo de lista (da mesma forma para product / *). Agora percebo que o Python é digitado dinamicamente, o que dificulta as coisas, mas esse é um problema resolvido em linguagens sãs com sistemas de tipos estáticos como Haskell. Mas Pythonsó permite sumtrabalhar com números de qualquer maneira, já sum(['a', 'b'])que nem funciona, então eu digo novamente que 0faz sentido para sume 1para o produto.
ponto

Respostas:


169

Sem usar o lambda:

from operator import mul
reduce(mul, list, 1)

é melhor e mais rápido. Com python 2.7.5

from operator import mul
import numpy as np
import numexpr as ne
# from functools import reduce # python3 compatibility

a = range(1, 101)
%timeit reduce(lambda x, y: x * y, a)   # (1)
%timeit reduce(mul, a)                  # (2)
%timeit np.prod(a)                      # (3)
%timeit ne.evaluate("prod(a)")          # (4)

Na seguinte configuração:

a = range(1, 101)  # A
a = np.array(a)    # B
a = np.arange(1, 1e4, dtype=int) #C
a = np.arange(1, 1e5, dtype=float) #D

Resultados com python 2.7.5

       | 1 | 2 3 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 A 20,8 µs 13,3 µs 22,6 µs 39,6 µs     
 B 106 µs 95,3 µs 5,92 µs 26,1 µs
 C 4,34 ms 3,51 ms 16,7 µs 38,9 µs
 D 46,6 ms 38,5 ms 180 µs 216 µs

Resultado: np.prodé o mais rápido, se você usar np.arraycomo estrutura de dados (18x para array pequeno, 250x para array grande)

com python 3.3.2:

       | 1 | 2 3 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 A 23,6 µs 12,3 µs 68,6 µs 84,9 µs     
 B 133 µs 107 µs 7,42 µs 27,5 µs
 C 4,79 ms 3,74 ms 18,6 µs 40,9 µs
 D 48,4 ms 36,8 ms 187 µs 214 µs

O python 3 é mais lento?


1
Muito interessante, obrigado. Alguma idéia de por que o python 3 pode ser mais lento?
Simon Watkins

3
Possíveis razões: (1) Python 3 inté Python 2 long. O Python 2 estará usando "int" até estourar 32 bits; O Python 3 usará "long" desde o início. (2) Python 3.0 foi uma "prova de conceito". Atualize para 3.1 o mais rápido possível!
John Machin

1
Eu refiz o mesmo teste em outra máquina: python 2.6 ('com lambda:', 21.843887090682983) ('sem lambda:', 9.7096879482269287) python 3.1: com lambda: 24.7712180614 sem lambda: 10.7758350372
Ruggero Turra

1
ambos falham com listas vazias.
bug

9
Observe que você deve importar o reduceoperador do functoolsmódulo no Python 3. IE from functools import reduce.
Chris Mueller

50
reduce(lambda x, y: x * y, list, 1)

3
+1, mas veja a resposta de @ wiso operator.mulpara obter uma maneira melhor de fazê-lo.
Chris Lutz

por que o operador.mul é preferível a x * y?
Adam Hughes

2
operator.mul é uma função e seria, portanto, uma substituição não só para x * y, mas para toda a expressão lambda (ou seja, o primeiro argumento a reduce)
Johannes Charra

6
Você tem que fazer uma importação from functools import reducepara fazê-lo funcionar em Python 3.
lifebalance

45

se você apenas tiver números em sua lista:

from numpy import prod
prod(list)

EDIT : como apontado por @ off99555, isso não funciona para resultados inteiros grandes. Nesse caso, ele retorna um resultado do tipo, numpy.int64enquanto a solução de Ian Clelland se baseia operator.mule reducetrabalha para resultados inteiros grandes porque retorna long.


isto é mais lento se a lista é curta
endolith

1
Eu tentei avaliar from numpy import prod; prod(list(range(5,101)))e saiu 0, você pode reproduzir esse resultado no Python 3?
off99555

1
porque prodretorna um resultado do tipo numpy.int64neste caso e você recebe um estouro (um valor negativo, na verdade) já range(5,23). Use a solução de @Ian Clelland baseada em operator.mule reducepara números inteiros grandes (ele retorna a longnesse caso, que parece ter precisão arbitrária).
11136 Andre Holzner

@ off99555 Duas soluções: comece com uma lista de tipos de flutuação executando np.prod(np.arange(5.0,101.0))ou converta-a em flutuação executando np.prod(np.array(range(5,101)).astype(np.float64)). Observe que o NumPy usa em np.float64vez de float. Não sei a diferença
Madeira

22

Bem, se você realmente quisesse criar uma linha sem importar nada, você poderia fazer:

eval('*'.join(str(item) for item in list))

Mas não.


Muito Pythonic em essência
Jitin 07/07

18
import operator
reduce(operator.mul, list, 1)

1
o último argumento (1) é realmente necessário?
Ruggero Turra

10
O último argumento é necessário se a lista estiver vazia, caso contrário, lançará uma exceção TypeError. Claro, às vezes uma exceção será o que você deseja.
Dave Kirby

2
Para mim, ele retorna 0 sem esse argumento; portanto, você também pode considerar necessário aplicar a convenção de produto vazio.
bug

ou functools.reduce(..)em python3
Andre Holzner

17

Iniciando Python 3.8, uma prodfunção foi incluída no mathmódulo na biblioteca padrão:

math.prod (iterável, *, start = 1)

que retorna o produto de um startvalor (padrão: 1) vezes um número iterável de números:

import math

math.prod([2, 3, 4]) # 24

Observe que se o iterável estiver vazio, isso produzirá 1(ou o startvalor, se fornecido).


15

Lembro-me de longas discussões sobre comp.lang.python (desculpe, com preguiça de produzir ponteiros agora), que concluíram que sua product()definição original é a mais pitonica .

Observe que a proposta não é gravar um loop for toda vez que você desejar, mas escrever uma função uma vez (por tipo de redução) e chamá-la conforme necessário! Chamar funções de redução é muito Pythonic - funciona suavemente com expressões geradoras e, desde a introdução bem-sucedida de sum(), Python continua crescendo cada vez mais funções de redução integradas - any()e all()são as adições mais recentes ...

Esta conclusão é meio oficial - reduce()foi removida dos buildins no Python 3.0, dizendo:

"Use functools.reduce()se você realmente precisar; no entanto, 99% das vezes um loop for explícito é mais legível."

Veja também O destino de reduzir () no Python 3000 para obter uma citação de suporte do Guido (e alguns comentários menos favoráveis ​​de Lispers que leem esse blog).

PS se por acaso você precisar product()de combinatória, consulte math.factorial()(novo 2.6).


2
+1 para obter uma conta precisa (tanto quanto seja do meu conhecimento) dos humores predominantes na comunidade Python - embora eu prefira definitivamente ir contra os humores predominantes nesse caso, é melhor conhecê-los pelo que são. Além disso, eu gosto de um pouco de Lispers sem suporte do LtU (eu seria um deles, eu acho). :-)
Michał Marczyk

7

A intenção desta resposta é fornecer um cálculo que seja útil em determinadas circunstâncias - a saber, quando: a) há um grande número de valores sendo multiplicados, de modo que o produto final pode ser extremamente grande ou extremamente pequeno eb) você não realmente não me importo com a resposta exata, mas sim com várias seqüências e queremos poder solicitá-las com base no produto de cada um.

Se você deseja multiplicar os elementos de uma lista, onde l é a lista, você pode:

import math
math.exp(sum(map(math.log, l)))

Agora, essa abordagem não é tão legível quanto

from operator import mul
reduce(mul, list)

Se você é um matemático que não está familiarizado com reduzir (), o oposto pode ser verdadeiro, mas eu não recomendaria usá-lo em circunstâncias normais. Também é menos legível que a função product () mencionada na pergunta (pelo menos para não matemáticos).

No entanto, se você estiver em uma situação em que corre o risco de excesso ou excesso, como em

>>> reduce(mul, [10.]*309)
inf

e seu objetivo é comparar os produtos de diferentes seqüências em vez de saber quais são os produtos,

>>> sum(map(math.log, [10.]*309))
711.49879373515785

é o caminho a seguir, porque é praticamente impossível ter um problema do mundo real no qual você transbordaria ou transbordaria com essa abordagem. (Quanto maior o resultado desse cálculo, maior o produto seria se você pudesse calculá-lo.)


1
É inteligente, mas falha se você tiver valores negativos ou zero. : /
Alex Meiburg

7

Testei várias soluções com perfplot (um pequeno projeto meu) e descobri que

numpy.prod(lst)

é de longe a solução mais rápida (se a lista não for muito curta).

insira a descrição da imagem aqui


Código para reproduzir o gráfico:

import perfplot
import numpy

import math
from operator import mul
from functools import reduce

from itertools import accumulate


def reduce_lambda(lst):
    return reduce(lambda x, y: x * y, lst)


def reduce_mul(lst):
    return reduce(mul, lst)


def forloop(lst):
    r = 1
    for x in lst:
        r *= x
    return r


def numpy_prod(lst):
    return numpy.prod(lst)


def math_prod(lst):
    return math.prod(lst)


def itertools_accumulate(lst):
    for value in accumulate(lst, mul):
        pass
    return value


perfplot.show(
    setup=numpy.random.rand,
    kernels=[reduce_lambda, reduce_mul, forloop, numpy_prod, itertools_accumulate, math_prod],
    n_range=[2 ** k for k in range(15)],
    xlabel="len(a)",
    logx=True,
    logy=True,
)

2

Estou surpreso que ninguém sugeriu usar itertools.accumulatecom operator.mul. Isso evita o uso reduce, que é diferente para o Python 2 e 3 (devido à functoolsimportação necessária para o Python 3) e, além disso, é considerado não-pitônico pelo próprio Guido van Rossum :

from itertools import accumulate
from operator import mul

def prod(lst):
    for value in accumulate(lst, mul):
        pass
    return value

Exemplo:

prod([1,5,4,3,5,6])
# 1800

1

Uma opção é usar numbae o @jitou @njitdecorador . Também fiz um ou dois pequenos ajustes no seu código (pelo menos no Python 3, "list" é uma palavra-chave que não deve ser usada para um nome de variável):

@njit
def njit_product(lst):
    p = lst[0]  # first element
    for i in lst[1:]:  # loop over remaining elements
        p *= i
    return p

Para propósitos de tempo, você precisa executar uma vez para compilar a função primeiro usando o numba. Em geral, a função será compilada na primeira vez que for chamada e, em seguida, chamada da memória depois disso (mais rápido).

njit_product([1, 2])  # execute once to compile

Agora, quando você executar seu código, ele será executado com a versão compilada da função. Programei-os usando um notebook Jupyter e a %timeitfunção mágica:

product(b)  # yours
# 32.7 µs ± 510 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

njit_product(b)
# 92.9 µs ± 392 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Observe que na minha máquina, executando o Python 3.5, o forloop nativo do Python era realmente o mais rápido. Pode haver um truque aqui quando se trata de medir o desempenho decorado com números com os notebooks Jupyter e a %timeitfunção mágica. Não tenho certeza de que os horários acima estão corretos, por isso recomendo experimentá-lo no seu sistema e ver se o numba oferece um aumento de desempenho.


0

A maneira mais rápida que encontrei foi usando while:

mysetup = '''
import numpy as np
from find_intervals import return_intersections 
'''

# code snippet whose execution time is to be measured
mycode = '''

x = [4,5,6,7,8,9,10]
prod = 1
i = 0
while True:
    prod = prod * x[i]
    i = i + 1
    if i == len(x):
        break
'''

# timeit statement for while:
print("using while : ",
timeit.timeit(setup=mysetup,
              stmt=mycode))

# timeit statement for mul:
print("using mul : ",
    timeit.timeit('from functools import reduce;
    from operator import mul;
    c = reduce(mul, [4,5,6,7,8,9,10])'))

# timeit statement for mul:
print("using lambda : ",      
    timeit.timeit('from functools import reduce;
    from operator import mul;
    c = reduce(lambda x, y: x * y, [4,5,6,7,8,9,10])'))

e os horários são:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895

Isto é provavelmente devido à curta duração da lista, é provável que seja necessária mais alguma experimentação
craymichael

0

Resultado do Python 3 para os testes do OP: (melhor de 3 para cada)

with lambda: 18.978000981995137
without lambda: 8.110567473006085
for loop: 10.795806062000338
with lambda (no 0): 26.612515013999655
without lambda (no 0): 14.704098362999503
for loop (no 0): 14.93075215499266

-4

Isso também funciona, apesar de sua trapaça

def factorial(n):
    x=[]
    if n <= 1:
        return 1
    else:
        for i in range(1,n+1): 
            p*=i
            x.append(p)
        print x[n-1]    

Corrigi o recuo, mas acho que você deve substituir o último printpor um retorno. Além disso, não há necessidade de armazenar os valores intermediários em uma lista, você só precisa armazenar pentre iterações.
BoppreH 18/10
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.