Rückgabe des Produkts einer Liste

157

Gibt es eine präzisere, effizientere oder einfach pythonischere Möglichkeit, Folgendes zu tun?

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

BEARBEITEN:

Ich finde tatsächlich, dass dies geringfügig schneller ist als die Verwendung von 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())

gibt mir

('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
quelle
3
Es gibt einen funktionalen Unterschied zwischen den hier angegebenen Optionen, da für eine leere Liste die reduceAntworten a auslösen TypeError, während die forSchleifenantwort 1 zurückgibt. Dies ist ein Fehler in der forSchleifenantwort (das Produkt einer leeren Liste ist nicht mehr 1 als 17 oder "Gürteltier").
Scott Griffiths
5
Bitte vermeiden Sie die Verwendung von Namen von integrierten Funktionen (z. B. Liste) für die Namen Ihrer Variablen.
Mark Byers
2
Alte Antwort, aber ich bin versucht zu bearbeiten, so dass es nicht listals Variablenname verwendet wird ...
Beroe
13
Das Produkt einer leeren Liste ist 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley
1
@ScottGriffiths Ich hätte angeben sollen, dass ich eine Liste von Zahlen meinte. Und ich würde sagen, dass die Summe einer leeren Liste das Identitätselement +für diesen Listentyp ist (ebenfalls für product / *). Jetzt ist mir klar, dass Python dynamisch typisiert ist, was die Sache schwieriger macht, aber dies ist ein gelöstes Problem in vernünftigen Sprachen mit statischen Typsystemen wie Haskell. Aber Pythonerlaubt sumsowieso nur , an Zahlen zu arbeiten, da sum(['a', 'b'])es nicht einmal funktioniert, also sage ich noch einmal, dass 0das für sumund 1für das Produkt Sinn macht .
Semikolon

Antworten:

169

Ohne Lambda:

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

es ist besser und schneller. Mit 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)

In der folgenden Konfiguration:

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

Ergebnisse mit Python 2.7.5

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

Ergebnis: np.prodist das schnellste, wenn Sie es np.arrayals Datenstruktur verwenden (18x für kleines Array, 250x für großes Array)

mit Python 3.3.2:

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

Ist Python 3 langsamer?

Ruggero Turra
quelle
1
Sehr interessant, danke. Irgendeine Idee, warum Python 3 langsamer sein könnte?
Simon Watkins
3
Mögliche Gründe: (1) Python 3 intist Python 2 long. Python 2 verwendet "int", bis 32 Bit überlaufen. Python 3 wird von Anfang an "long" verwenden. (2) Python 3.0 war ein "Proof of Concept". Upgrade auf 3.1 so schnell wie möglich!
John Machin
1
Ich habe den gleichen Test auf einem anderen Computer wiederholt: Python 2.6 ('mit Lambda:', 21.843887090682983) ('ohne Lambda:', 9.7096879482269287) Python 3.1: mit Lambda: 24.7712180614 ohne Lambda: 10.7758350372
Ruggero Turra
1
beide scheitern mit leeren Listen.
Fehler
9
Beachten Sie, dass Sie den reduceOperator aus dem functoolsModul in Python 3 importieren müssen . IE from functools import reduce.
Chris Mueller
50
reduce(lambda x, y: x * y, list, 1)
Johannes Charra
quelle
3
+1, aber siehe @ wisos Antwort operator.mulfür einen besseren Weg, dies zu tun.
Chris Lutz
Warum ist operator.mul x * y vorzuziehen?
Adam Hughes
2
operator.mul ist eine Funktion und wäre somit ein Ersatz nicht nur für x * y, sondern für den gesamten Lambda-Ausdruck (dh das erste Argument dafür reduce)
Johannes Charra
6
Sie müssen einen Import durchführen from functools import reduce, damit es in Python 3
funktioniert
45

Wenn Sie nur Zahlen in Ihrer Liste haben:

from numpy import prod
prod(list)

BEARBEITEN : Wie von @ off99555 hervorgehoben, funktioniert dies nicht für Ergebnisse mit großen Ganzzahlen. In diesem Fall wird ein Ergebnis vom Typ zurückgegeben, numpy.int64während Ian Clellands Lösung auf Ergebnissen mit großen Ganzzahlen basiert operator.mulund reducefür diese Ergebnisse funktioniert, da sie zurückgegeben wird long.

