Mengembalikan produk daftar

157

Apakah ada cara yang lebih ringkas, efisien atau hanya pythonic untuk melakukan hal berikut?

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

EDIT:

Saya benar-benar menemukan bahwa ini sedikit lebih cepat daripada menggunakan 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())

memberi saya

('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
sumber
3
Ada perbedaan fungsional antara opsi yang diberikan di sini dalam bahwa untuk daftar kosong reducejawaban menaikkan TypeError, sedangkan forjawaban loop kembali 1. Ini adalah bug dalam forjawaban loop (produk daftar kosong tidak lebih dari 1 daripada 17 atau 'armadillo').
Scott Griffiths
5
Cobalah untuk menghindari menggunakan nama built-in (seperti daftar) untuk nama-nama variabel Anda.
Mark Byers
2
Jawaban lama, tapi saya tergoda untuk mengedit sehingga tidak menggunakan listnama variabel ...
beroe
13
Produk daftar kosong adalah 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley
1
@ScottGriffiths Seharusnya saya menentukan bahwa yang saya maksud adalah daftar angka. Dan saya akan mengatakan bahwa jumlah daftar kosong adalah elemen identitas +untuk jenis daftar itu (juga untuk produk / *). Sekarang saya menyadari bahwa Python diketik secara dinamis yang membuat segalanya lebih sulit, tetapi ini adalah masalah yang diselesaikan dalam bahasa waras dengan sistem tipe statis seperti Haskell. Tetapi Pythonhanya memungkinkan sumuntuk bekerja pada angka, karena sum(['a', 'b'])bahkan tidak berfungsi, jadi saya kembali mengatakan itu 0masuk akal untuk sumdan 1untuk produk.
titik koma

Jawaban:

169

Tanpa menggunakan lambda:

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

lebih baik dan lebih cepat. Dengan 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)

Dalam konfigurasi berikut:

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

Hasil dengan 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

Hasil: np.prodadalah yang tercepat, jika Anda gunakan np.arraysebagai struktur data (18x untuk array kecil, 250x untuk array besar)

dengan 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

Apakah python 3 lebih lambat?

Ruggero Turra
sumber
1
Sangat menarik, terima kasih. Adakah yang tahu mengapa python 3 mungkin lebih lambat?
Simon Watkins
3
Alasan yang mungkin: (1) Python 3 intadalah Python 2 long. Python 2 akan menggunakan "int" sampai meluap 32 bit; Python 3 akan menggunakan "long" dari awal. (2) Python 3.0 adalah "bukti konsep". Tingkatkan ke 3.1 ASAP!
John Machin
1
Saya telah mengulang pengujian yang sama pada mesin lain: python 2.6 ('dengan lambda:', 21.843887090682983) ('tanpa lambda:', 9.7096879482269287) python 3.1: dengan lambda: 24.7712180614 tanpa lambda: 10.7758350372
Ruggero Turra
1
keduanya gagal dengan daftar kosong.
bug
9
Perhatikan bahwa Anda harus mengimpor reduceoperator dari functoolsmodul dengan Python 3. IE from functools import reduce.
Chris Mueller
50
reduce(lambda x, y: x * y, list, 1)
Johannes Charra
sumber
3
+1 tetapi lihat jawaban @ wiso tentang operator.mulcara yang lebih baik untuk melakukannya.
Chris Lutz
mengapa operator.mul lebih disukai daripada x * y?
Adam Hughes
2
operator.mul adalah fungsi dan karenanya akan menjadi pengganti tidak hanya untuk x * y tetapi untuk seluruh ekspresi lambda (yaitu argumen pertama untuk reduce)
Johannes Charra
6
Anda harus melakukan impor from functools import reduceuntuk membuatnya berfungsi dalam Python 3.
lifebalance
45

jika Anda hanya memiliki nomor dalam daftar Anda:

from numpy import prod
prod(list)

EDIT : seperti yang ditunjukkan oleh @ off99555 ini tidak berfungsi untuk hasil bilangan bulat besar dalam hal ini mengembalikan hasil tipe numpy.int64sedangkan solusi Ian Clelland berdasarkan operator.muldan reducebekerja untuk hasil bilangan bulat besar karena ia kembali long.

