Як знайти сукупну суму чисел у списку?


93
time_interval = [4, 6, 12]

Я хочу підвести цифри, як [4, 4+6, 4+6+12]для того, щоб отримати список t = [4, 10, 22].

Я спробував наступне:

t1 = time_interval[0]
t2 = time_interval[1] + t1
t3 = time_interval[2] + t2
print(t1, t2, t3)  # -> 4 10 22

Дивіться також stackoverflow.com/q/9258602
hpaulj

Відповіді:


128

Якщо ви робите багато числової роботи з такими масивами, я б запропонував numpy, що постачається з функцією сукупної суми cumsum:

import numpy as np

a = [4,6,12]

np.cumsum(a)
#array([4, 10, 22])

Numpy часто швидший за чистий python для такого роду речей, див. У порівнянні з @ Ashwini'saccumu :

In [136]: timeit list(accumu(range(1000)))
10000 loops, best of 3: 161 us per loop

In [137]: timeit list(accumu(xrange(1000)))
10000 loops, best of 3: 147 us per loop

In [138]: timeit np.cumsum(np.arange(1000))
100000 loops, best of 3: 10.1 us per loop

Але, звичайно, якщо це єдине місце, де ви будете використовувати numpy, можливо, не варто мати залежність від нього.


3
Це має бути np.cumsunсправа, яка починається зі списку, щоб врахувати час перетворення.
hpaulj

3
Хороший момент @hpaulj, для тих, хто починає з (або прагне до) a, listя б не рекомендував numpy.
askewchan

Я не думаю , що NumPy є найшвидшим stackoverflow.com/questions/15889131 / ...
Chris_Rands

3
Погодився, як я вже згадував вище. Уникаючи реакцій, подібних до вашої та реакції @ hpaulj, саме тому я спробував обмежити її масштаби в перших і останніх рядках моєї відповіді: - /
askewchan

1
@alex: Використовуючи timeit, "якщо -nне вказано, обчислюється відповідна кількість циклів, випробовуючи послідовні ступені 10, поки загальний час не менше 0,2 секунди." Якщо ви очікуєте, що це щось змінить, ви можете запропонувати, -n 1000щоб зробити їх усі еквівалентними.
askewchan

94

У Python 2 ви можете визначити власну функцію генератора таким чином:

def accumu(lis):
    total = 0
    for x in lis:
        total += x
        yield total

In [4]: list(accumu([4,6,12]))
Out[4]: [4, 10, 22]

А в Python 3.2+ ви можете використовувати itertools.accumulate():

In [1]: lis = [4,6,12]

In [2]: from itertools import accumulate

In [3]: list(accumulate(lis))
Out[3]: [4, 10, 22]

5
PEP 572 - Вирази призначення (очікується для Python 3.8) показує цікаву альтернативу total = 0; partial_sums = [total := total + v for v in values]. Я все одно сподівався accumulateб бути швидшим.
Стівен Румбальський

3
@StevenRumbalski Людина, я особисто вважаю, що це найгірший PEP за всю історію. Досить погано ...
Ашвіні Чаудхарі

19

Ось:

a = [4, 6, 12]
reduce(lambda c, x: c + [c[-1] + x], a, [0])[1:]

Виведе (як очікувалося):

[4, 10, 22]

17
Не ефективно. Загальні витрати на виконання c + [c[-1] + x]знову і знову складають загальну квадратичну тривалість виконання вхідної довжини.
user2357112 підтримує Моніку

зменшення корисно для одноразової сукупної суми, але якщо ви робите багато дзвінків до вашої функції cumsum, генератор буде корисним для "попередньої обробки" ваших значень cumulative_sum та доступу до них у O (1) для кожного наступного виклику.
Скотт Скілз,

18

Я провів тестування двох найкращих відповідей з Python 3.4 і виявив, що itertools.accumulateце швидше, ніж numpy.cumsumза багатьох обставин, часто набагато швидше. Однак, як ви можете бачити з коментарів, це може бути не завжди так, і важко вичерпно вивчити всі варіанти. (Не соромтеся додавати коментар або редагувати цю публікацію, якщо у вас є додаткові результати, які вас цікавлять.)

Деякі терміни ...

Для коротких списків accumulateце приблизно в 4 рази швидше:

from timeit import timeit

def sum1(l):
    from itertools import accumulate
    return list(accumulate(l))

def sum2(l):
    from numpy import cumsum
    return list(cumsum(l))

l = [1, 2, 3, 4, 5]

timeit(lambda: sum1(l), number=100000)
# 0.4243644131347537
timeit(lambda: sum2(l), number=100000)
# 1.7077815784141421

Для довших списків accumulateце приблизно в 3 рази швидше:

l = [1, 2, 3, 4, 5]*1000
timeit(lambda: sum1(l), number=100000)
# 19.174508565105498
timeit(lambda: sum2(l), number=100000)
# 61.871223849244416

