Retourner le produit d'une liste

157

Existe-t-il une manière plus concise, efficace ou simplement pythonique de faire ce qui suit?

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

ÉDITER:

Je trouve en fait que c'est légèrement plus rapide que d'utiliser 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())

Donne moi

('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
la source
3
Il y a une différence fonctionnelle entre les options données ici en ce que pour une liste vide, les reduceréponses lèvent a TypeError, alors que la forréponse en boucle renvoie 1. Il s'agit d'un bogue dans la forréponse en boucle (le produit d'une liste vide n'est pas plus 1 que 17 ou 'tatou').
Scott Griffiths
5
Essayez d'éviter d'utiliser des noms de fonctions intégrées (comme une liste) pour les noms de vos variables.
Mark Byers
2
Ancienne réponse, mais je suis tenté de modifier pour ne pas l'utiliser listcomme nom de variable ...
beroe
13
Le produit d'une liste vide est 1. fr.wikipedia.org/wiki/Empty_product
Paul Crowley
1
@ScottGriffiths J'aurais dû préciser que je voulais dire une liste de nombres. Et je dirais que la somme d'une liste vide est l'élément d'identité de +ce type de liste (de même pour le produit / *). Maintenant, je me rends compte que Python est typé dynamiquement, ce qui rend les choses plus difficiles, mais c'est un problème résolu dans des langages sains avec des systèmes de types statiques comme Haskell. Mais Pythonne permet sumde travailler que sur les chiffres de toute façon, car sum(['a', 'b'])cela ne fonctionne même pas, alors je répète que c'est 0logique pour sumet 1pour le produit.
point

Réponses:

169

Sans utiliser lambda:

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

c'est mieux et plus rapide. Avec 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)

Dans la configuration suivante:

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

Résultats avec 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

Résultat: np.prodest le plus rapide, si vous utilisez np.arraycomme structure de données (18x pour petit tableau, 250x pour grand tableau)

avec 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 est-il plus lent?

Ruggero Turra
la source
1
Très intéressant, merci. Une idée de la raison pour laquelle python 3 pourrait être plus lent?
Simon Watkins
3
Raisons possibles: (1) Python 3 intest Python 2 long. Python 2 utilisera "int" jusqu'à ce qu'il déborde de 32 bits; Python 3 utilisera "long" dès le début. (2) Python 3.0 était une "preuve de concept". Passez à 3.1 dès que possible!
John Machin
1
J'ai refait le même test sur une autre machine: python 2.6 ('avec lambda:', 21.843887090682983) ('sans lambda:', 9.7096879482269287) python 3.1: avec lambda: 24.7712180614 sans lambda: 10.7758350372
Ruggero Turra
1
les deux échouent avec des listes vides.
bug du
9
Notez que vous devez importer l' reduceopérateur depuis le functoolsmodule dans Python 3. IE from functools import reduce.
Chris Mueller
50
reduce(lambda x, y: x * y, list, 1)
Johannes Charra
la source
3
+1 mais voir la réponse de @ wiso sur operator.mulpour une meilleure façon de le faire.
Chris Lutz
pourquoi operator.mul est-il préférable à x * y?
Adam Hughes
2
operator.mul est une fonction et serait donc un remplacement non seulement pour x * y mais pour toute l'expression lambda (c'est-à-dire le premier argument de reduce)
Johannes Charra
6
Vous devez faire une importation from functools import reducepour le faire fonctionner en Python 3.
lifebalance
45

si vous n'avez que des numéros dans votre liste:

from numpy import prod
prod(list)

EDIT : comme indiqué par @ off99555, cela ne fonctionne pas pour les grands résultats entiers, auquel cas il retourne un résultat de type numpy.int64tandis que la solution d'Ian Clelland basée sur operator.mulet reducefonctionne pour les résultats de grands entiers car elle renvoie long.

