ส่งคืนผลิตภัณฑ์ของรายการ


157

มีวิธีรัดกุมมีประสิทธิภาพหรือเรียบง่ายกว่าในการทำสิ่งต่อไปนี้หรือไม่?

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

แก้ไข:

ฉันพบว่ามันเร็วกว่าการใช้โอเปอเรเตอร์เล็กน้อย

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

ให้ฉัน

('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
มีความแตกต่างในการใช้งานระหว่างตัวเลือกที่ให้ไว้ที่นี่สำหรับรายการที่ว่างreduceคำตอบที่เพิ่มTypeErrorในขณะที่forคำตอบวนกลับ 1 นี่คือข้อผิดพลาดในforคำตอบวน (ผลิตภัณฑ์ของรายการที่ว่างเปล่าไม่เกิน 1 มากกว่า 17 หรือ 'ตัวนิ่ม')
Scott Griffiths

5
โปรดหลีกเลี่ยงการใช้ชื่อบิวด์อิน (เช่นรายการ) สำหรับชื่อตัวแปรของคุณ
Mark Byers

2
คำตอบเก่า แต่ฉันอยากจะแก้ไขดังนั้นจึงไม่ได้ใช้listเป็นชื่อตัวแปร ...
Beroe

13
ผลิตภัณฑ์ในรายการว่างเปล่าคือ 1. en.wikipedia.org/wiki/Empty_product
Paul Crowley

1
@ScottGriffiths ฉันควรระบุว่าฉันหมายถึงรายการหมายเลข และฉันจะบอกว่าผลรวมของรายการที่ว่างเปล่าคือองค์ประกอบประจำตัว+ของรายการประเภทนั้น (เช่นเดียวกันกับผลิตภัณฑ์ / *) ตอนนี้ฉันรู้แล้วว่า Python ถูกพิมพ์แบบไดนามิกซึ่งทำให้สิ่งต่าง ๆ ยากขึ้น แต่นี่เป็นปัญหาที่แก้ไขได้ในภาษาที่มีสติด้วยระบบสแตติกประเภทเช่น Haskell แต่Pythonอนุญาตให้sumใช้กับตัวเลขได้เท่านั้นเนื่องจากsum(['a', 'b'])ไม่ทำงานดังนั้นฉันจึงพูดอีกครั้งว่า0เหมาะสมsumและ1เป็นผลิตภัณฑ์
เซมิโคลอน

คำตอบ:


169

โดยไม่ต้องใช้แลมบ์ดา:

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

มันดีกว่าและเร็วกว่า ด้วย 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)

ในการกำหนดค่าต่อไปนี้:

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

       | 1 | 2 | 3 | 4 |
------- + + ----------- ----------- ----------- + + ------ ----- +
 A 20.8 13s 13.3 22s 22.6 39s 39.6 .s     
 B 106 95s 95.3 5.s 5.92 26s 26.1 µs
 C 4.34 ms 3.51 ms 16.7 38s 38.9 µs
 D 46.6 ms 38.5 ms 180 216s 216 .s

ผลลัพธ์: np.prodเป็นสิ่งที่เร็วที่สุดถ้าคุณใช้np.arrayเป็นโครงสร้างข้อมูล (18x สำหรับอาร์เรย์ขนาดเล็ก, 250x สำหรับอาร์เรย์ขนาดใหญ่)

ด้วย python 3.3.2:

       | 1 | 2 | 3 | 4 |
------- + + ----------- ----------- ----------- + + ------ ----- +
 A 23.6 µs 12.3 68s 68.6 84s 84.9 .s     
 B 133 107s 107 7.s 7.42 27s 27.5 .s
 C 4.79 ms 3.74 ms 18.6 40s 40.9 .s
 D 48.4 ms 36.8 ms 187 21s 214 µs

หลาม 3 ช้าลงไหม?


1
น่าสนใจมากขอบคุณ ความคิดใดที่ว่าทำไม python 3 อาจช้าลง?
Simon Watkins

3
เหตุผลที่เป็นไปได้: (1) งูหลาม 3 intเป็นงูหลาม long2 Python 2 จะใช้ "int" จนกระทั่งล้น 32 บิต; Python 3 จะใช้ "long" ตั้งแต่เริ่มต้น (2) Python 3.0 เป็น "ข้อพิสูจน์แนวคิด" อัปเกรดเป็น 3.1 โดยเร็ว!
John Machin

