Bir listenin ürününü iade etme


157

Aşağıdakileri yapmanın daha kısa, etkili veya basit bir yolu var mı?

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

DÜZENLE:

Aslında bu işleç kullanmaktan biraz daha hızlı olduğunu buluyorum.

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

bana verir

('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
Burada verilen seçenekler arasında işlevsel bir fark vardır, boş bir liste için reducecevaplar bir a TypeErroryükselir, oysa fordöngü yanıtı 1 geri döner. Bu, fordöngü yanıtındaki bir hatadır (boş bir listenin ürünü 17'den fazla değildir). veya "armadillo").
Scott Griffiths

5
Lütfen değişkenlerinizin adları için yerleşik adların (liste gibi) kullanılmasından kaçının.
Mark Byers

2
Eski cevap, ama ben listdeğişken bir isim olarak kullanmaz bu yüzden düzenlemek için cazip ...
beroe

13
Boş bir listenin ürünü 1'dir. En.wikipedia.org/wiki/Empty_product
Paul Crowley

1
@ScottGriffiths Bir sayı listesi demek istediğimi belirtmeliydim. Ve boş bir listenin toplamının, +o liste türünün (aynı şekilde ürün / için *) kimlik öğesi olduğunu söyleyebilirim . Şimdi Python'un dinamik olarak yazıldığını fark ediyorum, bu da işleri zorlaştırıyor, ancak bu, Haskell gibi statik tip sistemlere sahip aklı dillerinde çözülmüş bir problem. Ama Pythonsadece sumrakamlar üzerinde çalışmaya izin veriyor , çünkü sum(['a', 'b'])çalışmıyor bile, bu yüzden yine bunun ürün 0için sumve 1ürün için mantıklı olduğunu söylüyorum .
noktalı virgül

Yanıtlar:


169

Lambda kullanmadan:

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

daha iyi ve daha hızlı. Python 2.7.5 ile

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)

Aşağıdaki yapılandırmada:

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

Python 2.7.5 sonuçları

       | 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

Sonuç: veri yapısı olarak np.prodkullanırsanız en hızlı olanıdır np.array(küçük dizi için 18x, büyük dizi için 250x)

python 3.3.2 ile:

       | 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 daha yavaş mı?


1
Çok ilginç, teşekkürler. Python 3'ün neden daha yavaş olabileceği hakkında bir fikrin var mı?
Simon Watkins

3
Olası nedenler: (1) Python 3 int, Python 2'dir long. Python 2 32 biti aşana kadar "int" kullanacaktır; Python 3 başlangıçtan itibaren "uzun" kullanır. (2) Python 3.0 bir "kavram kanıtı" idi. 3.1 ASAP sürümüne geçin!
John Machin

1
Piton 2.6: Aynı bir diğer makinede testi redone ettik ( 'lambda'da:' 21,843887090682983) ( 'lambda olmadan:' 9,7096879482269287) piton 3.1: lambda'da: 24,7712180614 lambda olmadan: 10,7758350372
Ruggero Turra

1
her ikisi de boş listelerle başarısız olur.
hata

9
reduceOperatörü functoolsPython 3'teki modülden içe aktarmanız gerektiğini unutmayın from functools import reduce. IE .
Chris Mueller

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

3
+1 ancak operator.mulbunu yapmanın daha iyi bir yolu için @ wiso'nun cevabına bakın .
Chris Lutz

operator.mul neden x * y için tercih edilir?
Adam Hughes

2
operator.mul bir işlevdir ve bu nedenle yalnızca x * y için değil, tüm lambda ifadesinin (yani ilk argüman reduce) yerine
geçecektir

6
from functools import reducePython 3'te çalışması için bir ithalat yapmalısınız.
lifebalance

45

listenizde sadece numaralar varsa:

from numpy import prod
prod(list)

DÜZENLEME : @ off99555 tarafından işaret edildiği gibi, bu büyük tamsayı sonuçları için çalışmaz, bu durumda numpy.int64Ian Clelland'ın çözümü dayalı bir sonuç sonucu döndürür operator.mulve reducegeri döndüğünden büyük tamsayı sonuçları için çalışır long.


liste kısaysa bu daha yavaş olur
endolith

1
Değerlendirmeyi denedim from numpy import prod; prod(list(range(5,101)))ve çıktı 0, bu sonucu Python 3'te çoğaltabilir misiniz?
off99555

1
çünkü bu durumda bir prodtür sonuç döndürür numpy.int64ve zaten için bir taşma (aslında negatif bir değer) alırsınız range(5,23). @Ian Clelland'ın çözümünü büyük tamsayılara operator.mulve reducebu tamsayılara göre kullanın ( longbu durumda keyfi bir hassasiyete sahip olan bir a döndürür ).
Andre Holzner

@ off99555 İki çözüm: ya bir şamandıra türü listesiyle başlayarak ya da yaparak np.prod(np.arange(5.0,101.0))şamandıraya dönüştürün np.prod(np.array(range(5,101)).astype(np.float64)). NumPy'nin np.float64yerine kullandığını unutmayın float. Farkı bilmiyorum.
Wood

22

Peki, yapabileceğiniz hiçbir şeyi içe aktarmadan gerçekten bir satır yapmak istiyorsanız:

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

Ama yapma.


Özünde Oldukça Pythonic
Jitin

hiçbir şey ithal etmeden sol için ty!
John D

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

1
(1) son argümanı gerçekten gerekli mi?
Ruggero Turra

10
Liste boşsa son argüman gereklidir, aksi takdirde bir TypeError istisnası atar. Tabii ki, bazen istediğiniz bir istisna olacaktır.
Dave Kirby

2
Benim için bu argüman olmadan 0 döndürür, bu nedenle boş ürün sözleşmesini zorlamanın da gerekli olduğunu düşünebilirsiniz.
böcek

ya functools.reduce(..)içinde python3
Andre Holzner

18

Başlangıçta Python 3.8, standart kütüphanedeki modüle bir prodfonksiyon dahil edilmiştir math:

math.prod (yinelenebilir, *, başlangıç ​​= 1)

bir startdeğerin çarpımını (varsayılan: 1) yinelenebilir sayılardan döndürür:

import math

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

Yinelenebilir boşsa, bunun üretileceğini 1(veya startsağlandıysa değeri) unutmayın.


15

Orijinal product()tanımınızın en Pythonic olduğu sonucuna varılan comp.lang.python (üzgünüm, şimdi işaretçiler üretmek için çok tembel) hakkında uzun tartışmalar hatırlıyorum .

Teklifin, her yapmak istediğinizde bir for döngüsü yazmak değil, bir kez (her azaltma türü için) bir işlev yazmak ve gerektiği gibi çağırmak olduğunu unutmayın! Azaltma fonksiyonlarını çağırmak çok Pythonic'tir - jeneratör ifadeleriyle tatlı bir şekilde çalışır ve başarılı bir şekilde sunulmasından bu yana sum()Python, gittikçe daha fazla yerleşik azaltma fonksiyonu büyümeye devam eder - any()ve all()en son eklemelerdir ...

Bu sonuç, biraz resmi olan - reduce()edildi kaldırıldı söyleyerek Python 3.0 yerleşikleri gelen:

" functools.reduce()Gerçekten ihtiyacınız varsa kullanın ; ancak, açık bir döngü için zamanın yüzde 99'u daha okunabilir."

Ayrıca Guido'dan destekleyici bir alıntı için Python 3000'deki reduce () ifadesinin kaderi (ve bu blogu okuyan Lispers tarafından daha az destekleyici yorumlar).

PS eğer şans eseri product()kombinatoriklere ihtiyacınız varsa bkz. math.factorial()(Yeni 2.6).


2
Python topluluğundaki hakim ruh hallerinin doğru (bilgim dahilinde) hesabı için +1 - kesinlikle bu durumda söz konusu hakim ruh hallerine karşı gitmeyi tercih ederken, zaten ne olduklarını bilmek en iyisidir. Ayrıca, LtU'dan destekleyici Lispers ile ilgili biraz hoşuma gidiyor (sanırım onlardan biri olurum). :-)
Michał Marczyk

7

Bu cevabın amacı belirli durumlarda yararlı olan bir hesaplama sağlamaktır - yani a) nihai ürünün aşırı büyük veya aşırı küçük olabileceği şekilde çarpılan çok sayıda değer olduğunda ve b) Kesin cevabı gerçekten önemsemiyorum, ancak bunun yerine bir dizi diziye sahip olun ve bunları her birinin ürününe göre sipariş edebilmek istiyorum.