André Holzner
la source
c'est plus lent si la liste est courte
endolith
1
J'ai essayé d'évaluer from numpy import prod; prod(list(range(5,101)))et il est sorti 0, pouvez-vous reproduire ce résultat sur Python 3?
off99555
1
car prodrenvoie un résultat de type numpy.int64dans ce cas et vous obtenez déjà un débordement (une valeur négative en fait) pour range(5,23). Utilisez la solution de @Ian Clelland basée sur operator.mulet reducepour les grands entiers (elle renvoie a longdans ce cas qui semble avoir une précision arbitraire).
Andre Holzner
@ off99555 Deux solutions: soit commencer avec une liste de type float en faisant, np.prod(np.arange(5.0,101.0))soit la convertir en float en faisant np.prod(np.array(range(5,101)).astype(np.float64)). Notez que NumPy utilise à la np.float64place de float. Je ne connais pas la différence.
Bois
22

Eh bien, si vous vouliez vraiment en faire une ligne sans importer quoi que ce soit, vous pourriez faire:

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

Mais ne le faites pas.

point virgule
la source
Assez pythonique par essence
Jitin
ty pour le sol sans rien importer!
John D il y a
18
import operator
reduce(operator.mul, list, 1)
Ian Clelland
la source
1
le dernier argument (1) est-il vraiment nécessaire?
Ruggero Turra
10
Le dernier argument est nécessaire si la liste peut être vide, sinon elle lèvera une exception TypeError. Bien sûr, parfois, une exception sera ce que vous voulez.
Dave Kirby
2
Pour moi, il renvoie 0 sans cet argument, vous pouvez donc également considérer qu'il est nécessaire d'appliquer la convention de produit vide.
bug du
ou functools.reduce(..)en python3
Andre Holzner
18

Au départ Python 3.8, une prodfonction a été intégrée au mathmodule dans la bibliothèque standard:

math.prod (itérable, *, début = 1)

qui renvoie le produit d'une startvaleur (par défaut: 1) fois un itérable de nombres:

import math

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

Notez que si l'itérable est vide, cela produira 1(ou la startvaleur si elle est fournie).

Xavier Guihot
la source
15

Je me souviens de quelques longues discussions sur comp.lang.python (désolé, trop paresseux pour produire des pointeurs maintenant) qui ont conclu que votre product()définition originale est la plus pythonique .

Notez que la proposition n'est pas d'écrire une boucle for à chaque fois que vous voulez le faire, mais d'écrire une fonction une fois (par type de réduction) et de l'appeler au besoin! L'appel de fonctions de réduction est très pythonique - cela fonctionne bien avec les expressions de générateur, et depuis l'introduction réussie de sum(), Python continue de développer de plus en plus de fonctions de réduction intégrées - any()et all()sont les derniers ajouts ...

Cette conclusion est un peu officielle - a reduce()été supprimée des fonctions intégrées de Python 3.0, en disant:

"A utiliser functools.reduce()si vous en avez vraiment besoin; cependant, 99% du temps, une boucle for explicite est plus lisible."

Voir aussi Le sort de reduction () dans Python 3000 pour une citation de Guido (et quelques commentaires moins favorables de Lispers qui lisent ce blog).

PS si par hasard vous avez besoin product()de combinatoire, voir math.factorial()(nouveau 2.6).

Beni Cherniavsky-Paskin
la source
2
+1 pour un compte rendu précis (à ma connaissance) des humeurs dominantes dans la communauté Python - bien que je préfère définitivement aller à l'encontre des humeurs dominantes dans ce cas, il est préférable de les connaître pour ce qu'elles sont de toute façon. Aussi, j'aime le peu sur les Lispers non compatibles de LtU (je serais l'un de ceux-là, je suppose). :-)
Michał Marczyk
7

