Wie finde ich die kumulative Summe von Zahlen in einer Liste?


91
time_interval = [4, 6, 12]

Ich möchte die Zahlen wie [4, 4+6, 4+6+12]folgt zusammenfassen, um die Liste zu erhalten t = [4, 10, 22].

Ich habe folgendes versucht:

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

Antworten:


125

Wenn Sie mit solchen Arrays viel numerische Arbeit leisten, würde ich vorschlagen numpy, dass sie eine kumulative Summenfunktion enthalten cumsum:

import numpy as np

a = [4,6,12]

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

Numpy ist für solche Dinge oft schneller als reines Python, siehe im Vergleich zu @ Ashwinisaccumu :

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

Aber wenn es der einzige Ort ist, an dem Sie Numpy verwenden, lohnt es sich möglicherweise nicht, davon abhängig zu sein.


3
Dies sollte einen np.cumsunFall haben, der mit einer Liste beginnt, um die Konvertierungszeit zu berücksichtigen.
Hpaulj

3
Guter Punkt @hpaulj, für diejenigen, die von a ausgehen (oder darauf abzielen), listwürde ich nicht empfehlen numpy.
Askewchan

Ich denke nicht, dass Numpy am schnellsten ist stackoverflow.com/questions/15889131/…
Chris_Rands

3
Einverstanden, wie ich oben erwähnt habe. Um Reaktionen wie Ihre und @ hpauljs zu vermeiden, habe ich versucht, den Umfang in der allerersten und letzten Zeile meiner Antwort
einzuschränken

1
@ Alex: Verwenden timeit, „wenn -nnicht angegeben ist, eine geeignete Anzahl von Schleifen ist , indem man versucht , aufeinanderfolgende Potenzen von 10 , bis der Gesamtzeit berechnet beträgt mindestens 0,2 Sekunden.“ Wenn Sie erwarten, dass es einen Unterschied macht, können Sie liefern -n 1000, um alle gleichwertig zu machen.
Askewchan

93

In Python 2 können Sie Ihre eigene Generatorfunktion folgendermaßen definieren:

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]

Und in Python 3.2+ können Sie Folgendes verwenden 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 - Zuweisungsausdrücke (erwartet für Python 3.8) zeigen eine interessante Alternative total = 0; partial_sums = [total := total + v for v in values]. Ich würde immer noch erwarten accumulate, schneller zu sein.
Steven Rumbalski

3
@StevenRumbalski Mann, ich persönlich denke, das ist der schlechteste PEP aller Zeiten. Schlimm genug ...
Ashwini Chaudhary

18

Erblicken:

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

Wird ausgegeben (wie erwartet):

[4, 10, 22]

16
Nicht effizient. Die Gesamtkosten für die wiederholte Ausführung c + [c[-1] + x]summieren sich zu einer quadratischen Gesamtlaufzeit in der Eingabelänge.
user2357112 unterstützt Monica

Reduzieren ist gut für eine einmalige kumulative Summe, aber wenn Sie viele Aufrufe Ihrer Cumsum-Funktion ausführen, ist ein Generator hilfreich, um Ihre cumulative_sum-Werte vorzuverarbeiten und bei jedem nachfolgenden Aufruf in O (1) darauf zuzugreifen.
Scott Skiles

16

Ich habe mit Python 3.4 einen Benchmark der beiden besten Antworten durchgeführt und festgestellt, dass dies itertools.accumulateschneller ist als numpy.cumsumunter vielen Umständen, oft viel schneller. Wie Sie den Kommentaren entnehmen können, ist dies jedoch möglicherweise nicht immer der Fall, und es ist schwierig, alle Optionen ausführlich zu untersuchen. (Fühlen Sie sich frei, einen Kommentar hinzuzufügen oder diesen Beitrag zu bearbeiten, wenn Sie weitere interessante Benchmark-Ergebnisse haben.)

Einige Timings ...

Für kurze Listen accumulateist etwa 4 mal schneller:

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

Bei längeren Listen accumulategeht das ca. 3 mal schneller:

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

Wenn das numpy arraynicht gegossen wird list, accumulateist es immer noch ungefähr 2 mal schneller:

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

