Trả lại sản phẩm của một danh sách


157

Có cách nào ngắn gọn hơn, hiệu quả hơn hay đơn giản là pythonic để làm như sau không?

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

BIÊN TẬP:

Tôi thực sự thấy rằng điều này nhanh hơn một chút so với sử dụng toán tử.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())

đưa cho tôi

('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
Có một sự khác biệt về chức năng giữa các tùy chọn được đưa ra ở đây trong danh sách trống, các reducecâu trả lời đưa ra a TypeError, trong khi forcâu trả lời vòng lặp trả về 1. Đây là một lỗi trong forcâu trả lời vòng lặp (sản phẩm của danh sách trống không quá 1 so với 17 hoặc 'armadillo').
Scott Griffiths

5
Vui lòng cố gắng tránh sử dụng tên của các phần dựng sẵn (chẳng hạn như danh sách) cho tên của các biến của bạn.
Mark Byers

2
Câu trả lời cũ, nhưng tôi muốn chỉnh sửa để nó không sử dụng listlàm tên biến ...
beroe

13
Sản phẩm của một danh sách trống là 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley

1
@ScottGriffiths Tôi nên đã xác định rằng tôi có nghĩa là một danh sách các số. Và tôi sẽ nói rằng tổng của một danh sách trống là thành phần nhận dạng của +loại danh sách đó (tương tự cho sản phẩm / *). Bây giờ tôi nhận ra rằng Python được gõ động khiến mọi thứ trở nên khó khăn hơn, nhưng đây là một vấn đề được giải quyết trong các ngôn ngữ lành mạnh với các hệ thống kiểu tĩnh như Haskell. Nhưng dù sao Pythonchỉ cho phép sumlàm việc trên các con số, vì sum(['a', 'b'])thậm chí không hoạt động, vì vậy tôi một lần nữa nói rằng điều đó 0có ý nghĩa cho sum1cho sản phẩm.
dấu chấm phẩy

Câu trả lời:


169

Không sử dụng lambda:

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

nó là tốt hơn và nhanh hơn Với trăn 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)

Trong cấu hình sau:

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

Kết quả với trăn 2.7.5

       | 1 | 2 | 3 | 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 A 20.8 18s 13.3     
 B 106 Lời khen 95.3 Lời khen 5.92 lượt 26.1
 C 4.34 ms 3.51 ms 16.7 Lời nói 38.9.
 D 46,6 ms 38,5 ms 180 lượt

Kết quả: np.prodlà kết quả nhanh nhất, nếu bạn sử dụng np.arraylàm cấu trúc dữ liệu (18x cho mảng nhỏ, 250x cho mảng lớn)

với trăn 3.3.2:

       | 1 | 2 | 3 | 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 A 23,6 12s 12,3     
 B 133 Lõi 107 107s 7,42 lượt 27,5
 C 4,79 ms 3,74 ms 18,6 Lời nói 40,9.
 D 48,4 ms 36,8 ms 187 Tiếng vang 214 lượt

Là trăn 3 chậm hơn?


1
Rất thú vị, cảm ơn. Bất cứ ý tưởng tại sao python 3 có thể chậm hơn?
Simon Watkins

3
Lý do có thể: (1) Python 3 intlà Python 2 long. Python 2 sẽ sử dụng "int" cho đến khi nó tràn 32 bit; Python 3 sẽ sử dụng "dài" từ đầu. (2) Python 3.0 là một "bằng chứng về khái niệm". Nâng cấp lên 3.1 càng sớm càng tốt!
John Machin

1
Tôi đã làm lại bài kiểm tra tương tự trên một máy khác: python 2.6 ( 'với lambda:', 21,843887090682983) ( 'mà không lambda:', 9,7096879482269287) python 3.1: với lambda: 24,7712180614 mà không lambda: 10,7758350372
Ruggero Turra

1
cả hai đều thất bại với danh sách trống.
lỗi

9
Lưu ý rằng bạn phải nhập reducetoán tử từ functoolsmô-đun trong Python 3. IE from functools import reduce.
Chris Mueller

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

3
+1 nhưng hãy xem câu trả lời của @ wiso về operator.mulcách tốt hơn để làm điều đó.
Chris Lutz

Tại sao toán tử.mul thích hơn x * y?
Adam Hughes

2
Toán tử.mul là một hàm và do đó sẽ thay thế không chỉ cho x * y mà cho toàn bộ biểu thức lambda (tức là đối số đầu tiên reduce)
Johannes Charra

6
Bạn phải thực hiện nhập from functools import reduceđể làm cho nó hoạt động trong Python 3.
tuổi thọ

45

