Comment trouver la somme cumulée des nombres dans une liste?


92
time_interval = [4, 6, 12]

Je veux résumer les nombres comme [4, 4+6, 4+6+12]pour obtenir la liste t = [4, 10, 22].

J'ai essayé ce qui suit:

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

Réponses:


128

Si vous faites beaucoup de travail numérique avec des tableaux comme celui-ci, je suggère numpy, qui est livré avec une fonction de somme cumulative cumsum:

import numpy as np

a = [4,6,12]

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

Numpy est souvent plus rapide que le python pur pour ce genre de chose, voir en comparaison avec @ 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

Mais bien sûr, si c'est le seul endroit où vous utiliserez numpy, cela ne vaut peut-être pas la peine d'en dépendre.


3
Cela devrait avoir un np.cumsuncas qui commence par une liste, pour prendre en compte le temps de conversion.
hpaulj

3
Bon point @hpaulj, pour ceux qui partent (ou visent) un listje ne recommanderais pas numpy.
askewchan

Je ne pense pas que numpy soit le plus rapide stackoverflow.com/questions/15889131/…
Chris_Rands

3
D'accord, comme je l'ai mentionné ci-dessus. Éviter les réactions comme la vôtre et celle de @ hpaulj est pourquoi j'ai essayé de limiter sa portée dans les toutes premières et dernières lignes de ma réponse: - /
askewchan

1
@alex: En utilisant timeit, "s'il -nn'est pas donné, un nombre approprié de boucles est calculé en essayant des puissances successives de 10 jusqu'à ce que le temps total soit d'au moins 0,2 seconde." Si vous vous attendez à ce que cela fasse une différence, vous pouvez fournir -n 1000pour les rendre tous équivalents.
askewchan

94

Dans Python 2, vous pouvez définir votre propre fonction de générateur comme ceci:

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]

Et dans Python 3.2+, vous pouvez utiliser 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 - Expressions d'affectation (attendues pour Python 3.8) montre une alternative intéressante total = 0; partial_sums = [total := total + v for v in values]. Je m'attendrais toujours accumulateà être plus rapide.
Steven Rumbalski

3
@StevenRumbalski Man, je pense personnellement que c'est le pire PEP de tous les temps. Mauvais assez ...
Ashwini Chaudhary

19

Voir:

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

Sortira (comme prévu):

[4, 10, 22]

17
Pas efficace. Le coût total de l'exécution c + [c[-1] + x]répétée s'ajoute à un quadratique d'exécution total dans la longueur d'entrée.
user2357112 soutient Monica

Réduire est bon pour une somme cumulative unique, mais si vous faites beaucoup d'appels à votre fonction cumsum, un générateur sera utile pour "prétraiter" vos valeurs cumulative_sum et y accéder dans O (1) pour chaque appel suivant.
Scott Skiles

17

J'ai fait un benchmark des deux premières réponses avec Python 3.4 et j'ai trouvé que itertools.accumulatec'est plus rapide que numpy.cumsumdans de nombreuses circonstances, souvent beaucoup plus rapide. Cependant, comme vous pouvez le voir dans les commentaires, ce n'est peut-être pas toujours le cas et il est difficile d'explorer de manière exhaustive toutes les options. (N'hésitez pas à ajouter un commentaire ou à modifier cet article si vous avez d'autres résultats de référence intéressants.)

Quelques horaires ...

Pour les listes courtes, accumulatec'est environ 4 fois plus rapide:

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

Pour des listes plus longues, accumulatec'est environ 3 fois plus rapide:

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

Si le numpy arrayn'est pas casté vers list, accumulateest toujours environ 2 fois plus rapide:

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

