Jak znaleźć łączną sumę liczb na liście?


92
time_interval = [4, 6, 12]

Chcę podsumować liczby tak [4, 4+6, 4+6+12], aby uzyskać listę t = [4, 10, 22].

Próbowałem następujących rzeczy:

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

Odpowiedzi:


128

Jeśli wykonujesz dużo pracy numerycznej z takimi tablicami, proponuję numpy, która zawiera skumulowaną funkcję sumy cumsum:

import numpy as np

a = [4,6,12]

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

Numpy jest często szybszy niż czysty Python w tego rodzaju rzeczach, zobacz w porównaniu do @ Ashwiniaccumu :

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

Ale oczywiście, jeśli jest to jedyne miejsce, w którym będziesz używać numpy, może nie być warte uzależnienia od niego.


3
Powinien mieć np.cumsunprzypadek, który zaczyna się od listy, aby uwzględnić czas konwersji.
hpaulj

3
Dobra uwaga @hpaulj, dla tych, którzy zaczynają od (lub dążą do) a list, nie polecam numpy.
askewchan

Nie sądzę, że numpy jest najszybszym stackoverflow.com/questions/15889131/ ...
Chris_Rands

3
Zgoda, jak wspomniałem powyżej. Unikanie reakcji takich jak twoja i @ hpaulj jest powodem, dla którego próbowałem ograniczyć ich zakres w pierwszej i ostatniej
linijce

1
@alex: Używając timeit, "jeśli -nnie jest podane, obliczana jest odpowiednia liczba pętli, próbując kolejnych potęg 10, aż całkowity czas wyniesie co najmniej 0,2 sekundy." Jeśli spodziewasz się, że to coś zmieni, możesz je dostarczyć, -n 1000aby wszystkie były równoważne.
askewchan

94

W Pythonie 2 możesz zdefiniować własną funkcję generatora w następujący sposób:

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]

A w Pythonie 3.2+ możesz użyć 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 - Wyrażenia przypisania (oczekiwane dla Pythona 3.8) pokazuje interesującą alternatywę total = 0; partial_sums = [total := total + v for v in values]. Nadal oczekiwałbym, accumulateże będę szybszy.
Steven Rumbalski

3
@StevenRumbalski Człowieku, osobiście uważam, że to najgorszy PEP wszechczasów. Wystarczająco źle ...
Ashwini Chaudhary,

19

Ujrzeć:

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

Wyświetli (zgodnie z oczekiwaniami):

[4, 10, 22]

17
Nieefektywne . Całkowity koszt wykonywania c + [c[-1] + x]w kółko sumuje się do całkowitego kwadratu czasu wykonywania w długości wejściowej.
user2357112 obsługuje Monikę

Redukcja jest dobra dla jednorazowej sumy skumulowanej, ale jeśli wykonujesz dużo wywołań swojej funkcji cumsum, generator będzie przydatny do "wstępnego przetwarzania" twoich wartości cumulative_sum i uzyskiwania do nich dostępu w O (1) dla każdego kolejnego wywołania.
Scott Skiles

17

Zrobiłem test porównawczy dwóch najlepszych odpowiedzi w Pythonie 3.4 i stwierdziłem, że itertools.accumulatejest szybszy niż numpy.cumsumw wielu okolicznościach, często znacznie szybszy. Jednak, jak widać z komentarzy, nie zawsze tak jest i trudno jest wyczerpująco zbadać wszystkie opcje. (Jeśli masz dalsze interesujące wyniki testów porównawczych, możesz dodać komentarz lub edytować ten post).

Niektóre czasy ...

W przypadku krótkich list accumulatejest około 4 razy szybszy:

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

W przypadku dłuższych list accumulatejest około 3 razy szybszy:

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

Jeśli numpy arraynie jest rzucany list, accumulatenadal jest około 2 razy szybszy:

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

