Restituzione del prodotto di un elenco


157

Esiste un modo più conciso, efficace o semplicemente pitone per eseguire le seguenti operazioni?

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

MODIFICARE:

In realtà trovo che questo è leggermente più veloce rispetto all'utilizzo di 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())

mi 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)

3
C'è una differenza funzionale tra le opzioni fornite qui in quanto per un elenco vuoto le reducerisposte generano a TypeError, mentre la forrisposta del ciclo restituisce 1. Questo è un bug nella forrisposta del ciclo (il prodotto di un elenco vuoto non è più 1 di quanto non sia 17 o "armadillo").
Scott Griffiths,

5
Cerca di evitare di usare i nomi dei built-in (come l'elenco) per i nomi delle tue variabili.
Mark Byers,

2
Vecchia risposta, ma sono tentato di modificare in modo che non utilizzi listcome nome variabile ...
beroe

13
Il prodotto di un elenco vuoto è 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley,

1
@ScottGriffiths Avrei dovuto specificare che intendevo un elenco di numeri. E direi che la somma di una lista vuota è l'elemento identità +per quel tipo di lista (anche per prodotto / *). Ora mi rendo conto che Python è tipizzato in modo dinamico, il che rende le cose più difficili, ma questo è un problema risolto in linguaggi sani con sistemi di tipo statico come Haskell. Ma Pythonconsente solo sumdi lavorare sui numeri, dato sum(['a', 'b'])che non funziona nemmeno, quindi dico di nuovo che 0ha senso per sume 1per il prodotto.
punto

Risposte:


169

Senza usare lambda:

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

è migliore e più veloce. 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)

Nella seguente configurazione:

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

Risultati con 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

Risultato: np.prodè il più veloce, se si utilizza np.arraycome struttura dati (18x per array piccolo, 250x per array grande)

con 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

Python 3 è più lento?


1
Molto interessante, grazie. Qualche idea sul perché Python 3 potrebbe essere più lento?
Simon Watkins,

3
Possibili ragioni: (1) Python 3 intè Python 2 long. Python 2 utilizzerà "int" fino a quando non trabocca a 32 bit; Python 3 utilizzerà "long" dall'inizio. (2) Python 3.0 era una "prova di concetto". Aggiornamento a 3.1 APPENA POSSIBILE!
John Machin,

1
Ho rifatto lo stesso test su un'altra macchina: python 2.6 ('with lambda:', 21.843887090682983) ('senza lambda:', 9.7096879482269287) python 3.1: con lambda: 24.7712180614 senza lambda: 10.7758350372
Ruggero Turra

1
entrambi falliscono con liste vuote.
bug

9
Nota che devi importare l' reduceoperatore dal functoolsmodulo in Python 3. IE from functools import reduce.
Chris Mueller,

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

3
+1 ma vedi la risposta di @ wiso operator.mulper un modo migliore di farlo.
Chris Lutz,

perché operator.mul è preferibile a x * y?
Adam Hughes,

2
operator.mul è una funzione e sarebbe quindi una sostituzione non solo per x * y ma per l'intera espressione lambda (ovvero il primo argomento di reduce)
Johannes Charra

6
Devi fare un'importazione from functools import reduceper farlo funzionare in Python 3.
Lifebalance

45

se hai solo numeri nella tua lista:

from numpy import prod
prod(list)

EDIT : come sottolineato da @ off99555 questo non funziona per risultati di numeri interi di grandi dimensioni, nel qual caso restituisce un risultato di tipo numpy.int64mentre la soluzione di Ian Clelland si basa operator.mule reducefunziona per risultati di numeri interi di grandi dimensioni perché restituisce long.


questo è più lento se l'elenco è breve
endolito il

1
Ho provato a valutare from numpy import prod; prod(list(range(5,101)))ed è stato emesso 0, puoi riprodurre questo risultato su Python 3?
off99555

1
perché in questo caso prodrestituisce un risultato di tipo numpy.int64e si ottiene già un overflow (un valore negativo) per range(5,23). Usa la soluzione di @Ian Clelland basata su operator.mule reduceper interi di grandi dimensioni (restituisce a longin questo caso che sembra avere una precisione arbitraria).
Andre Holzner,

@ off99555 Due soluzioni: iniziare con un elenco di tipi float facendo np.prod(np.arange(5.0,101.0))o convertendolo in float facendo np.prod(np.array(range(5,101)).astype(np.float64)). Nota che NumPy utilizza np.float64invece di float. Non conosco la differenza.
Wood,

22

Bene, se davvero volessi fare una riga senza importare nulla che potresti fare:

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

Ma non farlo.


Abbastanza Pythonic in sostanza
Jitin

ty per il sol senza importare nulla!
John D

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

1
l'ultimo argomento (1) è davvero necessario?
Ruggero Turra,

10
L'ultimo argomento è necessario se l'elenco può essere vuoto, altrimenti genererà un'eccezione TypeError. Certo, a volte un'eccezione sarà ciò che desideri.
Dave Kirby,

2
Per me restituisce 0 senza tale argomento, quindi puoi anche considerare necessario applicare la convenzione di prodotto vuota.
bug

o functools.reduce(..)in python3
Andre Holzner,

18

A partire Python 3.8, una prodfunzione è stata inclusa nel mathmodulo nella libreria standard:

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

che restituisce il prodotto di un startvalore (valore predefinito: 1) volte un iterabile di numeri:

import math

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