Andre Holzner
sumber
ini lebih lambat jika daftarnya pendek
endolith
1
Saya mencoba mengevaluasi from numpy import prod; prod(list(range(5,101)))dan hasilnya 0, dapatkah Anda mereproduksi hasil ini di Python 3?
off99555
1
karena prodmengembalikan hasil ketik numpy.int64dalam kasus ini dan Anda sudah mendapatkan overflow (nilai negatif sebenarnya) untuk range(5,23). Gunakan solusi @Ian Clelland berdasarkan operator.muldan reduceuntuk bilangan bulat besar (mengembalikan longdalam kasus ini yang tampaknya memiliki presisi sewenang-wenang).
Andre Holzner
@ off99555 Dua solusi: mulai dengan daftar tipe float dengan melakukan np.prod(np.arange(5.0,101.0))atau mengubahnya menjadi float dengan melakukan np.prod(np.array(range(5,101)).astype(np.float64)). Perhatikan bahwa NumPy menggunakan np.float64alih-alih float. Saya tidak tahu bedanya.
Wood
22

Nah, jika Anda benar-benar ingin menjadikannya satu baris tanpa mengimpor apa pun yang dapat Anda lakukan:

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

Tapi jangan.

titik koma
sumber
Intinya cukup Pythonic
Jitin
ty untuk sol tanpa mengimpor apa pun!
John D
18
import operator
reduce(operator.mul, list, 1)
Ian Clelland
sumber
1
apakah argumen terakhir (1) benar-benar diperlukan?
Ruggero Turra
10
Argumen terakhir diperlukan jika daftar mungkin kosong, jika tidak maka akan membuang pengecualian TypeError. Tentu saja, terkadang pengecualian adalah yang Anda inginkan.
Dave Kirby
2
Bagi saya itu mengembalikan 0 tanpa argumen itu, jadi Anda juga dapat menganggap perlu untuk menegakkan konvensi produk kosong.
bug
atau functools.reduce(..)dalam python3
Andre Holzner
18

Mulai Python 3.8, prodfungsi telah dimasukkan ke mathmodul di perpustakaan standar:

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

yang mengembalikan produk dari suatu startnilai (default: 1) kali angka berulang:

import math

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

Perhatikan bahwa jika iterable kosong, ini akan menghasilkan 1(atau startnilai jika disediakan).

Xavier Guihot
sumber
15

Saya ingat beberapa diskusi panjang tentang comp.lang.python (maaf, terlalu malas untuk menghasilkan pointer sekarang) yang menyimpulkan bahwa definisi asli Anda product()adalah yang paling Pythonic .

Perhatikan bahwa proposal tersebut bukan untuk menulis loop untuk setiap kali Anda ingin melakukannya, tetapi untuk menulis fungsi satu kali (per jenis pengurangan) dan menyebutnya sesuai kebutuhan! Memanggil fungsi reduksi sangat Pythonic - ia bekerja dengan manis dengan ekspresi generator, dan sejak pengenalan yang sukses sum(), Python terus tumbuh semakin banyak fungsi pengurangan builtin - any()dan all()merupakan tambahan terbaru ...

Kesimpulan ini agak resmi - reduce()telah dihapus dari builtin Python 3.0, mengatakan:

"Gunakan functools.reduce()jika Anda benar-benar membutuhkannya; namun, 99 persen waktu yang eksplisit untuk loop lebih mudah dibaca."

Lihat juga Nasib mengurangi () dalam Python 3000 untuk kutipan pendukung dari Guido (dan beberapa komentar yang kurang mendukung oleh Lispers yang membaca blog itu).

NB jika kebetulan Anda membutuhkan product()untuk kombinatorik, lihat math.factorial()(baru 2.6).

Beni Cherniavsky-Paskin
sumber
2
Memberi +1 untuk akun yang akurat (sesuai pengetahuan saya) tentang mood yang ada di komunitas Python - sementara saya lebih suka menentang mood yang berlaku dalam kasus ini, yang terbaik adalah mengetahui mereka apa adanya. Juga, saya suka sedikit tentang Lisper yang tidak mendukung dari LtU (saya akan menjadi salah satunya, saya kira). :-)
Michał Marczyk
7

