Zwrot produktu z listy

157

Czy istnieje bardziej zwięzły, skuteczny lub po prostu pytoniczny sposób na wykonanie następujących czynności?

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

EDYTOWAĆ:

Właściwie uważam, że jest to nieznacznie szybsze niż użycie 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())

daje mi

('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
źródło
3
Istnieje funkcjonalna różnica między opcjami podanymi tutaj polegająca na tym, że dla pustej listy reduceodpowiedzi podnoszą a TypeError, podczas gdy forodpowiedź w pętli zwraca 1. Jest to błąd w forodpowiedzi pętli (iloczyn pustej listy jest nie większy niż 17 lub „pancernik”).
Scott Griffiths
5
Prosimy unikać używania nazw wbudowanych (takich jak lista) w nazwach zmiennych.
Mark Byers
2
Stara odpowiedź, ale kusi mnie, aby ją edytować, więc nie używa jej listjako nazwy zmiennej ...
beroe
13
Produkt z pustej listy to 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley
1
@ScottGriffiths Powinienem był sprecyzować, że mam na myśli listę liczb. I powiedziałbym, że suma pustej listy jest elementem tożsamości +dla tego typu listy (podobnie dla produktu / *). Teraz zdaję sobie sprawę, że Python jest typowany dynamicznie, co utrudnia sprawę, ale jest to problem rozwiązany w rozsądnych językach ze statycznymi systemami typów, takimi jak Haskell. Ale i tak Pythonpozwala tylko sumpracować na liczbach, ponieważ sum(['a', 'b'])nawet nie działa, więc znowu mówię, że 0ma to sens dla produktu sumi 1dla produktu.
średnik

Odpowiedzi:

169

Bez użycia lambda:

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

jest lepiej i szybciej. W Pythonie 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)

W następującej konfiguracji:

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

Wyniki w Pythonie 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

Wynik: np.prodjest najszybszy, jeśli używasz np.arrayjako struktury danych (18x dla małej tablicy, 250x dla dużej tablicy)

z pythonem 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

Czy Python 3 jest wolniejszy?

Ruggero Turra
źródło
1
Bardzo interesujące, dzięki. Masz jakiś pomysł, dlaczego Python 3 może być wolniejszy?
Simon Watkins,
3
Możliwe przyczyny: (1) Python 3 intto Python 2 long. Python 2 będzie używał „int”, dopóki nie przekroczy 32 bitów; Python 3 będzie używać „long” od początku. (2) Python 3.0 był „dowodem słuszności koncepcji”. Zaktualizuj do wersji 3.1 JAK NAJSZYBCIEJ!
John Machin
1
Mam przerobione ten sam test na innym komputerze: Python 2.6 (z lambda ':', 21,843887090682983) ( 'bez lambda:' +9,7096879482269287) python 3.1: z lambda: +24,7712180614 bez lambda: +10,7758350372
Ruggero Turra
1
oba kończą się niepowodzeniem z pustymi listami.
błąd
9
Zauważ, że musisz zaimportować reduceoperatora z functoolsmodułu w Pythonie 3. IE from functools import reduce.
Chris Mueller,
50
reduce(lambda x, y: x * y, list, 1)
Johannes Charra
źródło
3
+1, ale zobacz odpowiedź @ wiso na temat operator.mullepszego sposobu na zrobienie tego.
Chris Lutz,
dlaczego operator.mul jest lepszy od x * y?
Adam Hughes
2
operator.mul jest funkcją i dlatego zastąpiłby nie tylko x * y, ale całe wyrażenie lambda (tj. pierwszy argument do reduce)
Johannes Charra
6
Musisz wykonać import, from functools import reduceaby działał w Pythonie 3
lifebalance
45

jeśli masz tylko numery na swojej liście:

from numpy import prod
prod(list)

EDYCJA : jak wskazano w @ off99555, nie działa to w przypadku dużych wyników całkowitych, w którym to przypadku zwraca wynik typu, numpy.int64podczas gdy rozwiązanie Iana Clellanda jest oparte na dużych wynikach całkowitych operator.muli reducedziała z nimi, ponieważ zwraca long.

