Возврат продукта из списка - PullRequest
139 голосов
/ 20 января 2010

Есть ли более лаконичный, эффективный или просто питонический способ сделать следующее?

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

EDIT:

Я на самом деле считаю, что это немного быстрее, чем при использовании 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)

Ответы [ 13 ]

158 голосов
/ 21 января 2010

Без использования лямбды:

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     |
-------+-----------+-----------+-----------+-----------+
 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

Результат: np.prod - самый быстрый, если вы используете np.array в качестве структуры данных (18x для маленького массива, 250x для большого массива)

с питоном 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

Python 3 медленнее?

44 голосов
/ 20 января 2010
reduce(lambda x, y: x * y, list, 1)
39 голосов
/ 10 декабря 2012

, если в вашем списке просто есть номера:

from numpy import prod
prod(list)

РЕДАКТИРОВАТЬ : как указано @ off99555, это не работает для больших целочисленных результатов, в этом случае он возвращает результат типа numpy.int64, тогда как решение Яна Клелланда основано на operator.mul и reduce работает для больших целочисленных результатов, потому что возвращает long.

17 голосов
/ 21 января 2010
import operator
reduce(operator.mul, list, 1)
16 голосов
/ 21 августа 2015

Хорошо, если вы действительно хотите сделать это одной строкой, не импортируя ничего, что вы могли бы сделать:

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

Но не надо.

14 голосов
/ 23 января 2010

Я помню несколько долгих обсуждений на comp.lang.python (извините, слишком ленив, чтобы создавать указатели сейчас), которые пришли к выводу, что ваше первоначальное определение product() является наиболее питоническим .

Обратите внимание, что предложение состоит не в том, чтобы писать цикл for каждый раз, когда вы хотите это сделать, а в том, чтобы написать функцию один раз (для каждого типа сокращения) и вызывать ее по мере необходимости! Вызов функций сокращения очень Pythonic - он прекрасно работает с выражениями генератора, и с тех пор, как успешно введено sum(), Python продолжает наращивать все больше и больше встроенных функций сокращения - any() и all() являются последними дополнениями ...

Этот вывод является своего рода официальным: reduce() был удален из встроенных в Python 3.0, говоря:

"Используйте functools.reduce(), если вам это действительно нужно; однако в 99 процентах случаев явный цикл for более читабелен."

См. Также Судьба Redu () в Python 3000 для поддерживающей цитаты из Гвидо (и некоторые менее поддерживающие комментарии Лисперса, которые читают этот блог).

P.S. если вам случайно понадобится product() для комбинаторики, см. math.factorial() (новый 2.6).

9 голосов
/ 14 февраля 2019

Начиная с Python 3.8, функция prod включена в модуль math в стандартной библиотеке:

math.prod (повторяем, *, start = 1)

, который возвращает произведение значения start (по умолчанию: 1), умноженное на итерируемое число:

import math

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

Обратите внимание, что если итерация пуста, это приведет к 1 (или значению start, если предусмотрено).

7 голосов
/ 02 февраля 2012

Цель этого ответа - предоставить расчет, который будет полезен в определенных обстоятельствах, а именно, когда: а) умножается большое количество значений, так что конечный продукт может быть очень большим или очень маленьким, и вы на самом деле не заботитесь о точном ответе, но вместо этого имеете ряд последовательностей и хотите иметь возможность упорядочить их по каждому продукту.

Если вы хотите умножить элементы списка, где 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

- это путь, потому что практически невозможно иметь реальную проблему, в которой вы будете переполнены или переполнены этим подходом. (Чем больше результат этого расчета, тем больше будет произведение, если вы сможете рассчитать его.)

2 голосов
/ 25 мая 2017

Я удивлен, что никто не предложил использовать itertools.accumulate с operator.mul. Это позволяет избежать использования reduce, который отличается для Python 2 и 3 (из-за импорта functools, требуемого для Python 3), и, кроме того, сам Гвидо ван Россум считает :

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 голос
/ 22 марта 2019

Я протестировал различные решения с perfplot (мой маленький проект) и обнаружил, что

numpy.prod(lst)

на данный момент самое быстрое решение (если список не очень короткий).

enter image description here


Код для воспроизведения сюжета:

import perfplot
import numpy

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 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,
    ],
    n_range=[2**k for k in range(15)],
    xlabel='len(a)',
    logx=True,
    logy=True,
    )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...