Maksud dari jawaban ini adalah untuk memberikan perhitungan yang berguna dalam keadaan tertentu - yaitu ketika a) ada sejumlah besar nilai yang dikalikan sehingga produk akhir mungkin sangat besar atau sangat kecil, dan b) Anda tidak perlu Saya tidak begitu peduli dengan jawaban yang tepat, tetapi malah memiliki sejumlah urutan, dan ingin dapat memesannya berdasarkan produk masing-masing.

Jika Anda ingin melipatgandakan elemen daftar, di mana l adalah daftar, Anda dapat melakukan:

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

Sekarang, pendekatan itu tidak bisa dibaca

from operator import mul
reduce(mul, list)

Jika Anda seorang ahli matematika yang tidak terbiasa dengan mengurangi () yang sebaliknya mungkin benar, tetapi saya tidak akan menyarankan menggunakannya dalam keadaan normal. Ini juga kurang dapat dibaca daripada fungsi product () yang disebutkan dalam pertanyaan (setidaknya untuk non-ahli matematika).

Namun, jika Anda pernah berada dalam situasi di mana Anda berisiko mengalami underflow atau overflow, seperti di

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

dan tujuan Anda adalah membandingkan produk-produk dari urutan yang berbeda daripada untuk mengetahui apa produk-produknya

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

adalah cara untuk pergi karena hampir tidak mungkin untuk memiliki masalah dunia nyata di mana Anda akan meluap atau melimpah dengan pendekatan ini. (Semakin besar hasil perhitungan itu, semakin besar produknya jika Anda bisa menghitungnya.)

garyrob
sumber
1
Ini pintar, tetapi gagal jika Anda memiliki nilai negatif atau nol. : /
Alex Meiburg
7

Saya telah menguji berbagai solusi dengan perfplot (proyek kecil saya) dan menemukan itu

numpy.prod(lst)

adalah jauh solusi tercepat (jika daftar tidak sangat singkat).

masukkan deskripsi gambar di sini


Kode untuk mereproduksi plot:

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

Saya terkejut tidak seorang pun telah menyarankan menggunakan itertools.accumulatedengan operator.mul. Ini menghindari penggunaan reduce, yang berbeda untuk Python 2 dan 3 (karena functoolsimpor diperlukan untuk Python 3), dan lebih lagi dianggap tidak pythonic oleh Guido van Rossum sendiri :

from itertools import accumulate
from operator import mul

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

Contoh:

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

Salah satu pilihan adalah menggunakan numbadan @jitatau @njitdekorator . Saya juga membuat satu atau dua perubahan kecil pada kode Anda (setidaknya dalam Python 3, "daftar" adalah kata kunci yang tidak boleh digunakan untuk nama variabel):

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

Untuk keperluan pengaturan waktu, Anda perlu menjalankan satu kali untuk mengkompilasi fungsi terlebih dahulu menggunakan numba. Secara umum, fungsi akan dikompilasi pertama kali dipanggil, dan kemudian dipanggil dari memori setelah itu (lebih cepat).

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

Sekarang ketika Anda menjalankan kode Anda, itu akan berjalan dengan versi fungsi yang dikompilasi. Saya mengatur waktu mereka menggunakan notebook Jupyter dan %timeitfungsi sulap:

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)

Perhatikan bahwa pada mesin saya, menjalankan Python 3.5, forloop Python asli sebenarnya yang tercepat. Mungkin ada trik di sini untuk mengukur kinerja yang didekorasi dengan numba dengan notebook Jupyter dan %timeitfungsi sulap. Saya tidak yakin pengaturan waktu di atas benar, jadi saya sarankan untuk mencobanya di sistem Anda dan melihat apakah numba memberi Anda peningkatan kinerja.

Engineero
sumber
0

Cara tercepat yang saya temukan adalah, menggunakan saat:

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

dan waktunya adalah:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895
appsdownload
sumber
Ini kemungkinan karena panjangnya daftar, beberapa eksperimen lebih lanjut mungkin diperlukan
craymichael
0

Hasil Python 3 untuk tes OP: (terbaik 3 untuk masing-masing)

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

Ini juga berfungsi meskipun curang

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]    
pengguna2120413
sumber
Saya telah memperbaiki indentasi, tetapi saya pikir Anda harus mengganti yang terakhir printdengan pengembalian. Juga, tidak perlu menyimpan nilai-nilai perantara dalam daftar, Anda hanya perlu menyimpan piterasi antarwe.
BoppreH