Come trovare la somma cumulativa dei numeri in un elenco?


92
time_interval = [4, 6, 12]

Voglio riassumere i numeri come [4, 4+6, 4+6+12]per ottenere l'elenco t = [4, 10, 22].

Ho provato quanto segue:

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

Risposte:


128

Se stai facendo molto lavoro numerico con array come questo, suggerirei numpy, che viene fornito con una funzione di somma cumulativa cumsum:

import numpy as np

a = [4,6,12]

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

Numpy è spesso più veloce del puro python per questo genere di cose, vedi in confronto a @ 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

Ma ovviamente se è l'unico posto in cui utilizzerai numpy, potrebbe non valere la pena di dipendere da esso.


3
Questo dovrebbe avere un np.cumsuncaso che inizia con un elenco, per tenere conto del tempo di conversione.
hpaulj

3
Buon punto @hpaulj, per coloro che iniziano (o mirano a) listnon lo consiglierei numpy.
askewchan

Non credo NumPy è più veloce stackoverflow.com/questions/15889131/...
Chris_Rands

3
D'accordo, come ho detto sopra. Evitare reazioni come la tua e quella di @ hpaulj è il motivo per cui ho cercato di limitarne l'ambito nelle primissime e ultime righe della mia risposta: - /
askewchan

1
@alex: Utilizzando timeit, "se -nnon viene fornito, viene calcolato un numero adeguato di cicli provando potenze successive di 10 fino a quando il tempo totale è di almeno 0,2 secondi." Se ti aspetti che faccia la differenza, puoi fornire -n 1000per renderli tutti equivalenti.
askewchan

94

In Python 2 puoi definire la tua funzione di generatore in questo modo:

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]

E in Python 3.2+ puoi usare 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 - Assignment Expressions (previsto per Python 3.8) mostra un'alternativa interessante total = 0; partial_sums = [total := total + v for v in values]. Mi aspetterei comunque accumulatedi essere più veloce.
Steven Rumbalski

3
@ StevenRumbalski Man, personalmente penso che sia il peggior PEP di sempre. Abbastanza brutto ...
Ashwini Chaudhary

19

Ecco:

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

Produrrà (come previsto):

[4, 10, 22]

17
Non efficiente. La spesa totale per l'esecuzione c + [c[-1] + x]ripetuta si somma a un tempo di esecuzione totale quadratico nella lunghezza di input.
user2357112 supporta Monica

reduce è buono per una somma cumulativa una tantum, ma se stai facendo molte chiamate alla tua funzione cumsum un generatore sarà utile per "preelaborare" i tuoi valori cumulative_sum e accedervi in ​​O (1) per ogni chiamata successiva.
Scott Skiles

17

Ho fatto un benchmark delle prime due risposte con Python 3.4 e ho scoperto che itertools.accumulateè più veloce che numpy.cumsumin molte circostanze, spesso molto più veloce. Tuttavia, come puoi vedere dai commenti, potrebbe non essere sempre così ed è difficile esplorare in modo esaustivo tutte le opzioni. (Sentiti libero di aggiungere un commento o modificare questo post se hai ulteriori risultati di benchmark di interesse.)

Alcuni tempi ...

Per gli elenchi brevi accumulateè circa 4 volte più veloce:

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

Per elenchi più lunghi accumulateè circa 3 volte più veloce:

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

Se numpy arraynon viene lanciato list, accumulateè ancora circa 2 volte più veloce:

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

Se metti le importazioni al di fuori delle due funzioni e restituisci ancora a numpy array, accumulateè ancora quasi 2 volte più veloce:

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
Non ti aspetteresti che un aereo sia più veloce del treno per viaggiare attraverso la città, soprattutto se si tratta di acquistare biglietti e controlli di sicurezza. Allo stesso modo non useresti numpy per elaborare uno listdei cinque elementi, soprattutto se non sei disposto ad accettarne uno arrayin cambio. Se l'elenco in questione è davvero così breve, il loro tempo di esecuzione sarebbe irrilevante : le dipendenze e la leggibilità avrebbero sicuramente dominato. Ma l'uso diffuso di un listtipo di dati numerici uniformi di lunghezza significativa sarebbe sciocco; per questo, un numpy array sarebbe appropriato e di solito più veloce.
askewchan

@askewchan beh, non lo trovo solo per elenchi brevi e la domanda dell'OP richiede un elenco come output piuttosto che un array numpy. Forse puoi modificare la tua risposta per essere più chiara su quando ogni uso è appropriato :)
Chris_Rands

@askewchan In effetti ho modificato la mia risposta con un confronto molto più dettagliato. In nessuna circostanza trovo numpydi essere più veloce, a meno che non abbia trascurato qualcosa?
Chris_Rands

2
Oh mio, sì davvero :) Non direi che hai trascurato qualcosa, ma il confronto è difficile da fare isolatamente senza considerare i tuoi input e output. La maggior parte delle volte nella tua sum2funzione è probabilmente la conversione lin un array. Prova il tempismo a = np.array(l)e np.cumsum(a)separatamente. Quindi provare a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. In un programma che esegue altri processi numerici (come la creazione o il caricamento di lin primo luogo) i dati di lavoro probabilmente sarebbero già in un array e la creazione sarebbe un costo costante.
askewchan

1
@askewchan ho avuto la tua stessa idea e quindi ho cronometrato a = np.array (l). Per sum2 senza la trasformazione in elenco e con un array numpy come input, sum2 è 5 volte più veloce grazie a sum1 nel mio computer in caso di lungo elenco / array.
Mantxu

9

Prova questo: la funzione accumulate, insieme all'operatore add, esegue l'addizione in corso.

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