1
ฉันได้ทำซ้ำการทดสอบเดียวกันบนเครื่องอื่น ๆ : หลาม 2.6 ( 'กับแลมบ์ดา:' 21.843887090682983) ( 'โดยไม่ต้องแลมบ์ดา:' 9.7096879482269287) หลาม 3.1: มีแลมบ์ดา: 24.7712180614 โดยไม่ต้องแลมบ์ดา: 10.7758350372
Ruggero Turra

1
ทั้งสองล้มเหลวด้วยรายการที่ว่างเปล่า
ข้อผิดพลาด

9
ทราบว่าคุณต้องนำเข้าreduceประกอบจากfunctoolsโมดูลในหลาม 3. from functools import reduceIE
Chris Mueller

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

3
+1 แต่ดูคำตอบของ @ wiso เกี่ยวกับoperator.mulวิธีที่ดีกว่า
Chris Lutz

ทำไมโอเปอเรเตอร์ควรเลือก x * y หรือไม่
Adam Hughes

2
operator.mul เป็นฟังก์ชั่นและจะเป็นการแทนที่ไม่เพียง แต่สำหรับ x * y แต่สำหรับการแสดงออกแลมบ์ดาทั้งหมด (เช่นการโต้แย้งครั้งแรกreduce)
Johannes Charra

6
คุณต้องทำการนำเข้า from functools import reduceเพื่อให้สามารถใช้งานได้ใน Python 3
lifebalance

45

หากคุณมีตัวเลขอยู่ในรายการ:

from numpy import prod
prod(list)

แก้ไข : เป็นแหลมออกโดย @ off99555 นี้ไม่ได้ทำงานเพื่อให้ได้ผลลัพธ์จำนวนเต็มขนาดใหญ่ซึ่งในกรณีนี้ก็จะส่งกลับผลของชนิดnumpy.int64ในขณะที่การแก้ปัญหาเอียน Clelland บนพื้นฐานoperator.mulและการทำงานเพื่อให้ได้ผลลัพธ์จำนวนเต็มขนาดใหญ่เพราะผลตอบแทนreducelong


สิ่งนี้จะช้ากว่านี้ถ้ารายการนั้นสั้น
endolith

1
ฉันลองประเมินfrom numpy import prod; prod(list(range(5,101)))และมันแสดง0ผลคุณสามารถสร้างผลลัพธ์นี้ใน Python 3 ได้หรือไม่
off99555

1
เพราะprodผลตอบแทนที่เป็นผลมาจากประเภทnumpy.int64ในกรณีนี้และคุณจะได้รับล้น (เป็นค่าลบจริง) range(5,23)แล้วสำหรับ ใช้วิธีแก้ปัญหาของ @Ian Clelland อ้างอิงจากoperator.mulและreduceสำหรับจำนวนเต็มขนาดใหญ่ (ส่งคืน a longในกรณีนี้ซึ่งดูเหมือนว่าจะมีความแม่นยำโดยพลการ)
Andre Holzner

@ off99555 สองโซลูชั่น: ทั้งเริ่มต้นด้วยประเภทลอยรายการโดยการทำหรือแปลงไปลอยด้วยการทำnp.prod(np.arange(5.0,101.0)) np.prod(np.array(range(5,101)).astype(np.float64))โปรดทราบว่า NumPy ใช้แทนnp.float64 floatฉันไม่รู้ความแตกต่าง
ไม้

22

ถ้าคุณต้องการทำให้มันเป็นหนึ่งบรรทัดโดยไม่ต้องนำเข้าสิ่งที่คุณสามารถทำได้:

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

แต่ทำไม่ได้


Pythonic ค่อนข้างสำคัญ
Jitin

สำหรับโซลโดยไม่ต้องนำเข้าอะไร!
John D

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

1
อาร์กิวเมนต์สุดท้าย (1) จำเป็นจริงๆหรือ
Ruggero Turra

10
อาร์กิวเมนต์สุดท้ายเป็นสิ่งจำเป็นหากรายการอาจว่างเปล่ามิฉะนั้นจะส่งข้อยกเว้น TypeError แน่นอนว่าบางครั้งข้อยกเว้นจะเป็นสิ่งที่คุณต้องการ
Dave Kirby

2
สำหรับฉันแล้วมันจะคืนค่า 0 โดยไม่มีอาร์กิวเมนต์นั้นดังนั้นคุณสามารถพิจารณาได้ว่าจำเป็นต้องบังคับใช้ระเบียบแบบแผนผลิตภัณฑ์ที่ว่างเปล่า
ข้อผิดพลาด

หรือfunctools.reduce(..)ในpython3
Andre Holzner

18

