목록의 제품 반품


157

다음을 수행하는 더 간결하고 효율적이거나 단순한 파이썬 방법이 있습니까?

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

편집하다:

실제로 이것은 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())

나에게 준다

('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가 17보다 루프 응답 (빈리스트의 제품은 더 이상 일하지 않습니다 또는 'armadillo').
Scott Griffiths

5
변수 이름에 내장 이름 (예 : 목록)을 사용하지 마십시오.
Mark Byers

2
오래된 대답이지만 list변수 이름으로 사용하지 않도록 편집하고
싶습니다

13
빈 목록의 제품은 1입니다. en.wikipedia.org/wiki/Empty_product
Paul Crowley

1
@ScottGriffiths 나는 숫자 목록을 의미하도록 지정해야합니다. 그리고 빈 목록의 합계는 +해당 유형의 목록 에 대한 동일성 요소라고 생각합니다 (예 : product / *). 이제 파이썬이 동적으로 타이핑되어 일이 더 어려워지는 것을 알고 있지만 Haskell과 같은 정적 타입 시스템을 갖춘 정상적인 언어에서는 해결 된 문제입니다. 그러나 Python단지 수 sum있기 때문에, 어쨌든 숫자에 대한 작업에 sum(['a', 'b'])내가 다시 말할 수 있도록, 심지어 일을하지 않습니다 0에 대한 의미가 있습니다 sum1제품에 대한.
세미콜론

답변:


169

람다를 사용하지 않고 :

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

더 좋고 빠릅니다. 파이썬 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 |
------- + ----------- + ----------- ++ ---------- + ------ ----- +
 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.34ms 3.51ms 16.7µs 38.9µs
 D 46.6ms 38.5ms 180µs 216µs

결과 : 데이터 구조로 np.prod사용 np.array하는 경우 가장 빠릅니다 (작은 배열의 경우 18x, 큰 배열의 경우 250x).

파이썬 3.3.2로 :

       | 1 | 2 | 3 | 4 |
------- + ----------- + ----------- ++ ---------- + ------ ----- +
 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.79ms 3.74ms 18.6µs 40.9µs
 D 48.4ms 36.8ms 187µs 214µs

파이썬 3이 느려 집니까?


1
매우 흥미 롭습니다. 감사합니다. 파이썬 3이 왜 느릴 지 아십니까?
Simon Watkins

3
가능한 이유 : (1) Python 3 int은 Python 2 long입니다. 파이썬 2는 32 비트가 넘을 때까지 "int"를 사용할 것입니다. Python 3은 처음부터 "long"을 사용합니다. (2) 파이썬 3.0은 "개념 증명"이었습니다. 최대한 빨리 3.1로 업그레이드하십시오!
John Machin

1
파이썬 2.6 ( '람다 :', 21.843887090682983) ( '람다없이 :', 9.7096879482269287) 파이썬 3.1 : 람다 : 24.7712180614 람다없이 : 10.7758350372
Ruggero Turra

1
둘 다 빈 목록으로 실패합니다.
bug

9
Python 3 IE reducefunctools모듈에서 연산자를 가져와야합니다 from functools import reduce.
Chris Mueller

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

3
+1이지만 operator.mul더 나은 방법 은 @wiso의 답변을 참조하십시오 .
Chris Lutz

operator.mul이 x * y보다 선호되는 이유는 무엇입니까?
Adam Hughes

2
operator.mul은 함수이므로 x * y뿐만 아니라 전체 람다 식 (즉, 첫 번째 인수 reduce)을
대체합니다.

6
from functools import reduce파이썬 3에서 작동 하려면 가져 오기를 수행해야합니다.
lifebalance

45

목록에 숫자 만있는 경우 :

from numpy import prod
prod(list)

편집 : @ off99555에 의해 지적 된대로 이것은 큰 정수 결과에 대해 작동하지 않습니다.이 경우 numpy.int64Ian Clelland의 솔루션을 기반으로 operator.mul하고 reduce큰 정수 결과에 대해 반환하는 동안 형식 결과를 반환합니다 long.


목록이 짧은 경우에 느리다
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큰 정수 에 사용하십시오 ( long이 경우 임의의 정밀도를 갖는 것으로 보이는).
Andre Holzner

@ off99555 두 가지 해결책 : float 유형 목록으로 시작 np.prod(np.arange(5.0,101.0))하거나이를 수행하여 float로 변환하십시오 np.prod(np.array(range(5,101)).astype(np.float64)). NumPy는 np.float64대신에 사용 합니다 float. 나는 그 차이를 모른다.
Wood

22

실제로 가져 오기없이 한 줄로 만들고 싶다면 다음을 수행하십시오.

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

그러나하지 마십시오.


본질적으로 꽤 파이썬적인 것
Jitin

아무것도 가져 오지 않고 졸에 대한 타이!
John D

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

1
마지막 논쟁 (1)이 정말로 필요한가?
Ruggero Turra

10
목록이 비어 있으면 마지막 인수가 필요합니다. 그렇지 않으면 TypeError 예외가 발생합니다. 물론, 때로는 예외가 당신이 원하는 것입니다.
Dave Kirby

2
나에게 그 인수없이 0을 반환하므로 빈 제품 규칙을 적용해야한다고 생각할 수도 있습니다.
bug

또는 functools.reduce(..)에서 python3
앙드레 HOLZNER

18

시작 Python 3.8하여 prod함수가 math표준 라이브러리 의 모듈에 포함되었습니다 .

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

startiterable 숫자 의 값 (기본값 : 1) 곱의 곱을 반환합니다 .

import math

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

iterable이 비어 있으면 1(또는 start제공된 경우 값) 생성됩니다.


15

comp.lang.python (죄송하지만 포인터를 생성하기에는 너무 게으르다)에 대한 오랜 토론을 기억 하며 원래 product()정의가 가장 Pythonic 이라고 결론 내 렸습니다 .

제안은 당신이 그것을 원할 때마다 for 루프를 작성하는 것이 아니라 함수를 한 번 (축소 유형별로) 작성하고 필요에 따라 호출하는 것입니다! 축소 함수를 호출하는 것은 매우 Pythonic입니다. 생성기 표현식과 잘 작동하며,을 성공적으로 도입 한 sum()이후 Python은 점점 더 기본 제공되는 축소 함수를 계속 증가 any()시키고 all()있습니다.

이 결론은 좀 공식입니다 - reduce()제거 말, 파이썬 3.0에 내장 명령에서 :

" functools.reduce()실제로 필요한 경우 사용하십시오 . 그러나 명시 적 for 루프가 더 읽기 쉬운 시간의 99 %입니다."

Guido의 지원 인용문 및 해당 블로그를 읽는 Lispers의 의견이 적지 않은 내용은 Python 3000의 reduce () 운명을 참조하십시오 .

우연히 product()조합론 이 필요한 경우 PS math.factorial()(신규 2.6)를 참조하십시오 .


2
파이썬 커뮤니티에서 우세한 분위기에 대한 정확한 (내 지식을 최대한 활용하여) 설명을 위해 +1-이 경우에 우세한 분위기에 반대하는 것을 확실히 선호하지만, 어쨌든 그들이 무엇인지 알고하는 것이 가장 좋습니다. 또한, 나는 LtU의지지하지 않는 Lispers에 대해 조금 좋아합니다 (나는 그중 하나 일 것입니다). :-)
Michał Marczyk