Nota che se l'iterabile è vuoto, questo produrrà 1(o il startvalore se fornito).


15

Ricordo alcune lunghe discussioni su comp.lang.python (scusate, troppo pigro per produrre puntatori ora) che hanno concluso che la vostra product()definizione originale è la più Pythonic .

Nota che la proposta non è quella di scrivere un ciclo for ogni volta che vuoi farlo, ma di scrivere una funzione una volta (per tipo di riduzione) e chiamarla secondo necessità! Chiamare le funzioni di riduzione è molto Pythonic - funziona dolcemente con le espressioni del generatore e, poiché la riuscita introduzione di sum(), Python continua a crescere sempre più funzioni di riduzione integrate - any()eall() sono le ultime aggiunte ...

Questa conclusione è un po 'ufficiale - è reduce()stata rimossa dai builtin in Python 3.0, dicendo:

"Usa functools.reduce()se ne hai davvero bisogno; tuttavia, il 99 percento delle volte un ciclo esplicito per è più leggibile."

Vedi anche Il destino di reduce () in Python 3000 per una citazione di supporto di Guido (e alcuni commenti meno di supporto di Lispers che leggono quel blog).

PS se per caso hai bisogno product()di combinatoria, vedi math.factorial()(nuovo 2.6).


2
+1 per un resoconto accurato (per quanto ne so) degli stati d'animo prevalenti nella comunità di Python - mentre in questo caso preferisco assolutamente andare contro gli stati d'animo prevalenti in questo caso, è meglio conoscerli per quello che sono comunque. Inoltre, mi piace un po 'di Lispers non supportati da LtU (sarei uno di quelli, immagino). :-)
Michał Marczyk,

7

Lo scopo di questa risposta è fornire un calcolo utile in determinate circostanze, vale a dire quando a) si moltiplica un gran numero di valori in modo tale che il prodotto finale possa essere estremamente grande o estremamente piccolo e b) non si ci tengo davvero alla risposta esatta, ma invece hanno un numero di sequenze e vogliono essere in grado di ordinarle in base al prodotto di ciascuno.

Se vuoi moltiplicare gli elementi di un elenco, dove l è l'elenco, puoi fare:

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

Ora, questo approccio non è così leggibile come

from operator import mul
reduce(mul, list)

Se sei un matematico che non ha familiarità con riduzioni (), potrebbe essere vero il contrario, ma non consiglierei di usarlo in circostanze normali. È anche meno leggibile della funzione product () menzionata nella domanda (almeno per i non matematici).

Tuttavia, se mai ti trovi in ​​una situazione in cui rischi di underflow o overflow, ad esempio in

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

e il tuo scopo è quello di confrontare i prodotti di diverse sequenze piuttosto che sapere quali sono i prodotti, quindi

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

è la strada da percorrere perché è praticamente impossibile avere un problema nel mondo reale in cui traboccerebbe o traboccherebbe con questo approccio. (Maggiore è il risultato di tale calcolo, maggiore sarà il prodotto se si potesse calcolarlo.)


1
È intelligente, ma fallisce se hai valori negativi o zero. : /
Alex Meiburg

7

Ho testato varie soluzioni con perfplot (un mio piccolo progetto) e l' ho scoperto

numpy.prod(lst)

è di gran lunga la soluzione più veloce (se l'elenco non è molto breve).

inserisci qui la descrizione dell'immagine


Codice per riprodurre 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,
)

2

Sono sorpreso che nessuno abbia suggerito di usarlo itertools.accumulatecon operator.mul. Questo evita l'utilizzo reduce, che è diverso per Python 2 e 3 (a causa functoolsdell'importazione richiesta per Python 3), e inoltre è considerato non-pitonico dallo stesso Guido van Rossum :

from itertools import accumulate
from operator import mul

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

Esempio:

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

1

Una possibilità è quella di utilizzare numbae la @jito il @njitdecoratore . Ho anche apportato una o due piccole modifiche al tuo codice (almeno in Python 3, "list" è una parola chiave che non dovrebbe essere usata per un nome di variabile):

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

Per scopi di temporizzazione, è necessario eseguire una volta per compilare la funzione utilizzando numba. In generale, la funzione verrà compilata la prima volta che viene chiamata e successivamente richiamata dalla memoria (più veloce).

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

Ora quando esegui il tuo codice, verrà eseguito con la versione compilata della funzione. Li ho cronometrati usando un notebook Jupyter e la %timeitfunzione magica:

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)

Nota che sulla mia macchina, con Python 3.5, il forloop nativo di Python era in realtà il più veloce. Potrebbe esserci un trucco qui quando si tratta di misurare le prestazioni decorate con numba con i notebook Jupyter e la %timeitfunzione magica. Non sono sicuro che i tempi sopra indicati siano corretti, quindi ti consiglio di provarlo sul tuo sistema e vedere se numba ti dà un aumento delle prestazioni.


0

Il modo più veloce che ho trovato è stato 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 i tempi sono:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895

Ciò è probabilmente dovuto alla breve lunghezza dell'elenco, probabilmente sono necessarie ulteriori sperimentazioni
Craymichael,

0

Risultato Python 3 per i test del PO: (migliore di 3 per ciascuno)

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

Questo funziona anche se tradisce

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]    

Ho corretto il rientro, ma penso che dovresti sostituire l'ultimo print con un ritorno. Inoltre, non è necessario memorizzare i valori intermedi in un elenco, è sufficiente memorizzare ptra le iterazioni.
BoppreH,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.