Bir listenin öğelerini çarpmak istiyorsanız, burada l listedir, şunları yapabilirsiniz:

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

Şimdi, bu yaklaşım kadar okunabilir değil

from operator import mul
reduce(mul, list)

Reduce () yöntemine aşina olmayan bir matematikçiyseniz, bunun tersi doğru olabilir, ancak normal şartlar altında kullanmanızı tavsiye etmem. Ayrıca, soruda belirtilen product () işlevinden daha az okunabilir (en azından matematikçi olmayanlar için).

Ancak, şunun gibi düşük veya aşırı taşma riskiniz olan bir durumdaysanız,

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

Amacınız, ürünlerin ne olduğunu bilmek yerine farklı sekansların ürünlerini karşılaştırmaktır.

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

çünkü bu yaklaşımla taşacak ya da taşacak gerçek bir dünya sorununa sahip olmak neredeyse imkansız. (Bu hesaplamanın daha büyük sonuç, eğer daha büyük bir ürün olacağını, olduğunu olabilir bunu hesaplamak.)


1
Akıllıdır, ancak negatif veya sıfır değerleriniz varsa başarısız olur. : /
Alex Meiburg

7

Perfplot (benim küçük bir projem) ile çeşitli çözümler denedim ve

numpy.prod(lst)