Wenn Sie die Importe außerhalb der beiden Funktionen platzieren und dennoch a zurückgeben numpy array, accumulateist dies immer noch fast zweimal schneller:

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
Sie würden nicht erwarten, dass ein Flugzeug schneller als der Zug durch die Stadt fährt, insbesondere mit Ticketkauf und Sicherheitsüberprüfung. Ebenso würden Sie numpy nicht verwenden, um einen listvon fünf Artikeln zu verarbeiten, insbesondere wenn Sie nicht bereit sind, einen Gegenwert anzunehmen array. Wenn die fragliche Liste wirklich so kurz ist, spielt ihre Laufzeit keine Rolle - Abhängigkeiten und Lesbarkeit würden sicherlich dominieren. Eine weite Verwendung eines listeinheitlichen numerischen Datentyps von signifikanter Länge wäre jedoch dumm; dafür array wäre eine numpy angebracht und normalerweise schneller.
Askewchan

@askewchan Nun, ich finde das nicht nur für kurze Listen und die Frage des OP fragt nach einer Liste als Ausgabe und nicht nach einem numpy-Array. Vielleicht können Sie Ihre Antwort bearbeiten, um klarer zu werden, wann jede Verwendung angemessen ist :)
Chris_Rands

@askewchan Tatsächlich habe ich meine Antwort mit einem viel detaillierteren Vergleich bearbeitet. Finden Sie numpyes unter keinen Umständen schneller, wenn ich etwas übersehen habe?
Chris_Rands

2
Oh mein Gott, ja in der Tat :) Ich würde nicht sagen, dass Sie etwas übersehen haben, aber der Vergleich ist schwer isoliert durchzuführen, ohne Ihre Ein- und Ausgänge zu berücksichtigen. Die meiste Zeit in Ihrer sum2Funktion wird wahrscheinlich lin ein Array konvertiert . Versuchen Sie das Timing a = np.array(l)und np.cumsum(a)separat. Dann versuchen a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. In einem Programm, das andere numerische Prozesse ausführt (wie das lerstmalige Erstellen oder Laden ), befinden sich Ihre Arbeitsdaten wahrscheinlich bereits in einem Array, und die Erstellung ist mit konstanten Kosten verbunden.
Askewchan

1
@askewchan Ich habe die gleiche Idee wie du und deshalb habe ich mal das a = np.array (l) gemacht. Für die Summe2 ohne die Umwandlung in eine Liste und mit einem numpy-Array als Eingabe ist sum2 fünfmal schneller, danke sum1 in meinem Computer im Fall der langen Liste / des langen Arrays.
Mantxu

9

Versuchen Sie Folgendes: Die Akkumulationsfunktion führt zusammen mit dem Operator add die laufende Addition durch.

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

5
Sie müssen nicht übergeben, operator.addda die Standardoperation ohnehin das Hinzufügen ist.
Eugene Yarmash

8

Zuweisungsausdrücke aus PEP 572 (neu in Python 3.8) bieten eine weitere Möglichkeit, dies zu lösen:

time_interval = [4, 6, 12]

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

5

Sie können die kumulative Summenliste in linearer Zeit mit einer einfachen forSchleife berechnen :

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]

Die Standardbibliotheken sind itertools.accumulatemöglicherweise eine schnellere Alternative (da sie in C implementiert sind):

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

Das Ausführen dieses Codes gibt

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

2

In Python3 können Sie Folgendes tun, um die kumulative Summe einer Liste zu ermitteln, wobei das ith-Element die Summe der ersten i + 1-Elemente aus der ursprünglichen Liste ist:

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

ODER Sie können das Listenverständnis verwenden:

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

Ausgabe

[4,10,22]

Das sieht richtig aus, kann aber einen Link zur Dokumentation hinterlassen, ohne den ich nicht upvoten kann.
S Meaden

2

Wenn Sie eine pythonische Methode ohne Numpy in 2.7 verwenden möchten, ist dies meine Vorgehensweise

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

Probieren wir es jetzt aus und testen es gegen alle anderen Implementierungen

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

1

Zunächst möchten Sie eine laufende Liste von Teilsequenzen:

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

Dann rufen Sie einfach sumjede Teilsequenz auf:

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

(Dies ist nicht die effizienteste Methode, da Sie alle Präfixe wiederholt hinzufügen. In den meisten Anwendungsfällen spielt dies jedoch wahrscheinlich keine Rolle, und es ist einfacher zu verstehen, wenn Sie nicht daran denken müssen die laufenden Summen.)

Wenn Sie Python 3.2 oder höher verwenden, können Sie itertools.accumulatedies für Sie tun:

sums = itertools.accumulate(seq)