Andre Holzner
źródło
to jest wolniejsze, jeśli lista jest krótka
endolith
1
Próbowałem ocenić from numpy import prod; prod(list(range(5,101)))i wyszło 0, czy możesz odtworzyć ten wynik w Pythonie 3?
off99555
1
ponieważ w tym przypadku prodzwraca wynik typu numpy.int64i otrzymujesz przepełnienie (w rzeczywistości wartość ujemną) już dla range(5,23). Użyj rozwiązania @Ian Clelland opartego na operator.muli reducedla dużych liczb całkowitych (zwraca longw tym przypadku a, co wydaje się mieć dowolną precyzję).
Andre Holzner
@ off99555 Dwa rozwiązania: albo zacznij od listy typów zmiennoprzecinkowych, wykonując, np.prod(np.arange(5.0,101.0))albo przekonwertuj ją na typ pływający wykonując np.prod(np.array(range(5,101)).astype(np.float64)). Zauważ, że NumPy używa np.float64zamiast float. Nie znam różnicy.
Drewno
22

Cóż, jeśli naprawdę chcesz zrobić jedną linię bez importowania czegokolwiek, co możesz zrobić:

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

Ale nie rób tego.

średnik
źródło
Całkiem Pythonic w istocie
Jitin
ty dla sol bez importowania czegokolwiek!
John D
18
import operator
reduce(operator.mul, list, 1)
Ian Clelland
źródło
1
czy ostatni argument (1) jest naprawdę potrzebny?
Ruggero Turra
10
Ostatni argument jest niezbędny, jeśli lista może być pusta, w przeciwnym razie zgłosi wyjątek TypeError. Oczywiście czasami wyjątek będzie tym, czego chcesz.
Dave Kirby
2
U mnie zwraca 0 bez tego argumentu, więc możesz również uznać za konieczne wymuszenie konwencji pustego produktu.
błąd
lub functools.reduce(..)w pythonie3
Andre Holzner
18

Począwszy od modułu w bibliotece standardowej Python 3.8dodano prodfunkcję math:

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

która zwraca iloczyn startwartości (domyślnie: 1) razy iterowalna liczba:

import math

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

Zauważ, że jeśli iterowalna jest pusta, to da 1(lub startwartość, jeśli została podana).

Xavier Guihot
źródło
15

Pamiętam kilka długich dyskusji na temat comp.lang.python (przepraszam, zbyt leniwy, by teraz tworzyć wskaźniki), w wyniku których stwierdzono, że Twoja oryginalna product()definicja jest najbardziej Pythonowa .

Zwróć uwagę, że propozycja nie polega na pisaniu pętli for za każdym razem, gdy chcesz to zrobić, ale na jednorazowym napisaniu funkcji (według typu redukcji) i wywołaniu jej w razie potrzeby! Wywoływanie funkcji redukcyjnych jest bardzo Pythonowe - działa słodko z wyrażeniami generatora, a od udanego wprowadzenia sum(), Python stale rozwija się coraz więcej wbudowanych funkcji redukcyjnych - any()i all()są najnowszymi dodatkami ...

Ten wniosek jest trochę oficjalny - reduce() został usunięty z wbudowanych w Pythonie 3.0, mówiąc:

„Użyj, functools.reduce()jeśli naprawdę tego potrzebujesz; jednak w 99% przypadków jawna pętla for jest bardziej czytelna”.

Zobacz też The fate of reduction () in Python 3000, aby uzyskać pomocniczy cytat z Guido (i kilka mniej wspierających komentarzy Lispersów, którzy czytali ten blog).

PS, jeśli przypadkiem potrzebujesz product()kombinatoryki, zobacz math.factorial()(nowy 2.6).

Beni Cherniavsky-Paskin
źródło
2
+1 za dokładne (zgodnie z moją najlepszą wiedzą) opis panujących nastrojów w społeczności Pythona - chociaż zdecydowanie wolę w tym przypadku przeciwstawiać się wspomnianym dominującym nastrojom, najlepiej jest je znać. Podoba mi się też trochę o nieobsługiwanych Lisperach z LtU (chyba byłbym jednym z nich). :-)
Michał Marczyk
7

Celem tej odpowiedzi jest przedstawienie obliczeń przydatnych w pewnych okolicznościach - a mianowicie, gdy a) mnożonych jest duża liczba wartości, tak że produkt końcowy może być bardzo duży lub bardzo mały, oraz b) nie należy Bardzo zależy mi na dokładnej odpowiedzi, ale zamiast tego mam kilka sekwencji i chcę mieć możliwość zamówienia ich na podstawie każdego produktu.

