Возврат продукта из списка

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ответы поднимают a TypeError, тогда как forответ цикла возвращает 1. Это ошибка в forответе цикла (произведение пустого списка не больше 1, чем 17 или «броненосец»).
Скотт Гриффитс
5
Пожалуйста, постарайтесь не использовать имена встроенных элементов (например, список) для имен ваших переменных.
Марк Байерс
2
Старый ответ, но у меня возникает соблазн редактировать, чтобы он не использовался listв качестве имени переменной ...
beroe
13
Результатом пустого списка является 1. en.wikipedia.org/wiki/Empty_product
Пол Кроули
1
@ScottGriffiths Я должен был указать, что я имел в виду список чисел. И я бы сказал, что сумма пустого списка является элементом идентификации +для этого типа списка (аналогично для продукта / *). Теперь я понимаю, что Python динамически типизирован, что усложняет ситуацию, но это решаемая проблема в нормальных языках со статическими системами типов, такими как Haskell. Но Pythonтолько позволяет sumработать над числами в любом случае, так как, sum(['a', 'b'])даже не работа, поэтому я еще раз говорю , что 0имеет смысл sumи 1для продукта.
точка с запятой

Ответы:

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

Результаты с питоном 2.7.5

       | 1 | 2 | 3 | 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 A 20,8 мкс 13,3 мкс 22,6 мкс 39,6 мкс     
 B 106 мкс 95,3 мкс 5,92 мкс 26,1 мкс
 C 4,34 мс 3,51 мс 16,7 мкс 38,9 мкс
 D 46,6 мс 38,5 мс 180 мкс 216 мкс

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

с питоном 3.3.2:

       | 1 | 2 | 3 | 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 A 23,6 мкс 12,3 мкс 68,6 мкс 84,9 мкс     
 B 133 мкс 107 мкс 7,42 мкс 27,5 мкс
 C 4,79 мс 3,74 мс 18,6 мкс 40,9 мкс
 D 48,4 мс 36,8 мс 187 мкс 214 мкс

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

Руджеро Турра
источник
1
Очень интересно, спасибо. Любая идея, почему Python 3 может быть медленнее?
Саймон Уоткинс
3
Возможные причины: (1) Python 3 int- это Python 2 long. Python 2 будет использовать int до тех пор, пока он не переполнится 32 битами; Python 3 будет использовать «long» с самого начала. (2) Python 3.0 был «доказательством концепции». Обновите до 3.1 как можно скорее!
Джон Мачин
1
Я переделал тот же тест на другой машине: python 2.6 ('с лямбда-кодом', 21.843887090682983) ('без лямбды:', 9.7096879482269287) python 3.1: с лямбдой: 24.7712180614 без лямбды: 10.7758350372
Ruggero Turra
1
оба терпят неудачу с пустыми списками.
ошибка
9
Обратите внимание, что вы должны импортировать reduceоператор из functoolsмодуля в Python 3. IE from functools import reduce.
Крис Мюллер,
50
reduce(lambda x, y: x * y, list, 1)
Йоханнес Чарра
источник
3
+1, но посмотрите ответ @ wiso о том, operator.mulкак лучше это сделать.
Крис Латс
почему operator.mul предпочтительнее, чем x * y?
Адам Хьюз
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то время как решение Яна Клелленда основано на больших целочисленных результатах, operator.mulи reduceработает, потому что оно возвращает long.

Андре Хольцнер
источник
это медленнее, если список короткий
эндолит
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в этом случае оно возвращает a, которое, по-видимому, имеет произвольную точность).
Андре Хольцнер
@ 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
Ты для соль, не импортируя ничего!
Джон Д
18
import operator
reduce(operator.mul, list, 1)
Ян Клелланд
источник
1
последний аргумент (1) действительно необходим?
Руджеро Турра
10
Последний аргумент необходим, если список может быть пустым, в противном случае он выдаст исключение TypeError. Конечно, иногда исключением будет то, что вы хотите.
Дейв Кирби
2
Для меня это возвращает 0 без этого аргумента, так что вы также можете счесть необходимым обеспечить соблюдение соглашения о пустом продукте.
ошибка
или functools.reduce(..)в python3
Андре Хольцнер,
18

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

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

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

import math

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

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

Ксавье Гихот
источник
15

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

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

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

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

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

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

Бени Чернявский-Паскин
источник
2
+1 за точный (насколько мне известно) отчет о преобладающих настроениях в сообществе Python - хотя я определенно предпочитаю идти против указанных преобладающих настроений в этом случае, лучше знать их такими, какие они есть. Кроме того, мне нравится немного о не поддерживающих Лисперсе от LtU (я думаю, я буду одним из них). :-)
Михал Марчик
7

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

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

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

garyrob
источник
1
Это умно, но не получается, если у вас есть отрицательные или нулевые значения. : /
Алекс Мейбург
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), и, кроме того, сам Гвидо ван Россум считает непитоническим :

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
Chris_Rands
источник
1

Одним из вариантов является использование numbaи @jitили @njitдекоратора . Я также сделал один или два небольших изменения в вашем коде (по крайней мере, в Python 3 «список» - это ключевое слово, которое не должно использоваться для имени переменной):

@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 нативный forцикл Python был на самом деле самым быстрым. Здесь может быть хитрость, когда дело доходит до измерения производительности, украшенной нумбой, с помощью ноутбуков Jupyter и %timeitволшебной функции. Я не уверен, что приведенные выше значения времени верны, поэтому я рекомендую попробовать их в своей системе и посмотреть, дает ли numba прирост производительности.

Engineero
источник
0

Самый быстрый способ, который я нашел, был при использовании 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])'))

и сроки:

>>> using while : 0.8887967770060641

>>> using mul : 2.0838719510065857

>>> using lambda : 2.4227715369997895
appsdownload
источник
Это, вероятно, из-за короткой длины списка, вероятно, потребуется еще несколько экспериментов
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]    
user2120413
источник
Я исправил отступ, но я думаю, что вы должны заменить последний printс возвратом. Кроме того, нет необходимости хранить промежуточные значения в списке, вам просто нужно хранить pмежду итерациями.
BoppreH