Повернення продукту списку


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

Результати з python 2.7.5

       | 1 | 2 | 3 | 4 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 А 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 |
------- + ----------- + ----------- + ----------- + ------ ----- +
 А 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 мкс

Пітон 3 повільніше?


1
Дуже цікаво, спасибі Будь-яка ідея, чому python 3 може бути повільніше?
Саймон Уоткінс

3
Можливі причини: (1) Python 3 intє Python 2 long. Python 2 буде використовувати "int", поки він не переповнить 32 біти; Python 3 з самого початку використовуватиме "довго". (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)
Йоханнес Чарра,

6
Ви повинні зробити імпорт, from functools import reduceщоб він працював у Python 3.
життєвий баланс

45

якщо у вас у списку просто номери:

from numpy import prod
prod(list)

EDIT : як вказував @ 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для великих цілих чисел (він повертає a longв цьому випадку, який, здається, має довільну точність).
Андре Хольцнер

@ off99555 Два рішення: або почати зі списку типу float, виконуючи його, np.prod(np.arange(5.0,101.0))або перетворити його на плаваючу дію np.prod(np.array(range(5,101)).astype(np.float64)). Зауважте, що np.float64замість них використовується NumPy float. Я не знаю різниці.
Вуд

22

Добре, якщо ви дійсно хотіли зробити його одним рядком, не імпортуючи нічого, що могли б зробити:

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

Але не варто.


Досить піфонічний по суті
Джитін

Ти для соля, не імпортуючи нічого!
Джон Д

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

1
чи справді необхідний останній аргумент (1)?
Ruggero Turra

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()визначення є найбільш піфонічним .

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

Цей висновок є чимось офіційним - його reduce()було вилучено з вбудованих програм Python 3.0, кажучи:

"Використовуйте, functools.reduce()якщо вам це справді потрібно; однак, 99 відсотків часу, який явний цикл, є більш читабельним."

Дивитися також долю скорочення () в Python 3000 для додаткової цитати від Guido (та деяких менш підтримуючих коментарів Lispers, які читають цей блог).

PS якщо випадково вам потрібна product()комбінаторика, дивіться math.factorial()(новий 2.6).


2
+1 для точного (наскільки мені відомо) доповіді про переважаючі настрої в спільноті Python - хоча я, безумовно, вважаю за краще йти проти зазначених переважаючих настроїв у цьому випадку, найкраще їх знати, якими вони є. Крім того, мені подобається трохи непідтримуваних Lispers з LtU (я думаю, я був би одним із таких). :-)
Міхал Марчик

7

Метою цієї відповіді є надання розрахунку, який корисний у певних обставинах, а саме коли: а) множиться велика кількість значень таким чином, що кінцевий продукт може бути надзвичайно великим або надзвичайно малим, і б) ви не ' t насправді не хвилює точну відповідь, але натомість має ряд послідовностей, і хочемо мати можливість замовити їх на основі продукту кожного.

Якщо ви хочете помножити елементи списку, де l - це список, ви можете зробити:

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

Тепер такий підхід не такий читабельний, як

from operator import mul
reduce(mul, list)

Якщо ви математик, який не знайомий зі зменшенням (), може бути і навпаки, але я б не радив використовувати його за звичайних обставин. Він також менш читабельний, ніж функція product (), зазначена у питанні (принаймні, для не-математиків).

Однак якщо ви коли-небудь потрапляєте в ситуацію, коли ви ризикуєте перетікати або переповнювати, наприклад, в

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

і ваше призначення - порівняти продукти різних послідовностей, а не знати, що це за продукти

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

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


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

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

Тепер, коли ви виконайте свій код, він запуститься зі складеною версією функції. Я приурочив їх за допомогою ноутбука Юпітера та %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 насправді був найшвидшим. Тут може бути хитрість, коли справа стосується вимірювання ефективності, прикрашеної нумба, за допомогою ноутбуків Юпітера та %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 для тестів на ОП: (найкращий з 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.