Якщо значення numpy arrayне виконано list, accumulateвсе одно приблизно в 2 рази швидше:

from timeit import timeit

def sum1(l):
    from itertools import accumulate
    return list(accumulate(l))

def sum2(l):
    from numpy import cumsum
    return cumsum(l)

l = [1, 2, 3, 4, 5]*1000

print(timeit(lambda: sum1(l), number=100000))
# 19.18597290944308
print(timeit(lambda: sum2(l), number=100000))
# 37.759664884768426

Якщо ви поставите імпорт поза двома функціями і все одно повернете a numpy array, accumulateце все одно майже в 2 рази швидше:

from timeit import timeit
from itertools import accumulate
from numpy import cumsum

def sum1(l):
    return list(accumulate(l))

def sum2(l):
    return cumsum(l)

l = [1, 2, 3, 4, 5]*1000

timeit(lambda: sum1(l), number=100000)
# 19.042188624851406
timeit(lambda: sum2(l), number=100000)
# 35.17324400227517

10
Ви не очікуєте, що літак буде швидшим за поїзд, щоб їхати по місту, особливо, включаючи придбання квитків та перевірку безпеки. Так само ви не використовуєте numpy для обробки listп'яти предметів, особливо якщо ви не бажаєте приймати arrayвзамін. Якщо перелік, про який йде мова, справді такий короткий, то час їх роботи був би суттєвим - залежності та розбірливість, безумовно, домінували б. Але широке використання listуніфікованого числового типу даних значної довжини було б безглуздо; для цього, NumPy array буде доречно, і , як правило , швидше.
askewchan

@askewchan ну, я не просто знаходжу це для коротких списків, і питання OP вимагає список як вихідний результат, а не масив numpy. Можливо, ви зможете відредагувати свою відповідь, щоб було зрозуміліше, коли кожне використання доречне :)
Chris_Rands

@askewchan Насправді я відредагував свою відповідь набагато детальнішим порівнянням. Я ні за яких обставин не вважаю numpyсебе швидшим, якщо я чогось не пропустив?
Chris_Rands

2
О, так, справді :) Я б не сказав, що ви щось пропустили, але порівняння важко зробити окремо, не враховуючи ваші вхідні та вихідні дані. Велику частину часу у вашій sum2функції, мабуть, полягає у перетворенні lв масив. Спробуйте хронометраж a = np.array(l)і np.cumsum(a)окремо. Тоді спробуйте a = np.tile(np.arange(1, 6), 1000)проти l = [1,2,3,4,5]*1000. У програмі, яка проводить інші числові процеси (наприклад, створення або завантаження, lв першу чергу), ваші робочі дані, мабуть, вже будуть у масиві, і створення буде постійною вартістю.
askewchan

1
@askewchan У мене така сама ідея, як і у вас, і тому я пришвидшив a = np.array (l). Для sum2 без перетворення в список та з масивом numpy як вхідним, sum2 в 5 разів швидший слава sum1 на моєму комп'ютері у випадку довгого списку / масиву.
Mantxu

9

Спробуйте наступне: функція накопичення разом з оператором add виконує запущене додавання.

import itertools  
import operator  
result = itertools.accumulate([1,2,3,4,5], operator.add)  
list(result)

5
Вам не потрібно переходити, operator.addоскільки операція за замовчуванням є додаванням.
Євген Ярмаш


5

Ви можете розрахувати сукупний список сум за лінійний час за допомогою простого forциклу:

def csum(lst):
    s = lst.copy()
    for i in range(1, len(s)):
        s[i] += s[i-1]
    return s

time_interval = [4, 6, 12]
print(csum(time_interval))  # [4, 10, 22]

Стандартна бібліотека itertools.accumulateможе бути швидшою альтернативою (оскільки вона реалізована на мові C):

from itertools import accumulate
time_interval = [4, 6, 12]
print(list(accumulate(time_interval)))  # [4, 10, 22]

2
values = [4, 6, 12]
total  = 0
sums   = []

for v in values:
  total = total + v
  sums.append(total)

print 'Values: ', values
print 'Sums:   ', sums

Запуск цього коду дає

Values: [4, 6, 12]
Sums:   [4, 10, 22]

2

У Python3, щоб знайти сукупну суму списку, де ith-й елемент є сумою перших i + 1 елементів із вихідного списку, ви можете зробити:

a = [4 , 6 , 12]
b = []
for i in range(0,len(a)):
    b.append(sum(a[:i+1]))
print(b)

АБО ви можете використовувати розуміння списку:

b = [sum(a[:x+1]) for x in range(0,len(a))]

Вихідні дані

[4,10,22]

Це виглядає правильно, але може залишити посилання на документацію, без цього я не можу проголосувати.
S Meaden