5
Non è necessario passare in operator.addquanto l'operazione predefinita è comunque l'aggiunta.
Eugene Yarmash

8

Le espressioni di assegnazione da PEP 572 (novità in Python 3.8) offrono ancora un altro modo per risolvere questo problema:

time_interval = [4, 6, 12]

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

5

È possibile calcolare l'elenco della somma cumulativa in tempo lineare con un semplice forciclo:

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]

La libreria standard itertools.accumulatepuò essere un'alternativa più veloce (poiché è implementata in 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

L'esecuzione di questo codice dà

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

2

In Python3, per trovare la somma cumulativa di una lista in cui il ith elemento è la somma dei primi i + 1 elementi dalla lista originale, puoi fare:

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

OPPURE puoi usare la comprensione dell'elenco:

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

Produzione

[4,10,22]

Sembra corretto ma può rilasciare un collegamento alla documentazione, senza di esso non posso votare.
S Meaden,

2

Se vuoi un modo pitonico senza che numpy funzioni in 2.7, questo sarebbe il mio modo di farlo

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

ora proviamolo e testiamolo contro tutte le altre implementazioni

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

Potrebbero esserci molte risposte per questo a seconda della lunghezza dell'elenco e delle prestazioni. Un modo molto semplice in cui posso pensare senza pensare alla performance è questo:

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]

Questo è usando la comprensione delle liste e questo può funzionare abbastanza bene è solo che qui sto aggiungendo molte volte sopra il sottoarray, potresti improvvisare su questo e renderlo semplice!

Saluti al tuo impegno!


1

Innanzitutto, vuoi un elenco in esecuzione di sottosequenze:

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

Quindi basta chiamare sumogni sottosequenza:

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

(Questo non è il modo più efficiente per farlo, perché stai aggiungendo ripetutamente tutti i prefissi. Ma probabilmente non avrà importanza per la maggior parte dei casi d'uso ed è più facile da capire se non devi pensare a i totali parziali.)

Se stai usando Python 3.2 o più recente, puoi usarlo itertools.accumulateper farlo per te:

sums = itertools.accumulate(seq)

E se stai usando 3.1 o versioni precedenti, puoi semplicemente copiare il sorgente "equivalente a" direttamente dai documenti (eccetto per il passaggio next(it)a it.next()2.5 e precedenti).


9
Funziona in tempo quadratico (forse non importa per l'OP, ma vale la pena menzionarlo).
Chris Taylor

Primo, quando N = 3, a chi importa del tempo quadratico? E non credo che sia troppo complicato. Sono due passaggi molto semplici, ognuno dei quali trasforma un iteratore in un altro, traducendo direttamente la descrizione in lingua inglese. (Il fatto che stia usando un modo insolito per definire le serie, in cui il prefisso di lunghezza 0 non viene contato, lo rende un po 'più complicato ... ma è inerente al problema, e ho pensato che fosse meglio metterlo nel rangepiuttosto che [1:]
aggirarlo

1
Presumibilmente il vero problema dell'OP non è quello di ottenere le somme parziali [4,6,12]poiché, come ha scritto nella domanda, sa già di cosa si tratta!
Chris Taylor

@ChrisTaylor: Ha detto esplicitamente che sa già come scrivere questo, ma vuole "un modo più semplice per scriverlo".
abarnert

1

Prova questo:

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]

Questo è leggermente più veloce del metodo generatore sopra di @Ashwini per piccoli elenchi

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

Per elenchi più grandi, il generatore è la strada da percorrere di sicuro. . .

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
Stai cronometrando solo per un elenco di 3 elementi, prova per 10 ^ 4 elementi.
Ashwini Chaudhary

1
È vero, per elenchi più grandi il generatore è molto più veloce!
reptilicus

-1

Un po 'hacky, ma sembra funzionare:

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

Pensavo che la funzione interna sarebbe stata in grado di modificare il ydichiarato nello scope lessicale esterno, ma non ha funzionato, quindi suoniamo alcuni brutti hack con la modifica della struttura. Probabilmente è più elegante usare un generatore.


-1

Senza dover usare Numpy, puoi eseguire il ciclo direttamente sull'array e accumulare la somma lungo il percorso. Per esempio:

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

Risultati in:

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

-1

Un oneliner in puro pitone per somma cumulativa:

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

Questa è una versione ricorsiva ispirata a somme cumulative ricorsive . Alcune spiegazioni:

  1. Il primo termine X[:1]è un elenco contenente l'elemento precedente ed è quasi lo stesso di [X[0]](che si lamenterebbe per elenchi vuoti).
  2. La cumsumchiamata ricorsiva nel secondo termine elabora l'elemento corrente [1]e l'elenco rimanente la cui lunghezza sarà ridotta di uno.
  3. if X[1:]è più breve per if len(X)>1.

Test:

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

cumsum([])
#[]

E simile per il prodotto cumulativo:

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

Ecco un'altra divertente soluzione. Questo sfrutta il locals()dict di una comprensione, cioè variabili locali generate all'interno dell'ambito di comprensione della lista:

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

Ecco cosa locals()appare per ogni iterazione:

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

Le prestazioni non sono terribili per piccoli elenchi:

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

E ovviamente cade piatto per elenchi più grandi.

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

Anche se il metodo è brutto e non pratico, è sicuramente divertente.


-2
lst = [4,6,12]

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

Se stai cercando una soluzione più efficiente (elenchi più grandi?) Un generatore potrebbe essere una buona scelta (o semplicemente usarlo numpyse ti interessa davvero la perf).

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

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

-3

Questo sarebbe in stile Haskell:

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])
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.