Si vous mettez les importations en dehors des deux fonctions et retournez toujours a numpy array, accumulatec'est toujours presque 2 fois plus rapide:

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
Vous ne vous attendriez pas à ce qu'un avion soit plus rapide que le train pour traverser la ville, en particulier l'achat de billets et le contrôle de sécurité. De même, vous n'utiliserez pas numpy pour traiter un listdes cinq éléments, surtout si vous ne souhaitez pas accepter un arrayen retour. Si la liste en question est vraiment si courte, alors leur durée d'exécution serait sans conséquence - les dépendances et la lisibilité domineraient sûrement. Mais une large utilisation d'un listtype de données numériques uniformes de longueur significative serait ridicule; pour cela, un numpy array serait approprié, et généralement plus rapide.
askewchan

@askewchan eh bien, je ne trouve pas cela uniquement pour les listes courtes et la question de l'OP demande une liste en sortie plutôt qu'un tableau numpy. Peut-être pouvez-vous modifier votre réponse pour être plus claire sur le moment où chaque utilisation est appropriée :)
Chris_Rands

@askewchan En fait, j'ai édité ma réponse avec une comparaison beaucoup plus détaillée. En aucun cas, est-ce que je trouve numpyêtre plus rapide, sauf si j'ai oublié quelque chose?
Chris_Rands

2
Oh mon Dieu, oui en effet :) Je ne dirais pas que vous avez oublié quelque chose, mais la comparaison est difficile à faire isolément sans tenir compte de vos entrées et sorties. La plupart du temps, votre sum2fonction consiste probablement à convertir len tableau. Essayez de chronométrer a = np.array(l)et np.cumsum(a)séparément. Ensuite , essayez a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. Dans un programme effectuant d'autres processus numériques (comme la création ou le chargement len premier lieu), vos données de travail seraient probablement déjà dans un tableau, et la création serait un coût constant.
askewchan

1
@askewchan J'ai eu la même idée que vous et j'ai donc chronométré le a = np.array (l). Pour la somme2 sans la transformation en liste, et avec un tableau numpy en entrée, sum2 est 5 fois plus rapide grâce à sum1 dans mon ordinateur en cas de liste / tableau longue.
Mantxu

9

Essayez ceci: la fonction d'accumulation, avec l'opérateur add, effectue l'addition en cours.

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

5
Vous n'avez pas besoin de passer operator.addcar l'opération par défaut est l'addition de toute façon.
Eugene Yarmash

8

Les expressions d'affectation de PEP 572 (nouveau dans Python 3.8) offrent encore un autre moyen de résoudre ce problème:

time_interval = [4, 6, 12]

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

5

Vous pouvez calculer la liste de somme cumulée en temps linéaire avec une simple forboucle:

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 bibliothèque standard itertools.accumulatepeut être une alternative plus rapide (puisqu'elle est implémentée en 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'exécution de ce code donne

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

2

En Python3, pour trouver la somme cumulée d'une liste où le ième élément est la somme des i + 1 premiers éléments de la liste d'origine, vous pouvez faire:

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

OU vous pouvez utiliser la compréhension de liste:

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

Production

[4,10,22]

Cela semble correct mais peut supprimer un lien vers la documentation, sans cela, je ne peux pas voter pour.
S Meaden le

2

Si vous voulez une manière pythonique sans travailler numpy en 2.7, ce serait ma façon de le faire

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

maintenant essayons-le et testons-le contre toutes les autres implémentations

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

Il peut y avoir plusieurs réponses à cela en fonction de la longueur de la liste et de la performance. Une manière très simple à laquelle je peux penser sans penser à la performance est la suivante:

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]

C'est en utilisant la compréhension de liste et cela peut fonctionner assez bien, c'est juste qu'ici j'ajoute plusieurs fois sur le sous-tableau, vous pourriez éventuellement improviser là-dessus et le rendre simple!

Bravo à votre effort!


1

Tout d'abord, vous voulez une liste courante de sous-séquences:

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

Ensuite, vous appelez simplement sumchaque sous-séquence:

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

(Ce n'est pas le moyen le plus efficace de le faire, car vous ajoutez tous les préfixes à plusieurs reprises. Mais cela n'aura probablement pas d'importance dans la plupart des cas d'utilisation, et c'est plus facile à comprendre si vous n'avez pas à penser à les totaux cumulés.)