Le but de cette réponse est de fournir un calcul qui est utile dans certaines circonstances - à savoir quand a) il y a un grand nombre de valeurs multipliées de telle sorte que le produit final peut être extrêmement grand ou extrêmement petit, et b) vous ne le faites pas ' t vraiment se soucier de la réponse exacte, mais plutôt avoir un certain nombre de séquences et vouloir pouvoir les commander en fonction du produit de chacun.

Si vous souhaitez multiplier les éléments d'une liste, où l est la liste, vous pouvez faire:

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

Maintenant, cette approche n'est pas aussi lisible que

from operator import mul
reduce(mul, list)

Si vous êtes un mathématicien qui ne connaît pas la réduction (), le contraire peut être vrai, mais je ne conseillerais pas de l'utiliser dans des circonstances normales. Elle est également moins lisible que la fonction product () mentionnée dans la question (du moins pour les non-mathématiciens).

Cependant, si jamais vous vous trouvez dans une situation où vous risquez un sous-débordement ou un dépassement, comme dans

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

et votre but est de comparer les produits de différentes séquences plutôt que de savoir quels sont les produits, alors

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

est la voie à suivre car il est pratiquement impossible d'avoir un problème du monde réel dans lequel vous déborderiez ou déborderiez avec cette approche. (Plus le résultat de ce calcul est grand, plus le produit serait grand si vous pouviez le calculer.)

Garyrob
la source
1
C'est intelligent, mais échoue si vous avez des valeurs négatives ou nulles. : /
Alex Meiburg
7

J'ai testé diverses solutions avec perfplot (un de mes petits projets) et j'ai trouvé que

numpy.prod(lst)

est de loin la solution la plus rapide (si la liste n'est pas très courte).

entrez la description de l'image ici


Code pour reproduire le tracé:

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
la source
2

Je suis surpris que personne n'ait suggéré d'utiliser itertools.accumulateavec operator.mul. Cela évite d'utiliser reduce, qui est différent pour Python 2 et 3 (en raison de l' functoolsimport requis pour Python 3), et est d'ailleurs considéré comme non pythonique par Guido van Rossum lui - même :

from itertools import accumulate
from operator import mul

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

Exemple:

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

Une option consiste à utiliser numbaet le décorateur @jitou@njit . J'ai également apporté une ou deux petites modifications à votre code (au moins en Python 3, "list" est un mot-clé qui ne doit pas être utilisé pour un nom 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

À des fins de synchronisation, vous devez exécuter une fois pour compiler la fonction en utilisant d'abord numba. En général, la fonction sera compilée la première fois qu'elle est appelée, puis appelée à partir de la mémoire par la suite (plus rapidement).

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

Maintenant, lorsque vous exécutez votre code, il s'exécutera avec la version compilée de la fonction. Je les ai chronométrés à l'aide d'un notebook Jupyter et de la %timeitfonction magique:

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)

Notez que sur ma machine, exécutant Python 3.5, la forboucle native Python était en fait la plus rapide. Il peut y avoir un truc ici quand il s'agit de mesurer les performances décorées par numba avec les cahiers Jupyter et la %timeitfonction magique. Je ne suis pas sûr que les horaires ci-dessus soient corrects, je vous recommande donc de l'essayer sur votre système et de voir si numba vous donne une amélioration des performances.

Engineero
la source
0

Le moyen le plus rapide que j'ai trouvé était d'utiliser 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])'))

et les horaires sont:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895
téléchargement d'applications
la source
Cela est probablement dû à la courte longueur de la liste, plus d'expérimentation est probablement nécessaire
craymichael
0

Résultat Python 3 pour les tests de l'OP: (meilleur de 3 pour chacun)

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
la source
-4

Cela fonctionne aussi bien que sa triche

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
la source
J'ai corrigé l'indentation, mais je pense que vous devriez remplacer le dernier printpar un retour. De plus, il n'est pas nécessaire de stocker les valeurs intermédiaires dans une liste, il vous suffit de stocker pentre les itérations.
BoppreH