Jeśli umieścisz import poza dwiema funkcjami i nadal zwrócisz a numpy array, accumulateto nadal jest prawie 2 razy szybsze:

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
Nie spodziewałbyś się, że samolot będzie szybszy niż pociąg, aby podróżować po mieście, zwłaszcza w przypadku zakupu biletów i kontroli bezpieczeństwa. Podobnie nie użyłbyś numpy do przetworzenia listz pięciu elementów, zwłaszcza jeśli nie chcesz przyjąć arrayw zamian. Gdyby lista, o której mowa, była naprawdę tak krótka, to ich czas wyświetlania byłby nieistotny - z pewnością dominowałyby zależności i czytelność. Jednak szerokie stosowanie listjednolitego, znacznego typu danych numerycznych byłoby głupie; za to tępyarray byłoby odpowiednie i zwykle szybsze.
askewchan

@askewchan, cóż, nie znajduję tego tylko dla krótkich list, a pytanie OP prosi o listę jako wyjście, a nie o tablicę numpy. Być może możesz zmienić swoją odpowiedź, aby była jaśniejsza, kiedy każde użycie jest właściwe :)
Chris_Rands

@askewchan W rzeczywistości zredagowałem swoją odpowiedź, wprowadzając znacznie bardziej szczegółowe porównanie. Czy pod żadnym pozorem nie jestem numpyszybszy, chyba że coś przeoczyłem?
Chris_Rands

2
Ojej, tak naprawdę :) Nie powiedziałbym, że coś przeoczyłeś, ale porównanie jest trudne do zrobienia w odosobnieniu bez uwzględnienia danych wejściowych i wyjściowych. Przez większość czasu twoja sum2funkcja jest prawdopodobnie przekształcana lw tablicę. Wypróbuj czas a = np.array(l)i np.cumsum(a)osobno. Następnie spróbuj a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. W programie wykonującym inne procesy numeryczne (takie jak tworzenie lub ładowanie lw pierwszej kolejności) dane robocze prawdopodobnie już znajdowałyby się w tablicy, a ich tworzenie wiązałoby się ze stałym kosztem.
askewchan

1
@askewchan Wpadłem na ten sam pomysł co ty i dlatego sprawdziłem czas a = np.array (l). Dla sum2 bez transformacji do listy iz tablicą numpy jako danymi wejściowymi, sum2 jest 5 razy szybsza dzięki sum1 w moim komputerze w przypadku długiej listy / tablicy.
Mantxu

9

Spróbuj tego: funkcja akumuluj razem z operatorem add wykonuje bieżące dodawanie.

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

5
Nie musisz przekazywać, operator.addponieważ domyślną operacją i tak jest dodawanie.
Eugene Yarmash

8

Wyrażenia przypisania z PEP 572 (nowość w Pythonie 3.8) oferują jeszcze inny sposób rozwiązania tego problemu:

time_interval = [4, 6, 12]

total_time = 0
cum_time = [total_time := total_time + t for t in time_interval]

5

Możesz obliczyć skumulowaną listę sum w czasie liniowym za pomocą prostej forpętli:

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]

Biblioteka standardowa itertools.accumulatemoże być szybszą alternatywą (ponieważ jest zaimplementowana w 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

Uruchomienie tego kodu daje

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

2

W Pythonie3, aby znaleźć skumulowaną sumę listy, gdzie ten ielement jest sumą pierwszych elementów i + 1 z oryginalnej listy, możesz wykonać:

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

LUB możesz użyć rozumienia z listy:

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

Wynik

[4,10,22]

Wygląda to dobrze, ale można upuścić link do dokumentacji, bez tego nie mogę głosować za.
S Meaden

2

Jeśli chcesz mieć pythonowy sposób bez numpy pracy w 2.7, to byłby mój sposób na zrobienie tego

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

teraz wypróbujmy to i przetestujmy z wszystkimi innymi implementacjami

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

Odpowiedzi na to może być wiele w zależności od długości listy i wyników. Jeden bardzo prosty sposób, który mogę pomyśleć, nie myśląc o spektaklu, jest następujący:

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]

Dzieje się to za pomocą rozumienia list i może to działać całkiem nieźle, po prostu tutaj wiele razy dodam podtablicę, możesz prawdopodobnie improwizować i uprościć!

Pozdrawiam twoje przedsięwzięcie!


1

Po pierwsze, chcesz mieć bieżącą listę podciągów:

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

Następnie po prostu wywołujesz sumkażdy podciąg:

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

(Nie jest to najskuteczniejszy sposób, aby to zrobić, ponieważ wielokrotnie dodajesz wszystkie przedrostki. Ale to prawdopodobnie nie ma znaczenia w większości przypadków użycia i łatwiej jest to zrozumieć, jeśli nie musisz myśleć o sumy bieżące).