7

이 답변의 목적은 특정 상황에서 유용한 계산을 제공하는 것입니다. 즉, a) 최종 제품이 매우 크거나 작을 수 있도록 많은 수의 값을 곱한 경우 b) 실제로 정확한 답에 신경 쓰지 말고 대신 많은 수의 시퀀스를 가지며 각 제품에 따라 주문할 수 있기를 원합니다.

리스트의 요소를 곱하려면 (여기서 l은리스트 임) 다음을 수행 할 수 있습니다.

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

이제 그 접근 방식은 읽을 수 없습니다

from operator import mul
reduce(mul, list)

reduce ()에 익숙하지 않은 수학자라면 그 반대 일 수도 있지만 정상적인 상황에서는 사용하지 않는 것이 좋습니다. 또한 질문에 언급 된 product () 함수보다 읽기 쉽지 않습니다 (적어도 비 수학자에게는).

그러나 다음과 같이 언더 플로 또는 오버플로 위험이있는 상황에 처한 경우

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

당신의 목적은 제품이 무엇인지 아는 것이 아니라 다른 시퀀스의 제품을 비교하는 것입니다.

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

이 방법을 사용하면 오버플로 또는 언더 플로가 발생하는 실제 문제가 거의 불가능하기 때문에 갈 수있는 방법입니다. (계산 결과가 클수록 계산할 있는 경우 제품이 커집니다 .)


1
영리하지만 음수 또는 0 값이 있으면 실패합니다. : /
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. 이것은 reducePython 2와 3에 functools대해 (파이썬 3에 필요한 가져 오기 로 인해) 다른을 사용하지 않으며 , 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또는 @njitdecorator 를 사용 하는 것 입니다. 또한 코드에 하나 또는 두 개의 작은 조정을했습니다 (적어도 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 for루프가 실제로 가장 빠릅니다. Jupyter 노트북과 %timeit마법 기능 을 사용하여 numba로 꾸며진 성능을 측정 할 때 트릭이있을 수 있습니다 . 위의 타이밍이 올바른지 확실하지 않으므로 시스템에서 시도해보고 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

OP 테스트를위한 Python 3 결과 : (각각 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.