Und wenn Sie 3.1 oder früher verwenden, können Sie einfach kopieren Sie die „gleichbedeutend mit“ Quelle direkt aus der Dokumentation ( mit Ausnahme des Wechsels next(it)auf it.next()2,5 und früher).


9
Dies läuft in quadratischer Zeit (vielleicht spielt das für das OP keine Rolle, ist aber erwähnenswert).
Chris Taylor

Erstens, wenn N = 3, wen interessiert die quadratische Zeit? Und ich denke nicht, dass es überkompliziert ist. Es sind zwei sehr einfache Schritte, die jeweils einen Iterator in einen anderen umwandeln und die englischsprachige Beschreibung direkt übersetzen. (Die Tatsache, dass er eine ungewöhnliche Art der Definition von Serien verwendet, bei der das Präfix der Länge 0 nicht gezählt wird, macht es etwas komplizierter… aber das ist dem Problem inhärent, und ich dachte, es wäre besser, das in das zu setzen rangeals [1:]am Ende
herumzuhacken

1
Vermutlich besteht das eigentliche Problem des OP nicht darin, die Teilsummen zu erhalten, [4,6,12]da er, wie er in der Frage schrieb, bereits weiß, was das ist!
Chris Taylor

@ChrisTaylor: Er sagte ausdrücklich, dass er bereits weiß, wie man das schreibt, aber "einen einfacheren Weg, es zu schreiben" will.
Abarnert

1

Versuche dies:

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

1

Abhängig von der Länge der Liste und der Leistung kann es viele Antworten geben. Ein sehr einfacher Weg, den ich denken kann, ohne an die Aufführung zu denken, ist folgender:

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]

Dies geschieht durch Verwendung des Listenverständnisses und dies kann ziemlich gut funktionieren. Es ist nur so, dass ich hier viele Male über das Subarray hinzufüge. Sie könnten möglicherweise improvisieren und es einfach machen!

Prost auf dein Bestreben!


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

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

Dies ist etwas schneller als die oben beschriebene Generatormethode von @Ashwini für kleine Listen

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

Bei größeren Listen ist der Generator der richtige Weg. . .

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
Sie planen nur 3 Artikel, versuchen Sie es mit 10 ^ 4 Artikeln.
Ashwini Chaudhary

1
Für größere Listen ist der Generator zwar viel schneller!
Reptilien

-1

Etwas hackig, scheint aber zu funktionieren:

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

Ich dachte, dass die innere Funktion in der Lage sein würde, das yim äußeren lexikalischen Bereich deklarierte zu ändern , aber das hat nicht funktioniert, also spielen wir stattdessen einige böse Hacks mit Strukturmodifikation. Es ist wahrscheinlich eleganter, einen Generator zu verwenden.


-1

Ohne Numpy verwenden zu müssen, können Sie direkt über das Array schleifen und die Summe auf dem Weg akkumulieren. Beispielsweise:

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

Ergebnisse in:

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

-1

Ein reiner Python-Oneliner für die kumulative Summe:

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

Dies ist eine rekursive Version, die von rekursiven kumulativen Summen inspiriert ist . Einige Erklärungen:

  1. Der erste Begriff X[:1]ist eine Liste, die das vorherige Element enthält und fast identisch mit [X[0]](was sich für leere Listen beschweren würde).
  2. Der rekursive cumsumAufruf im zweiten Term verarbeitet das aktuelle Element [1]und die verbleibende Liste, deren Länge um eins reduziert wird.
  3. if X[1:]ist kürzer für if len(X)>1.

Prüfung:

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

cumsum([])
#[]

Und ähnlich für kumulatives Produkt:

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

Prüfung:

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

Hier ist eine weitere unterhaltsame Lösung. Dies nutzt das locals()Diktat eines Verständnisses aus, dh lokale Variablen, die innerhalb des Bereichs des Listenverständnisses erzeugt werden:

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

So locals()sieht das für jede Iteration aus:

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

Die Leistung ist für kleine Listen nicht schlecht:

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

Und fällt offensichtlich für größere Listen flach.

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

Obwohl die Methode hässlich und nicht praktisch ist, macht sie auf jeden Fall Spaß.


-2
lst = [4,6,12]

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

Wenn Sie nach einer effizienteren Lösung suchen (größere Listen?), Könnte ein Generator ein guter Anruf sein (oder nur verwenden, numpywenn Sie sich wirklich für Perf interessieren).

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

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

-3

Dies wäre im Haskell-Stil:

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])
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.