Jeśli chcesz pomnożyć elementy listy, gdzie l jest listą, możesz:

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

To podejście nie jest tak czytelne jak

from operator import mul
reduce(mul, list)

Jeśli jesteś matematykiem, który nie jest zaznajomiony z redukcją (), może być odwrotnie, ale nie radziłbym używać go w normalnych okolicznościach. Jest też mniej czytelny niż wspomniana w pytaniu funkcja product () (przynajmniej dla nie-matematyków).

Jeśli jednak kiedykolwiek znajdziesz się w sytuacji, w której ryzykujesz niedomiar lub przepełnienie, na przykład w

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

a twoim celem jest porównanie produktów o różnych sekwencjach, a nie wiedzieć, jakie to produkty

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

jest drogą do zrobienia, ponieważ praktycznie niemożliwe jest wystąpienie problemu w świecie rzeczywistym, w którym można by przepełnić lub niedopełnić przy takim podejściu. (Im większy jest wynik tego obliczenia, tym większy byłby iloczyn, gdybyś mógł go obliczyć).

garyrob
źródło
1
Jest sprytny, ale zawodzi, jeśli masz jakiekolwiek wartości ujemne lub zerowe. : /
Alex Meiburg
7

Przetestowałem różne rozwiązania za pomocą perfplot ( mój mały projekt) i znalazłem to

numpy.prod(lst)

jest zdecydowanie najszybszym rozwiązaniem (jeśli lista nie jest zbyt krótka).

wprowadź opis obrazu tutaj


Kod do odtworzenia fabuły:

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
źródło
2

Jestem zaskoczony, że nikt nie zasugerował używania itertools.accumulatez operator.mul. Pozwala to uniknąć używania reduce, które jest inne dla Pythona 2 i 3 (ze względu na functoolsimport wymagany dla Pythona 3), a ponadto sam Guido van Rossum uważa, że ​​nie jest w Pythonie :

from itertools import accumulate
from operator import mul

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

Przykład:

prod([1,5,4,3,5,6])
# 1800
Chris_Rands
źródło
1

Jedną z opcji jest użycie numba i @jitlub @njitdekorator . Dokonałem również jednej lub dwóch drobnych poprawek w Twoim kodzie (przynajmniej w Pythonie 3 „lista” to słowo kluczowe, którego nie powinno się używać w nazwie zmiennej):

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

Ze względów czasowych musisz uruchomić raz, aby najpierw skompilować funkcję przy użyciu numba. Ogólnie rzecz biorąc, funkcja zostanie skompilowana przy pierwszym wywołaniu, a następnie wywołana z pamięci (szybciej).

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

Teraz, kiedy wykonasz swój kod, będzie on działał ze skompilowaną wersją funkcji. Zmierzyłem czas za pomocą notatnika Jupyter i %timeitmagicznej funkcji:

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)

Zauważ, że na moim komputerze, na którym działa Python 3.5, natywna forpętla Pythona była w rzeczywistości najszybsza. Może być tu pewien podstęp, jeśli chodzi o mierzenie wydajności ozdobionej numba za pomocą notebooków Jupyter i%timeit magiczną funkcją. Nie jestem pewien, czy powyższe czasy są prawidłowe, więc polecam wypróbowanie tego w systemie i sprawdzenie, czy numba zapewnia wzrost wydajności.

Engineero
źródło
0

Najszybszy sposób, jaki znalazłem, to użycie 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])'))

a czasy to:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895
appsdownload
źródło
Jest to prawdopodobnie spowodowane krótką listą, prawdopodobnie potrzebne są dalsze eksperymenty
craymichael
0

Wynik w Pythonie 3 dla testów OP: (najlepiej 3 dla każdego)

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
źródło
-4

Działa to również w przypadku oszustwa

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]    
user2120413
źródło
Naprawiłem wcięcie, ale myślę, że powinieneś zastąpić ostatni printzwrotem. Ponadto nie ma potrzeby przechowywania wartości pośrednich na liście, wystarczy przechowywać pmiędzy iteracjami.
BoppreH