เริ่มต้นPython 3.8เป็นprodฟังก์ชั่นได้รับการรวมกับmathโมดูลในห้องสมุดมาตรฐาน:

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

ซึ่งส่งคืนผลิตภัณฑ์ที่มีstartค่า (ค่าเริ่มต้น: 1) คูณด้วยจำนวนซ้ำได้:

import math

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

โปรดทราบว่าหาก iterable ว่างเปล่าสิ่งนี้จะสร้างขึ้น1(หรือstartค่าถ้ามีให้)


15

ผมจำได้ว่าการอภิปรายยาวบางอย่างเกี่ยวกับ comp.lang.python (ขออภัยขี้เกียจเกินไปในการผลิตตัวชี้ตอนนี้) ซึ่งได้ข้อสรุปว่าต้นฉบับของคุณproduct()นิยามเป็น Pythonic

โปรดทราบว่าข้อเสนอนี้ไม่ได้เขียน for for loop ทุกครั้งที่คุณต้องการ แต่เขียนฟังก์ชั่นหนึ่งครั้ง (ตามประเภทของการลด) และเรียกมันตามต้องการ! ฟังก์ชั่นการลดเสียงเรียกนั้นเป็นแบบ Pythonic - มันใช้งานได้ดีกับนิพจน์เครื่องกำเนิดไฟฟ้าและตั้งแต่การแนะนำที่สำเร็จsum()Python ยังคงเพิ่มฟังก์ชั่นการลดในตัวมากขึ้นเรื่อย ๆ - any()และall()เป็นส่วนเสริมล่าสุด ...

ข้อสรุปนี้เป็นทางการครับ - reduce()ถูกลบออกจาก buildins ใน Python 3.0 โดยพูดว่า:

"ใช้functools.reduce()ถ้าคุณต้องการจริงๆอย่างไรก็ตามร้อยละ 99 ของเวลาที่ชัดเจนสำหรับลูปสามารถอ่านได้มากกว่า"

ดูเพิ่มเติมชะตากรรมของการลด () ใน Python 3000สำหรับคำพูดสนับสนุนจาก Guido (และความเห็นที่สนับสนุนน้อยโดย Lispers ที่อ่านบล็อกนั้น)

ป.ล. ถ้าบังเอิญคุณต้องการproduct()combinatorics ดูmath.factorial()(ใหม่ 2.6)


2
+1 สำหรับบัญชีที่ถูกต้อง (ตามความรู้ของฉัน) ที่ถูกต้องเกี่ยวกับอารมณ์ความรู้สึกที่แพร่หลายในชุมชน Python - ในขณะที่ฉันชอบที่จะต่อต้านอารมณ์ความรู้สึกที่แพร่หลายในกรณีนี้เป็นสิ่งที่ดีที่สุดที่จะรู้ว่าพวกเขาเป็นอย่างไร นอกจากนี้ฉันชอบนิดหน่อยเกี่ยวกับ Lispers ที่ไม่สนับสนุนจาก LtU (ฉันเป็นหนึ่งในนั้นฉันเดา) :-)
Michał Marczyk

7

ความตั้งใจของคำตอบนี้คือการให้การคำนวณที่มีประโยชน์ในบางสถานการณ์ - คือเมื่อ a) มีค่าจำนวนมากที่ถูกคูณเช่นผลิตภัณฑ์สุดท้ายอาจมีขนาดใหญ่มากหรือเล็กมากและ b) คุณไม่ได้ ไม่สนใจคำตอบที่แน่นอน แต่มีลำดับจำนวนมากและต้องการที่จะสามารถสั่งซื้อได้ตามผลิตภัณฑ์ของแต่ละคน

หากคุณต้องการคูณองค์ประกอบของรายการโดยที่ l คือรายการคุณสามารถทำสิ่งต่อไปนี้

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

ตอนนี้วิธีการนั้นไม่สามารถอ่านได้เหมือน

from operator import mul
reduce(mul, list)

หากคุณเป็นนักคณิตศาสตร์ที่ไม่คุ้นเคยกับการลด () ตรงกันข้ามอาจเป็นจริง แต่ฉันจะไม่แนะนำให้ใช้ในสถานการณ์ปกติ นอกจากนี้ยังสามารถอ่านได้น้อยกว่าฟังก์ชั่นผลิตภัณฑ์ () ที่กล่าวถึงในคำถาม (อย่างน้อยสำหรับผู้ที่ไม่ใช่นักคณิตศาสตร์)

อย่างไรก็ตามหากคุณเคยอยู่ในสถานการณ์ที่เสี่ยงต่อการได้รับเงินทุนหมุนเวียนหรือล้นเช่นใน

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