2

Якщо ви хочете, щоб пітонічний спосіб без роботи numpy працював у 2.7, це мій спосіб зробити це

l = [1,2,3,4]
_d={-1:0}
cumsum=[_d.setdefault(idx, _d[idx-1]+item) for idx,item in enumerate(l)]

тепер спробуймо і протестуємо його проти всіх інших реалізацій

import timeit, sys
L=list(range(10000))
if sys.version_info >= (3, 0):
    reduce = functools.reduce
    xrange = range


def sum1(l):
    cumsum=[]
    total = 0
    for v in l:
        total += v
        cumsum.append(total)
    return cumsum


def sum2(l):
    import numpy as np
    return list(np.cumsum(l))

def sum3(l):
    return [sum(l[:i+1]) for i in xrange(len(l))]

def sum4(l):
    return reduce(lambda c, x: c + [c[-1] + x], l, [0])[1:]

def this_implementation(l):
    _d={-1:0}
    return [_d.setdefault(idx, _d[idx-1]+item) for idx,item in enumerate(l)]


# sanity check
sum1(L)==sum2(L)==sum3(L)==sum4(L)==this_implementation(L)
>>> True    

# PERFORMANCE TEST
timeit.timeit('sum1(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.001018061637878418

timeit.timeit('sum2(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.000829620361328125

timeit.timeit('sum3(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.4606760001182556 

timeit.timeit('sum4(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.18932826995849608

timeit.timeit('this_implementation(L)','from __main__ import sum1,sum2,sum3,sum4,this_implementation,L', number=100)/100.
>>> 0.002348129749298096

2

Відповідей на це може бути багато, залежно від довжини списку та результативності. Дуже простий спосіб, про який я можу думати, не замислюючись про виставу, такий:

a = [1, 2, 3, 4]
a = [sum(a[0:x:1]) for x in range(len(a)+1)][1:]
print(a)

[1, 3, 6, 10]

Це за допомогою розуміння списку, і це може працювати досить добре, просто я тут багато разів додаю над підмасивом, можливо, ви можете імпровізувати це і зробити це просто!

Вітаємо за ваші старання!


1

По-перше, вам потрібен запущений список підпослідовностей:

subseqs = (seq[:i] for i in range(1, len(seq)+1))

Потім ви просто зателефонуєте sumдо кожної послідовності:

sums = [sum(subseq) for subseq in subseqs]

(Це не найефективніший спосіб зробити це, оскільки ви додаєте всі префікси неодноразово. Але це, мабуть, не буде мати значення для більшості випадків використання, і це легше зрозуміти, якщо вам не доведеться думати про загальні підсумки.)

Якщо ви використовуєте Python 3.2 або новішу версію, ви можете itertools.accumulateзробити це за вас:

sums = itertools.accumulate(seq)

А якщо ви використовуєте версію 3.1 або старішу, ви можете просто скопіювати джерело "еквівалент" прямо з документів (за винятком зміни next(it)на it.next()2.5 і раніше).


9
Це працює в квадратичному часі (можливо, це не має значення для ОП, але варто згадати).
Кріс Тейлор,

По-перше, коли N = 3, хто дбає про квадратичний час? І я не думаю, що це надто складно. Це два дуже простих кроки, кожен з яких перетворює один ітератор в інший, безпосередньо перекладаючи англомовний опис. (Той факт, що він використовує незвичний спосіб визначення серії, де префікс 0-довжини не враховується, робить це дещо складнішим ... але це невід'ємна частина проблеми, і я вважав, що краще ввести це в rangeніж хакати навколо цього, роблячи [1:]в кінці, або ігнорувати це.)
abarnert

1
Імовірно, справжня проблема ОП полягає не в тому, щоб отримати часткові суми, [4,6,12]оскільки, як він писав у питанні, він уже знає, що це таке!
Кріс Тейлор,

@ChrisTaylor: Він прямо сказав, що вже знає, як це писати, але хоче "простіший спосіб написати".
abarnert

1

Спробуйте це:

result = []
acc = 0
for i in time_interval:
    acc += i
    result.append(acc)

-1
In [42]: a = [4, 6, 12]

In [43]: [sum(a[:i+1]) for i in xrange(len(a))]
Out[43]: [4, 10, 22]

Це трохи швидше, ніж метод генератора, описаний вище @Ashwini для невеликих списків

In [48]: %timeit list(accumu([4,6,12]))
  100000 loops, best of 3: 2.63 us per loop

In [49]: %timeit [sum(a[:i+1]) for i in xrange(len(a))]
  100000 loops, best of 3: 2.46 us per loop

Для більших списків генератор - це напевно. . .

In [50]: a = range(1000)

In [51]: %timeit [sum(a[:i+1]) for i in xrange(len(a))]
  100 loops, best of 3: 6.04 ms per loop

In [52]: %timeit list(accumu(a))
  10000 loops, best of 3: 162 us per loop

1
Ви призначаєте лише 3 списки, спробуйте 10 ^ 4.
Ashwini Chaudhary

1
Правда, для більших списків генератор набагато швидший!
reptilicus

-1

Дещо хакі, але, здається, працює:

def cumulative_sum(l):
  y = [0]
  def inc(n):
    y[0] += n
    return y[0]
  return [inc(x) for x in l]

Я дійсно думав, що внутрішня функція зможе змінити yзаявлене у зовнішній лексичній області, але це не спрацювало, тому замість цього ми граємо в неприємні хаки із модифікацією структури. Можливо, більш елегантно використовувати генератор.


-1

Не використовуючи Numpy, ви можете прокручувати безпосередньо масив і накопичувати суму по дорозі. Наприклад:

a=range(10)
i=1
while((i>0) & (i<10)):
    a[i]=a[i-1]+a[i]
    i=i+1
print a

Призводить до:

[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

-1

Чистий python oneliner для сукупної суми:

cumsum = lambda X: X[:1] + cumsum([X[0]+X[1]] + X[2:]) if X[1:] else X

Це рекурсивна версія, натхненна рекурсивними кумулятивними сумами . Деякі пояснення:

  1. Перший термін X[:1]- це список, що містить попередній елемент, і майже такий самий, як і [X[0]](який скаржиться на порожні списки).
  2. Рекурсивний cumsumвиклик у другому доданку обробляє поточний елемент [1]та список, що залишився, довжина якого буде зменшена на одиницю.
  3. if X[1:]коротше на if len(X)>1.

Тест:

cumsum([4,6,12])
#[4, 10, 22]

cumsum([])
#[]

І модельний для сукупного продукту:

cumprod = lambda X: X[:1] + cumprod([X[0]*X[1]] + X[2:]) if X[1:] else X

Тест:

cumprod([4,6,12])
#[4, 24, 288]

-1
l = [1,-1,3]
cum_list = l

def sum_list(input_list):
    index = 1
    for i in input_list[1:]:
        cum_list[index] = i + input_list[index-1]
        index = index + 1 
    return cum_list

print(sum_list(l))

-1

Ось ще одне цікаве рішення. Це використовує locals()переказ розуміння, тобто локальні змінні, що генеруються всередині області розуміння списку:

>>> [locals().setdefault(i, (elem + locals().get(i-1, 0))) for i, elem 
     in enumerate(time_interval)]
[4, 10, 22]

Ось як locals()виглядає кожна ітерація:

>>> [[locals().setdefault(i, (elem + locals().get(i-1, 0))), locals().copy()][1] 
     for i, elem in enumerate(time_interval)]
[{'.0': <enumerate at 0x21f21f7fc80>, 'i': 0, 'elem': 4, 0: 4},
 {'.0': <enumerate at 0x21f21f7fc80>, 'i': 1, 'elem': 6, 0: 4, 1: 10},
 {'.0': <enumerate at 0x21f21f7fc80>, 'i': 2, 'elem': 12, 0: 4, 1: 10, 2: 22}]

Ефективність не страшна для невеликих списків:

>>> %timeit list(accumulate([4, 6, 12]))
387 ns ± 7.53 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

>>> %timeit np.cumsum([4, 6, 12])
5.31 µs ± 67.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

>>> %timeit [locals().setdefault(i, (e + locals().get(i-1,0))) for i,e in enumerate(time_interval)]
1.57 µs ± 12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

І, очевидно, не можна сказати про більші списки.

>>> l = list(range(1_000_000))
>>> %timeit list(accumulate(l))
95.1 ms ± 5.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit np.cumsum(l)
79.3 ms ± 1.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit np.cumsum(l).tolist()
120 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit [locals().setdefault(i, (e + locals().get(i-1, 0))) for i, e in enumerate(l)]
660 ms ± 5.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Незважаючи на те, що метод потворний і не практичний, він, безсумнівно, приносить задоволення.


-2
lst = [4,6,12]

[sum(lst[:i+1]) for i in xrange(len(lst))]

Якщо ви шукаєте більш ефективне рішення (більші списки?), Генератор може стати гарним викликом (або просто використати, numpyякщо ви дійсно піклуєтесь про перфоманс).

def gen(lst):
    acu = 0
    for num in lst:
        yield num + acu
        acu += num

print list(gen([4, 6, 12]))

-3

Це буде в стилі Хаскелла:

def wrand(vtlg):

    def helpf(lalt,lneu): 

        if not lalt==[]:
            return helpf(lalt[1::],[lalt[0]+lneu[0]]+lneu)
        else:
            lneu.reverse()
            return lneu[1:]        

    return helpf(vtlg,[0])
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.