Devolver el producto de una lista

157

¿Existe una manera más concisa, eficiente o simplemente pitónica de hacer lo siguiente?

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

EDITAR:

De hecho, encuentro que esto es marginalmente más rápido 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 da

('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)
Simon Watkins
fuente
3
Hay una diferencia funcional entre las opciones dadas aquí en que para una lista vacía las reducerespuestas generan un TypeError, mientras que la forrespuesta en bucle devuelve 1. Este es un error en la forrespuesta en bucle (el producto de una lista vacía no es más de 1 que 17 o 'armadillo').
Scott Griffiths
55
Intente evitar el uso de nombres de elementos integrados (como list) para los nombres de sus variables.
Mark Byers
2
Vieja respuesta, pero estoy tentado de edición para que no se utilice listcomo nombre de variable ...
Beroe
13
El producto de una lista vacía es 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley
1
@ScottGriffiths Debería haber especificado que me refería a una lista de números. Y yo diría que la suma de una lista vacía es el elemento de identidad +para ese tipo de lista (del mismo modo para producto / *). Ahora me doy cuenta de que Python está tipeado dinámicamente, lo que dificulta las cosas, pero este es un problema resuelto en idiomas sanos con sistemas de tipo estático como Haskell. Pero de todos modos Pythonsolo permite sumtrabajar en números, ya sum(['a', 'b'])que ni siquiera funciona, por lo que nuevamente digo que 0tiene sentido para sumy 1para el producto.
punto

Respuestas:

169

Sin usar lambda:

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

Es mejor y más rápido. Con 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)

En la siguiente configuración:

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 con python 2.7.5

       El | 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.prodes el más rápido, si lo utiliza np.arraycomo estructura de datos (18x para matriz pequeña, 250x para matriz grande)

con python 3.3.2:

       El | 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

¿Python 3 es más lento?

Ruggero Turra
fuente
1
Muy interesante, gracias. ¿Alguna idea de por qué Python 3 podría ser más lento?
Simon Watkins el
3
Posibles razones: (1) Python 3 intes Python 2 long. Python 2 usará "int" hasta que se desborde 32 bits; Python 3 usará "largo" desde el principio. (2) Python 3.0 era una "prueba de concepto". ¡Actualice a 3.1 CUANTO ANTES!
John Machin
1
He rehecho la misma prueba en otra máquina: python 2.6 ('con lambda:', 21.843887090682983) ('sin lambda:', 9.7096879482269287) python 3.1: con lambda: 24.7712180614 sin lambda: 10.7758350372
Ruggero Turra
1
ambos fallan con listas vacías.
error
9
Tenga en cuenta que debe importar el reduceoperador desde el functoolsmódulo en Python 3. IE from functools import reduce.
Chris Mueller
50
reduce(lambda x, y: x * y, list, 1)
Johannes Charra
fuente
3
Haga +1 pero vea la respuesta de @ wiso sobre operator.muluna mejor manera de hacerlo.
Chris Lutz
¿Por qué es operator.mul preferible a x * y?
Adam Hughes
2
operator.mul es una función y, por lo tanto, sería un reemplazo no solo para x * y sino para toda la expresión lambda (es decir, el primer argumento para reduce)
Johannes Charra
66
Tienes que hacer una importación from functools import reducepara que funcione en Python 3.
lifebalance
45

si solo tienes números en tu lista:

from numpy import prod
prod(list)

EDITAR : como señaló @ off99555, esto no funciona para resultados enteros grandes, en cuyo caso devuelve un resultado de tipo, numpy.int64mientras que la solución de Ian Clelland se basa operator.muly reducefunciona para resultados enteros grandes porque devuelve long.

Andre Holzner
fuente
esto es más lento si la lista es corta
endolith
1
Intenté evaluar from numpy import prod; prod(list(range(5,101)))y salió 0, ¿puedes reproducir este resultado en Python 3?
off99555
1
porque proddevuelve un resultado de tipo numpy.int64en este caso y ya obtienes un desbordamiento (un valor negativo en realidad) range(5,23). Utilice la solución de @Ian Clelland basada en operator.muly reducepara enteros grandes (devuelve un longen este caso que parece tener una precisión arbitraria).
Andre Holzner
@ off99555 Dos soluciones: comenzar con una lista de tipo flotante haciendo np.prod(np.arange(5.0,101.0))o convertirla en flotante haciendo np.prod(np.array(range(5,101)).astype(np.float64)). Tenga en cuenta que NumPy usa en np.float64lugar de float. No se la diferencia.
Madera
22

Bueno, si realmente quisieras hacer una línea sin importar nada, podrías hacer:

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

Pero no lo hagas.

punto y coma
fuente
Bastante pitón en esencia
Jitin
Ty para el sol sin importar nada!
John D hace
18
import operator
reduce(operator.mul, list, 1)
Ian Clelland
fuente
1
¿Es realmente necesario el último argumento (1)?
Ruggero Turra
10
El último argumento es necesario si la lista puede estar vacía, de lo contrario arrojará una excepción TypeError. Por supuesto, a veces una excepción será lo que quieras.
Dave Kirby el
2
Para mí, devuelve 0 sin ese argumento, por lo que también puede considerar necesario aplicar la convención de producto vacía.
error
o functools.reduce(..)en python3
Andre
18

Comenzando Python 3.8, prodse ha incluido una función al mathmódulo en la biblioteca estándar:

math.prod (iterable, *, inicio = 1)