Andre Holzner
quelle
Dies ist langsamer, wenn die Liste kurz ist
Endolith
1
Ich habe versucht zu evaluieren from numpy import prod; prod(list(range(5,101)))und es wurde ausgegeben. Können 0Sie dieses Ergebnis auf Python 3 reproduzieren?
off99555
1
weil in diesem Fall prodein Ergebnis vom Typ zurückgibt numpy.int64und Sie bereits einen Überlauf (tatsächlich einen negativen Wert) für erhalten range(5,23). Verwenden Sie die @ Ian Clelland-Lösung, die auf operator.mulund reducefür große Ganzzahlen basiert ( longin diesem Fall wird eine zurückgegeben, die eine willkürliche Genauigkeit zu haben scheint).
Andre Holzner
@ off99555 Zwei Lösungen: Beginnen Sie entweder mit einer Float-Typ-Liste, indem Sie sie ausführen, np.prod(np.arange(5.0,101.0))oder konvertieren Sie sie mit einer Float-Typ-Liste np.prod(np.array(range(5,101)).astype(np.float64)). Beachten Sie, dass NumPy np.float64anstelle von verwendet float. Ich kenne den Unterschied nicht.
Wood
22

Nun, wenn Sie wirklich eine Zeile erstellen möchten, ohne etwas zu importieren, können Sie Folgendes tun:

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

Aber nicht.

Semikolon
quelle
Ganz pythonisch im Wesentlichen
Jitin
18
import operator
reduce(operator.mul, list, 1)
Ian Clelland
quelle
1
ist das letzte Argument (1) wirklich notwendig?
Ruggero Turra
10
Das letzte Argument ist erforderlich, wenn die Liste leer sein kann, andernfalls wird eine TypeError-Ausnahme ausgelöst. Natürlich ist manchmal eine Ausnahme das, was Sie wollen.
Dave Kirby
2
Für mich wird ohne dieses Argument 0 zurückgegeben, sodass Sie es auch für erforderlich halten können, die Konvention für leere Produkte durchzusetzen.
Fehler
oder functools.reduce(..)in python3
Andre Holzner
17

Ab dem Start Python 3.8wurde eine prodFunktion in das mathModul in der Standardbibliothek aufgenommen:

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

Dies gibt das Produkt eines startWertes (Standard: 1) mal einer iterierbaren Zahl zurück:

import math

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

Beachten Sie, dass wenn die Iterable leer ist, dies erzeugt 1(oder den startWert, falls angegeben).

Xavier Guihot
quelle
15

Ich erinnere mich an einige lange Diskussionen über comp.lang.python (sorry, zu faul, um jetzt Zeiger zu erstellen), die zu dem Schluss kamen, dass Ihre ursprüngliche product()Definition die pythonischste ist .

Beachten Sie, dass der Vorschlag nicht darin besteht, jedes Mal eine for-Schleife zu schreiben, sondern eine Funktion einmal (pro Reduktionsart) zu schreiben und sie nach Bedarf aufzurufen! Das Aufrufen von Reduktionsfunktionen ist sehr pythonisch - es funktioniert hervorragend mit Generatorausdrücken, und seit der erfolgreichen Einführung von sum()Python werden immer mehr integrierte Reduktionsfunktionen entwickelt - any()und all()es handelt sich um die neuesten Ergänzungen ...

Diese Schlussfolgerung ist ein bisschen offiziell - reduce()wurde aus den in Python 3.0 integrierten Funktionen entfernt und lautet:

"Verwenden functools.reduce()Sie es, wenn Sie es wirklich brauchen. In 99 Prozent der Fälle ist eine explizite for-Schleife jedoch besser lesbar."

Siehe auch Das Schicksal von redu () in Python 3000 für ein unterstützendes Zitat von Guido (und einige weniger unterstützende Kommentare von Lispers, die diesen Blog lesen).

PS Wenn Sie zufällig product()Kombinatorik benötigen , siehe math.factorial()(neu 2.6).

Beni Cherniavsky-Paskin
quelle
2
+1 für eine genaue (nach meinem besten Wissen) Darstellung der in der Python-Community vorherrschenden Stimmungen - obwohl ich es in diesem Fall definitiv vorziehe, gegen die vorherrschenden Stimmungen vorzugehen, ist es am besten, sie so zu kennen, wie sie sind. Ich mag auch das bisschen über nicht unterstützende Lispers von LtU (ich würde einer von denen sein, denke ich). :-)
Michał Marczyk
7