nếu bạn chỉ có số trong danh sách của mình:

from numpy import prod
prod(list)

EDIT : như được chỉ ra bởi @ off99555, điều này không hoạt động đối với các kết quả số nguyên lớn trong trường hợp nó trả về kết quả của loại numpy.int64trong khi giải pháp của Ian Clelland dựa trên operator.mulreducehoạt động cho kết quả số nguyên lớn vì nó trả về long.


việc này chậm hơn nếu danh sách ngắn
endolith

1
Tôi đã thử đánh giá from numpy import prod; prod(list(range(5,101)))và nó xuất ra 0, bạn có thể sao chép kết quả này trên Python 3 không?
off99555

1
bởi vì prodtrả về kết quả của loại numpy.int64trong trường hợp này và bạn đã nhận được một tràn (giá trị âm thực sự) đã có range(5,23). Sử dụng giải pháp của @Ian Clelland dựa trên operator.mulreducecho các số nguyên lớn (nó trả về một longtrong trường hợp này dường như có độ chính xác tùy ý).
Andre Holzner

@ off99555 Hai giải pháp: bắt đầu với danh sách kiểu float bằng cách thực hiện np.prod(np.arange(5.0,101.0))hoặc chuyển đổi nó thành float bằng cách thực hiện np.prod(np.array(range(5,101)).astype(np.float64)). Lưu ý rằng NumPy sử dụng np.float64thay vì float. Tôi không biết sự khác biệt.
Gỗ

22

Chà, nếu bạn thực sự muốn làm cho nó một dòng mà không cần nhập bất cứ điều gì bạn có thể làm:

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

Nhưng đừng.


Khá Pythonic về bản chất
Jitin

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

1
đối số cuối cùng (1) có thực sự cần thiết?
Ruggero Turra

10
Đối số cuối cùng là cần thiết nếu danh sách có thể trống, nếu không nó sẽ ném ngoại lệ TypeError. Tất nhiên, đôi khi một ngoại lệ sẽ là những gì bạn muốn.
Dave Kirby

2
Đối với tôi, nó trả về 0 mà không có đối số đó, vì vậy bạn cũng có thể xem xét cần thiết để thực thi quy ước sản phẩm trống.
lỗi

hoặc functools.reduce(..)trong python3
Andre Holzner

17

Bắt đầu Python 3.8, một prodchức năng đã được đưa vào mathmô-đun trong thư viện chuẩn:

math.prod (lặp lại, *, bắt đầu = 1)

trong đó trả về sản phẩm của một startgiá trị (mặc định: 1) nhân với số lần lặp:

import math

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

Lưu ý rằng nếu iterable trống, điều này sẽ tạo ra 1(hoặc startgiá trị nếu được cung cấp).


15

Tôi nhớ một số cuộc thảo luận dài trên comp.lang.python (xin lỗi, quá lười để tạo ra các con trỏ bây giờ) đã kết luận rằng định nghĩa ban đầu của bạn product()là Pythonic nhất .

Lưu ý rằng đề xuất không phải là viết một vòng lặp for mỗi khi bạn muốn thực hiện nó, mà là viết một hàm một lần (cho mỗi loại giảm) và gọi nó là cần thiết! Gọi các hàm giảm là rất Pythonic - nó hoạt động rất tốt với các biểu thức của trình tạo và kể từ khi giới thiệu thành công sum(), Python tiếp tục phát triển ngày càng nhiều các hàm khử tích hợp - any()all()là những bổ sung mới nhất ...

Kết luận này là chính thức - reduce()đã bị xóa khỏi nội dung trong Python 3.0, cho biết:

"Sử dụng functools.reduce()nếu bạn thực sự cần nó, tuy nhiên, 99% thời gian một vòng lặp rõ ràng dễ đọc hơn."

Xem thêm Số phận của less () trong Python 3000 để biết trích dẫn hỗ trợ từ Guido (và một số bình luận ít hỗ trợ hơn của Lispers đã đọc blog đó).

PS nếu tình cờ bạn cần product()tổ hợp, xem math.factorial()(mới 2.6).


2
+1 cho một tài khoản chính xác (theo hiểu biết tốt nhất của tôi) về những tâm trạng phổ biến trong cộng đồng Python - trong khi tôi chắc chắn thích đi ngược lại với những tâm trạng đang thịnh hành trong trường hợp này, tốt nhất là nên biết họ về những gì họ đang có. Ngoài ra, tôi thích một chút về Lispers không hỗ trợ từ LtU (tôi đoán là một trong số đó, tôi đoán vậy). :-)
Michał Marc:

7