และจุดประสงค์ของคุณคือการเปรียบเทียบผลิตภัณฑ์ที่มีลำดับแตกต่างกันแทนที่จะทราบว่าผลิตภัณฑ์นั้นเป็นอย่างไร

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

เป็นวิธีที่จะไปเพราะแทบจะเป็นไปไม่ได้ที่จะมีปัญหาในโลกแห่งความเป็นจริงที่คุณจะล้นหรือไหลบ่าเข้ามาด้วยวิธีนี้ (ยิ่งผลลัพธ์ของการคำนวณนั้นมากขึ้นเท่าใดผลิตภัณฑ์ก็จะยิ่งมากขึ้นหากคุณสามารถคำนวณได้)


1
มันฉลาด แต่ล้มเหลวหากคุณมีค่าลบหรือศูนย์ใด ๆ : /
Alex Meiburg

7

ฉันได้ทดสอบวิธีแก้ปัญหาต่าง ๆ ด้วยperfplot (โครงการเล็ก ๆ ของฉัน) และพบว่า

numpy.prod(lst)

คือไกลโดยวิธีที่เร็วที่สุด (ถ้ารายการไม่ได้เป็นอย่างสั้น)

ป้อนคำอธิบายรูปภาพที่นี่


รหัสในการทำซ้ำพล็อต:

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

ฉันกำลังแปลกใจไม่มีใครได้แนะนำให้ใช้กับitertools.accumulate operator.mulวิธีนี้หลีกเลี่ยงการใช้reduceซึ่งแตกต่างจาก Python 2 และ 3 (เนื่องจากfunctoolsการนำเข้าที่จำเป็นสำหรับ Python 3) และนอกจากนี้ยังถือว่าไม่ได้เป็น pythonic โดย Guido van Rossum เอง :

from itertools import accumulate
from operator import mul

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

ตัวอย่าง:

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

1

เลือกหนึ่งคือการใช้งานnumbaและ@jitหรือ@njitมัณฑนากร ฉันได้ปรับแต่งโค้ดของคุณหนึ่งหรือสองเล็กน้อย (อย่างน้อยใน Python 3 "list" เป็นคำหลักที่ไม่ควรใช้สำหรับชื่อตัวแปร):

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

เพื่อจุดประสงค์ในการจับเวลาคุณจะต้องรันหนึ่งครั้งเพื่อรวบรวมฟังก์ชั่นก่อนใช้งาน numba โดยทั่วไปแล้วฟังก์ชั่นจะถูกรวบรวมในครั้งแรกที่มันถูกเรียกแล้วเรียกจากหน่วยความจำหลังจากนั้น (เร็วกว่า)

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

ตอนนี้เมื่อคุณรันโค้ดมันจะทำงานด้วยฟังก์ชั่นเวอร์ชั่นคอมไพล์ ฉันหมดเวลาโดยใช้สมุดบันทึก Jupyter และ%timeitฟังก์ชั่นเวทย์มนตร์:

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)

โปรดสังเกตว่าบนเครื่องของฉันที่ใช้ Python 3.5 นั้น Python forloop แบบดั้งเดิมนั้นเร็วที่สุด อาจมีกลอุบายที่นี่เมื่อพูดถึงการวัดประสิทธิภาพที่ตกแต่งด้วยชาด้วยโน๊ตบุ๊ค Jupyter และ%timeitฟังก์ชั่นเวทมนต์ ฉันไม่แน่ใจว่าการกำหนดเวลาด้านบนถูกต้องดังนั้นฉันขอแนะนำให้ลองใช้ระบบของคุณและดูว่า numba ช่วยเพิ่มประสิทธิภาพได้หรือไม่


0

วิธีที่เร็วที่สุดที่ฉันพบคือใช้ในขณะที่:

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

และการกำหนดเวลาคือ:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895

นี่อาจเป็นเพราะความยาวของรายการสั้น ๆ มีความจำเป็นต้องทำการทดลองเพิ่มเติม
craymichael

0

Python 3 ส่งผลให้การทดสอบของ OP: (ดีที่สุด 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

-4

วิธีนี้ใช้งานได้แม้ว่าการโกง

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]    

ฉันแก้ไขการเยื้องแล้ว แต่ฉันคิดว่าคุณควรแทนที่อันสุดท้ายprintด้วยการส่งคืน นอกจากนี้ยังไม่จำเป็นต้องจัดเก็บค่ากลางในรายการคุณเพียงแค่ต้องจัดเก็บpซ้ำระหว่าง
BoppreH
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.