açık arayla en hızlı çözümdür (liste çok kısa değilse).

resim açıklamasını buraya girin


Grafiği yeniden oluşturmak için kod:

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

Ben kullanarak önerdi kimse şaşırdım itertools.accumulateile operator.mul. Bu, reducePython 2 ve 3 için farklı olan ( functoolsPython 3 için gereken içe aktarma nedeniyle) kullanımdan kaçınır ve üstelik Guido van Rossum'un kendisi tarafından pythonic olarak kabul edilir :

from itertools import accumulate
from operator import mul

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

Misal:

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

1

Bir seçenek numbave @jitveya @njitdekoratör kullanmaktır . Ayrıca kodunuzda bir veya iki küçük değişiklik yaptım (en azından Python 3'te, "list" değişken bir isim için kullanılmaması gereken bir anahtar kelime):

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

Zamanlama amacıyla, önce numba kullanarak işlevi derlemek için bir kez çalıştırmanız gerekir. Genel olarak, işlev ilk çağrıldığında derlenecek ve bundan sonra bellekten çağırılacaktır (daha hızlı).

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

Şimdi kodunuzu çalıştırdığınızda, fonksiyonun derlenmiş versiyonu ile çalışacaktır. Bir Jupyter dizüstü bilgisayar ve %timeitsihirli işlevi kullanarak onları zamanladım :

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)

Makinemde Python 3.5 çalıştıran yerel Python fordöngüsünün aslında en hızlı olduğunu unutmayın. Jupyter dizüstü bilgisayarları ve %timeitsihir işlevi ile numba ile dekore edilmiş performansın ölçülmesi söz konusu olduğunda burada bir hile olabilir . Yukarıdaki zamanlamaların doğru olduğundan emin değilim, bu yüzden sisteminizde denemenizi ve numba'nın size bir performans artışı sağlayıp sağlamadığını görmenizi tavsiye ederim.


0

Bulduğum en hızlı yol ise:

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

ve zamanlamalar:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895

Bu muhtemelen listenin kısa uzunluğundan kaynaklanıyor, biraz daha deneme yapılması gerekiyor
craymichael

0

OP testleri için Python 3 sonucu: (her biri için 3'ün en iyisi)

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

Bu aynı zamanda hile olsa da çalışır

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]    

Girintiyi düzelttim, ama sanırım sonuncuyu printbir dönüşle değiştirmelisin. Ayrıca, ara değerleri bir listede depolamaya gerek yoktur, sadece ara pyinelemeleri depolamanız gerekir .
BoppreH
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.