Mục đích của câu trả lời này là cung cấp một phép tính hữu ích trong một số trường hợp nhất định - cụ thể là khi a) có một số lượng lớn các giá trị được nhân lên sao cho sản phẩm cuối cùng có thể cực kỳ lớn hoặc cực kỳ nhỏ và b) bạn không ' Tôi thực sự quan tâm đến câu trả lời chính xác, nhưng thay vào đó có một số trình tự và muốn có thể đặt hàng dựa trên mỗi sản phẩm của mỗi người.

Nếu bạn muốn nhân các phần tử của danh sách, trong đó l là danh sách, bạn có thể làm:

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

Bây giờ, cách tiếp cận đó không thể đọc được như

from operator import mul
reduce(mul, list)

Nếu bạn là một nhà toán học không quen với việc giảm () thì điều ngược lại có thể đúng, nhưng tôi không khuyên bạn nên sử dụng nó trong các trường hợp thông thường. Nó cũng ít đọc hơn hàm sản phẩm () được đề cập trong câu hỏi (ít nhất là đối với những người không phải là nhà toán học).

Tuy nhiên, nếu bạn đã từng ở trong một tình huống mà bạn có nguy cơ bị tràn hoặc tràn, chẳng hạn như trong

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

và mục đích của bạn là so sánh các sản phẩm của các chuỗi khác nhau chứ không phải để biết các sản phẩm đó là gì, sau đó

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

là cách để đi bởi vì hầu như không thể có một vấn đề trong thế giới thực mà bạn sẽ tràn vào hoặc tràn vào phương pháp này. (Kết quả của phép tính đó càng lớn, sản phẩm sẽ càng lớn nếu bạn có thể tính toán.)


1
Thật thông minh, nhưng thất bại nếu bạn có bất kỳ giá trị âm hoặc không. : /
Alex Meiburg

7

Tôi đã thử nghiệm nhiều giải pháp khác nhau với perfplot (một dự án nhỏ của tôi) và thấy rằng

numpy.prod(lst)

bởi đến nay các giải pháp nhanh nhất (nếu danh sách không phải là rất ngắn).

nhập mô tả hình ảnh ở đây


Mã để tái tạo cốt truyện:

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

Tôi ngạc nhiên không ai đề nghị sử dụng itertools.accumulatevới operator.mul. Điều này tránh sử dụng reduce, khác với Python 2 và 3 (do functoolsyêu cầu nhập cho Python 3) và hơn nữa, chính Guido van Rossum được coi là không dùng pythonic :

from itertools import accumulate
from operator import mul

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

Thí dụ:

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

1

Một lựa chọn là sử dụng numba@jithoặc @njittrang trí . Tôi cũng đã thực hiện một hoặc hai điều chỉnh nhỏ cho mã của bạn (ít nhất là trong Python 3, "list" là một từ khóa không nên được sử dụng cho một tên biến):

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

Đối với mục đích thời gian, bạn cần chạy một lần để biên dịch hàm trước bằng cách sử dụng numba. Nói chung, hàm sẽ được biên dịch lần đầu tiên khi nó được gọi và sau đó được gọi từ bộ nhớ sau đó (nhanh hơn).

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

Bây giờ khi bạn thực thi mã của mình, nó sẽ chạy với phiên bản đã biên dịch của hàm. Tôi đã hẹn họ bằng cách sử dụng sổ ghi chép Jupyter và %timeitchức năng ma thuật:

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)

Lưu ý rằng trên máy của tôi, chạy Python 3.5, forvòng lặp Python gốc thực sự là nhanh nhất. Có thể có một mẹo ở đây khi đo hiệu suất trang trí numba với máy tính xách tay Jupyter và %timeitchức năng ma thuật. Tôi không chắc chắn rằng thời gian trên là chính xác, vì vậy tôi khuyên bạn nên thử nó trên hệ thống của bạn và xem liệu numba có giúp bạn tăng hiệu suất hay không.


0

Cách nhanh nhất tôi tìm thấy là, sử dụng 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])'))

và thời gian là:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895

Điều này có thể là do độ dài của danh sách ngắn, một số thử nghiệm nữa có thể cần thiết
craymichael

0

Kết quả Python 3 cho các thử nghiệm của OP: (tốt nhất là 3 cho mỗi thử nghiệm)

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

Điều này cũng hoạt động mặc dù gian lận của nó

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]    

Tôi đã sửa lỗi thụt đầu dòng, nhưng tôi nghĩ bạn nên thay thế lần cuối printbằng trả lại. Ngoài ra, không cần lưu trữ các giá trị trung gian trong danh sách, bạn chỉ cần lưu trữ các plần lặp lại.
BoppreH
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.