Jeśli używasz Pythona 3.2 lub nowszego, możesz itertools.accumulatezrobić to za siebie:

sums = itertools.accumulate(seq)

Jeśli używasz wersji 3.1 lub wcześniejszej, możesz po prostu skopiować źródło „odpowiednika” bezpośrednio z dokumentów (z wyjątkiem zmiany next(it)na wersję it.next()2.5 i starszą).


9
To działa w czasie kwadratowym (może to nie ma znaczenia dla PO, ale warto o tym wspomnieć).
Chris Taylor,

Po pierwsze, kiedy N = 3, kogo obchodzi czas kwadratowy? I nie sądzę, żeby to było zbyt skomplikowane. To dwa bardzo proste kroki, z których każdy przekształca jeden iterator w inny, bezpośrednio tłumacząc opis w języku angielskim. (Fakt, że używa on nietypowego sposobu definiowania serii, w których przedrostek o długości 0 nie jest liczony, sprawia, że ​​jest to trochę bardziej skomplikowane… ale to jest nieodłącznym elementem problemu i pomyślałem, że lepiej będzie umieścić to w rangeniż hakować wokół, robiąc [1:]na koniec, lub ignorować.)
abarnert

1
Przypuszczalnie faktycznym problemem PO nie jest uzyskanie częściowych sum, [4,6,12]ponieważ, jak napisał w pytaniu, już wie, co to jest!
Chris Taylor,

@ChrisTaylor: Wyraźnie powiedział, że już wie, jak to napisać, ale chce „łatwiejszego sposobu, aby to napisać”.
abarnert

1

Spróbuj tego:

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]

Jest to trochę szybsze niż metoda generatora powyżej autorstwa @Ashwini dla małych list

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

W przypadku większych list z pewnością dobrym rozwiązaniem jest generator. . .

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
Czas na listę tylko 3 pozycji, spróbuj 10 ^ 4 pozycji.
Ashwini Chaudhary

1
To prawda, w przypadku większych list generator jest znacznie szybszy!
reptilicus

-1

Trochę hacky, ale wydaje się działać:

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

Myślałem, że funkcja wewnętrzna byłaby w stanie zmodyfikować yzadeklarowany w zewnętrznym zakresie leksykalnym, ale to nie zadziałało, więc zamiast tego gramy w paskudne hacki z modyfikacją struktury. Prawdopodobnie bardziej eleganckie jest użycie generatora.


-1

Bez konieczności używania Numpy, możesz wykonać pętlę bezpośrednio nad tablicą i po drodze gromadzić sumę. Na przykład:

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

Prowadzi do:

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

-1

Czysty oneliner w pytonie dla sumy skumulowanej:

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

Jest to wersja rekurencyjna inspirowana rekursywnymi sumami kumulacyjnymi . Kilka wyjaśnień:

  1. Pierwszy termin X[:1]jest listą zawierającą poprzedni element i jest prawie taki sam jak [X[0]](który narzekałby na puste listy).
  2. cumsumWywołanie rekurencyjne w drugim członie przetwarza bieżący element [1]i pozostałą listę, których długość zostanie zmniejszona o jeden.
  3. if X[1:]jest krótszy dla if len(X)>1.

Test:

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

cumsum([])
#[]

I podobnie dla produktu zbiorczego:

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

Test:

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

Oto kolejne zabawne rozwiązanie. Wykorzystuje to locals()dyktowanie rozumienia, czyli zmienne lokalne generowane wewnątrz zakresu rozumienia listy:

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

Oto, jak locals()wygląda każda iteracja:

>>> [[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}]

Wydajność nie jest straszna w przypadku małych list:

>>> %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)

I oczywiście wypada płasko w przypadku większych list.

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

Chociaż metoda jest brzydka i niepraktyczna, z pewnością jest zabawna.


-2
lst = [4,6,12]

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

Jeśli szukasz bardziej wydajnego rozwiązania (większych list?), Dobrym rozwiązaniem może być generator (lub po prostu użyj, numpyjeśli naprawdę zależy Ci na perf).

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

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

-3

Byłoby to w stylu Haskella:

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])
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.