Mit dieser Antwort soll eine Berechnung bereitgestellt werden, die unter bestimmten Umständen nützlich ist - nämlich wenn a) eine große Anzahl von Werten so multipliziert wird, dass das Endprodukt extrem groß oder extrem klein sein kann, und b) Sie dies nicht tun. Ich kümmere mich nicht wirklich um die genaue Antwort, sondern habe stattdessen eine Reihe von Sequenzen und möchte sie basierend auf dem jeweiligen Produkt bestellen können.

Wenn Sie die Elemente einer Liste multiplizieren möchten, wobei l die Liste ist, können Sie Folgendes tun:

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

Dieser Ansatz ist nicht so lesbar wie

from operator import mul
reduce(mul, list)

Wenn Sie ein Mathematiker sind, der mit redu () nicht vertraut ist, ist das Gegenteil der Fall, aber ich würde nicht empfehlen, es unter normalen Umständen zu verwenden. Es ist auch weniger lesbar als die in der Frage erwähnte Funktion product () (zumindest für Nicht-Mathematiker).

Wenn Sie sich jedoch jemals in einer Situation befinden, in der Sie einen Unter- oder Überlauf riskieren, z

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

und Ihr Zweck ist es, die Produkte verschiedener Sequenzen zu vergleichen, anstatt zu wissen, was die Produkte sind

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

Dies ist der richtige Weg, da es praktisch unmöglich ist, ein reales Problem zu haben, bei dem Sie mit diesem Ansatz über- oder unterlaufen würden. (Je größer das Ergebnis dieser Berechnung ist, desto größer wäre das Produkt, wenn Sie es berechnen könnten .)

Garyrob
quelle
1
Es ist klug, schlägt aber fehl, wenn Sie negative oder Nullwerte haben. : /
Alex Meiburg
7

Ich habe verschiedene Lösungen mit perfplot (einem kleinen Projekt von mir) getestet und festgestellt, dass

numpy.prod(lst)

ist bei weitem die schnellste Lösung (wenn die Liste nicht sehr kurz ist).

Geben Sie hier die Bildbeschreibung ein


Code zur Reproduktion der Handlung:

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
quelle
2

Ich bin überrascht, dass niemand vorgeschlagen hat, mit itertools.accumulatezu verwenden operator.mul. Dies vermeidet die Verwendung reduce, die für Python 2 und 3 unterschiedlich ist (aufgrund des functoolsfür Python 3 erforderlichen Imports), und wird außerdem von Guido van Rossum selbst als nicht-pythonisch angesehen :

from itertools import accumulate
from operator import mul

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

Beispiel:

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

Eine Option ist die Verwendung von numbaund @jitoder @njitDekorateur . Ich habe auch ein oder zwei kleine Änderungen an Ihrem Code vorgenommen (zumindest in Python 3 ist "list" ein Schlüsselwort, das nicht für einen Variablennamen verwendet werden sollte):

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

Aus Timing-Gründen müssen Sie einmal ausführen, um die Funktion zuerst mit numba zu kompilieren. Im Allgemeinen wird die Funktion beim ersten Aufruf kompiliert und danach (schneller) aus dem Speicher aufgerufen.

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

Wenn Sie jetzt Ihren Code ausführen, wird er mit der kompilierten Version der Funktion ausgeführt. Ich habe sie mit einem Jupyter-Notizbuch und der %timeitmagischen Funktion zeitlich festgelegt :

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)

Beachten Sie, dass auf meinem Computer mit Python 3.5 die native Python- forSchleife tatsächlich die schnellste war. Hier kann es einen Trick geben, wenn es darum geht, die Leistung von numba-dekorierten Jupyter-Notebooks und die %timeitmagische Funktion zu messen . Ich bin mir nicht sicher, ob die oben genannten Timings korrekt sind. Ich empfehle daher, sie auf Ihrem System auszuprobieren und zu prüfen, ob Sie mit numba einen Leistungsschub erzielen.

Engineero
quelle
0

Der schnellste Weg, den ich gefunden habe, war, while zu verwenden:

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])'))

und die Zeiten sind:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895
Apps herunterladen
quelle
Dies ist wahrscheinlich aufgrund der kurzen Länge der Liste, einige weitere Experimente sind wahrscheinlich erforderlich
craymichael
0

Python 3-Ergebnis für die OP-Tests: (jeweils das Beste aus 3)

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
quelle
-4

Dies funktioniert auch durch Betrug

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
quelle
Ich habe die Einrückung korrigiert, aber ich denke, Sie sollten die letzte printdurch eine Rückgabe ersetzen . Außerdem müssen die Zwischenwerte nicht in einer Liste gespeichert werden, psondern nur zwischen den Iterationen.
BoppreH