que devuelve el producto de un startvalor (predeterminado: 1) multiplicado por un iterable de números:

import math

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

Tenga en cuenta que si el iterable está vacío, esto producirá 1(o el startvalor si se proporciona).

Xavier Guihot
fuente
15

Recuerdo algunas largas discusiones sobre comp.lang.python (lo siento, demasiado flojo para producir punteros ahora) que concluyeron que su product()definición original es la más Pythonic .

Tenga en cuenta que la propuesta no es escribir un bucle for cada vez que quiera hacerlo, sino escribir una función una vez (por tipo de reducción) y llamarla según sea necesario. Llamar a las funciones de reducción es muy Pythonic: funciona muy bien con las expresiones generadoras y, desde la introducción exitosa sum(), Python sigue creciendo cada vez más funciones de reducción incorporadas, any()y all()son las últimas incorporaciones ...

Esta conclusión es un poco oficial: reduce()se eliminó de las incorporaciones en Python 3.0, diciendo:

"Úselo functools.reduce()si realmente lo necesita; sin embargo, el 99 por ciento de las veces un bucle explícito for es más legible".

Consulte también El destino de reduce () en Python 3000 para obtener una cita de apoyo de Guido (y algunos comentarios de menos apoyo de Lispers que leyeron ese blog).

PD: si por casualidad necesitas product()combinatoria, ver math.factorial()(nuevo 2.6).

Beni Cherniavsky-Paskin
fuente
2
+1 para una descripción precisa (que yo sepa) de los estados de ánimo prevalecientes en la comunidad de Python, aunque definitivamente prefiero ir en contra de dichos estados de ánimo prevalecientes en este caso, de todos modos es mejor conocerlos por lo que son. Además, me gusta un poco acerca de Lispers sin apoyo de LtU (supongo que sería uno de esos). :-)
Michał Marczyk
7

La intención de esta respuesta es proporcionar un cálculo que sea útil en ciertas circunstancias, es decir, cuando a) se multiplique una gran cantidad de valores de manera que el producto final pueda ser extremadamente grande o extremadamente pequeño, yb) usted no ' Realmente no me importa la respuesta exacta, sino que tiene una serie de secuencias y quiero poder ordenarlas en función del producto de cada uno.

Si desea multiplicar los elementos de una lista, donde l es la lista, puede hacer lo siguiente:

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

Ahora, ese enfoque no es tan legible como

from operator import mul
reduce(mul, list)

Si eres un matemático que no está familiarizado con reduce (), lo contrario podría ser cierto, pero no recomendaría usarlo en circunstancias normales. También es menos legible que la función producto () mencionada en la pregunta (al menos para los no matemáticos).

Sin embargo, si alguna vez se encuentra en una situación en la que corre el riesgo de desbordamiento o desbordamiento, como en

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

y su propósito es comparar los productos de diferentes secuencias en lugar de saber cuáles son los productos, luego

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

es el camino a seguir porque es prácticamente imposible tener un problema del mundo real en el que se desborde o desborde con este enfoque. (Cuanto mayor sea el resultado de ese cálculo, mayor será el producto si pudiera calcularlo).

garyrob
fuente
1
Es inteligente, pero falla si tiene valores negativos o cero. : /
Alex Meiburg
7

He probado varias soluciones con perfplot (un pequeño proyecto mío) y descubrí que

numpy.prod(lst)

es, con mucho, la solución más rápida (si la lista no es muy corta).

ingrese la descripción de la imagen aquí


Código para reproducir la trama:

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,
)
Nico Schlömer
fuente
2

Me sorprende que nadie haya sugerido usar itertools.accumulatecon operator.mul. Esto evita el uso reduce, que es diferente para Python 2 y 3 (debido a la functoolsimportación requerida para Python 3), y además el propio Guido van Rossum lo considera antipitónico :

from itertools import accumulate
from operator import mul

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

Ejemplo:

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

Una opción es usar numbay el @jito @njitdecorador . También hice uno o dos pequeños ajustes a su código (al menos en Python 3, "lista" es una palabra clave que no debe usarse para un nombre de variable):

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

Para fines de temporización, debe ejecutar una vez para compilar la función primero usando numba. En general, la función se compilará la primera vez que se llame y luego se llamará desde la memoria (más rápido).

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

Ahora, cuando ejecute su código, se ejecutará con la versión compilada de la función. Los cronometré usando un cuaderno Jupyter y la %timeitfunción 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)

Tenga en cuenta que en mi máquina, ejecutando Python 3.5, el forbucle nativo de Python fue en realidad el más rápido. Puede haber un truco aquí cuando se trata de medir el rendimiento decorado con numba con los portátiles Jupyter y la %timeitfunción mágica. No estoy seguro de que los tiempos anteriores sean correctos, por lo que recomiendo probarlo en su sistema y ver si numba le brinda un aumento de rendimiento.

Engineero
fuente
0

La forma más rápida que encontré fue, 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])'))

y los horarios son:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895
descarga de aplicaciones
fuente
Esto probablemente se deba a la corta extensión de la lista, es probable que se necesite más experimentación
craymichael
0

Resultado de Python 3 para las pruebas de OP: (el mejor de 3 para cada uno)

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
Adhitya Ganesan
fuente
-4

Esto también funciona a pesar de su trampa

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]    
usuario2120413
fuente
He arreglado la sangría, pero creo que deberías reemplazar la última printcon un retorno. Además, no es necesario almacenar los valores intermedios en una lista, solo necesita almacenar pentre iteraciones.
BoppreH