Si vous utilisez Python 3.2 ou plus récent, vous pouvez le itertools.accumulatefaire pour vous:

sums = itertools.accumulate(seq)

Et si vous utilisez la version 3.1 ou antérieure, vous pouvez simplement copier la source «équivalent à» directement à partir de la documentation (sauf pour passer next(it)à la version it.next()2.5 et antérieure).


9
Cela fonctionne en temps quadratique (peut-être que cela n'a pas d'importance pour l'OP, mais cela vaut la peine d'être mentionné).
Chris Taylor

Premièrement, quand N = 3, qui se soucie du temps quadratique? Et je ne pense pas que ce soit trop compliqué. Il s'agit de deux étapes très simples, chacune transformant un itérateur en un autre, traduisant directement la description en anglais. (Le fait qu'il utilise une manière inhabituelle de définir une série, où le préfixe de longueur 0 n'est pas compté, le rend un peu plus compliqué ... mais c'est inhérent au problème, et j'ai pensé qu'il valait mieux le mettre dans le rangeque de le contourner en le faisant [1:]à la fin, ou de l'ignorer.)
abarnert

1
Vraisemblablement, le problème réel du PO n'est pas d'obtenir les sommes partielles [4,6,12]puisque, comme il l'a écrit dans la question, il sait déjà ce que c'est!
Chris Taylor

@ChrisTaylor: Il a dit explicitement qu'il savait déjà comment écrire ceci, mais qu'il voulait "un moyen plus simple de l'écrire".
abarnert

1

Essaye ça:

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]

C'est légèrement plus rapide que la méthode du générateur ci-dessus par @Ashwini pour les petites listes

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

Pour les listes plus volumineuses, le générateur est la solution. . .

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
Vous chronométrez pour seulement 3 listes d'articles, essayez pour 10 ^ 4 articles.
Ashwini Chaudhary

1
Certes, pour les listes plus grandes, le générateur est beaucoup plus rapide!
reptilicus

-1

Un peu hacky, mais semble fonctionner:

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

Je pensais que la fonction interne serait capable de modifier le ydéclaré dans la portée lexicale externe, mais cela n'a pas fonctionné, nous avons donc joué à de vilains hacks avec une modification de structure à la place. Il est probablement plus élégant d'utiliser un générateur.


-1

Sans avoir à utiliser Numpy, vous pouvez effectuer une boucle directement sur le tableau et accumuler la somme en cours de route. Par exemple:

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

Résulte en:

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

-1

Un pur python oneliner pour la somme cumulée:

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

Il s'agit d'une version récursive inspirée des sommes cumulatives récursives . Quelques explications:

  1. Le premier terme X[:1]est une liste contenant l'élément précédent et est presque le même que [X[0]](ce qui se plaindrait pour les listes vides).
  2. L' cumsumappel récursif du second terme traite l'élément courant [1]et la liste restante dont la longueur sera réduite de un.
  3. if X[1:]est plus court pour if len(X)>1.

Tester:

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

cumsum([])
#[]

Et simulaire pour le produit cumulatif:

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

Tester:

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

Voici une autre solution amusante. Cela tire parti du locals()dict d'une compréhension, c'est-à-dire des variables locales générées à l'intérieur de la portée de compréhension de liste:

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

Voici à quoi locals()ressemble chaque itération:

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

Les performances ne sont pas terribles pour les petites listes:

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

Et tombe évidemment à plat pour les listes plus grandes.

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

Même si la méthode est moche et peu pratique, elle est certainement amusante.


-2
lst = [4,6,12]

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

Si vous cherchez une solution plus efficace (des listes plus grandes?), Un générateur pourrait être un bon choix (ou simplement utiliser numpysi vous vous souciez vraiment de la performance).

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

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

-3

Ce serait à la 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])
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.