Bir listede kümülatif sayı toplamı nasıl bulunur?


92
time_interval = [4, 6, 12]

[4, 4+6, 4+6+12]Listeyi elde etmek için sayıları gibi özetlemek istiyorum t = [4, 10, 22].

Aşağıdakileri denedim:

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

Yanıtlar:


128

Bunun gibi dizilerle çok sayıda sayısal çalışma yapıyorsanız numpy, kümülatif toplam işleviyle birlikte gelen şunu öneririm cumsum:

import numpy as np

a = [4,6,12]

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

Numpy, bu tür şeyler için genellikle saf python'dan daha hızlıdır, @ Ashwini'ninaccumu :

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

Ama elbette, numpy kullanacağınız tek yer burasıysa, ona bağımlı olmaya değmeyebilir.


3
Bu np.cumsun, dönüştürme süresini hesaba katmak için bir listeyle başlayan bir duruma sahip olmalıdır .
hpaulj

3
İyi bir nokta @hpaulj, a'dan başlayan (veya hedefleyen) olanlar için listtavsiye etmem numpy.
askewchan


3
Yukarıda bahsettiğim gibi katılıyorum. Sizin ve @ hpaulj'unki gibi tepkilerden kaçınmak, cevabımın ilk ve son satırlarında kapsamını sınırlandırmaya
çalışmamın nedenidir

1
@alex: kullanarak timeit, eğer " -nverilmezse, ilmeklerin uygun sayıda toplam süre olan en az 0.2 saniye kadar, 10 birbirini takip eden güçler deneyerek hesaplanır." Fark yaratmasını bekliyorsanız, -n 1000hepsini eşdeğer kılmak için tedarik edebilirsiniz .
askewchan

94

Python 2'de kendi oluşturucu işlevinizi şu şekilde tanımlayabilirsiniz:

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]

Ve Python 3.2+ sürümünde şunları kullanabilirsiniz 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 - Atama İfadeleri (Python 3.8 için bekleniyor) ilginç bir alternatif gösteriyor total = 0; partial_sums = [total := total + v for v in values]. Yine accumulatede daha hızlı olmayı beklerdim .
Steven Rumbalski

3
@StevenRumbalski Adam, şahsen bunun şimdiye kadarki en kötü PEP olduğunu düşünüyorum. Yeterince kötü ...
Ashwini Chaudhary

19

Seyretmek:

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

Çıkacak (beklendiği gibi):

[4, 10, 22]

17
Verimli değil . Tekrar tekrar gerçekleştirmenin toplam gideri c + [c[-1] + x], girdi uzunluğunda toplam çalışma süresi ikinci dereceye kadar ekler.
user2357112 Monica'yı

azaltma, bir kerelik kümülatif toplam için iyidir, ancak kümülatif işlevinize çok sayıda çağrı yapıyorsanız, bir jeneratör, kümülatif_toplam değerlerinizi "önceden işlemek" ve sonraki her çağrı için O (1) 'de bunlara erişmek için yararlı olacaktır.
Scott Skiles

17

Python 3.4 ile en iyi iki cevabın bir karşılaştırmasını yaptım ve buldum itertools.accumulateki numpy.cumsumbirçok koşuldan daha hızlı, genellikle çok daha hızlı. Bununla birlikte, yorumlardan da görebileceğiniz gibi, bu her zaman böyle olmayabilir ve tüm seçenekleri ayrıntılı bir şekilde araştırmak zordur. (İlgilendiğiniz başka karşılaştırma sonuçlarınız varsa, yorum eklemekten veya bu gönderiyi düzenlemekten çekinmeyin.)

Bazı zamanlamalar ...

Kısa listeler accumulateiçin yaklaşık 4 kat daha hızlıdır:

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

Daha uzun listeler accumulateiçin yaklaşık 3 kat daha hızlıdır:

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

Eğer numpy arrayartığını değildir list, accumulatedaha hızlı 2 kere hala:

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

İki fonksiyonların dışında ithalatı koymak ve hala dönerseniz numpy array, accumulateyaklaşık 2 kat daha hızlı hala:

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
Özellikle bilet satın alma ve güvenlik taraması dahil olmak üzere, bir uçağın şehir genelinde seyahat etmekten daha hızlı olmasını beklemezsiniz. Aynı şekilde listbeş maddeden birini işlemek için numpy kullanmazsınız , özellikle arrayde karşılığında bir kabul etmek istemiyorsanız . Söz konusu liste gerçekten bu kadar kısaysa, çalışma süreleri önemsiz olacaktır - bağımlılıklar ve okunaklılık kesinlikle baskın olacaktır. Ancak listönemli uzunlukta tek tip sayısal veri türünün geniş kullanımı aptalca olurdu; bunun için bir uyuşukluk uygun array olur ve genellikle daha hızlıdır.
askewchan

@askewchan Bunu sadece kısa listeler için bulmuyorum ve OP'nin sorusu, bir diziden ziyade çıktı olarak bir liste istiyor. Belki de cevabınızı, her kullanımın uygun olduğu zaman daha net olacak şekilde düzenleyebilirsiniz :)
Chris_Rands

@askewchan Aslında cevabımı çok daha detaylı bir karşılaştırmayla düzenledim. Hiçbir koşulda, bir numpyşeyi gözden kaçırmadığım sürece daha hızlı bulamaz mıyım?
Chris_Rands

2
Aman tanrım, evet gerçekten :) Bir şeyi gözden kaçırdığınızı söyleyemem, ancak girdilerinizi ve çıktılarınızı dikkate almadan karşılaştırmayı tek başına yapmak zor. sum2İşlevinizde çoğu zaman muhtemelen lbir diziye dönüştürülüyor . Zamanlamayı a = np.array(l)ve np.cumsum(a)ayrı ayrı deneyin . Sonra denemek a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. Diğer sayısal süreçleri ( lilk etapta oluşturma veya yükleme gibi) yürüten bir programda , çalışma verileriniz muhtemelen zaten bir dizide olacaktır ve oluşturma sabit bir maliyet olacaktır.
askewchan

1
@askewchan Seninle aynı fikri aldım ve bu nedenle a = np.array (l) için zaman yaptım. Listeye dönüşümü olmayan sum2 için ve girdi olarak bir numpy dizisi ile, uzun liste / dizi olması durumunda bilgisayarımda sum2 5 kat daha hızlıdır.
Mantxu

9

Şunu deneyin: biriktirme işlevi, operatör ekleme ile birlikte çalışan eklemeyi gerçekleştirir.

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

5
operator.addVarsayılan işlem zaten toplama olduğu için geçmenize gerek yok .
Eugene Yarmash

8

PEP 572'deki atama ifadeleri (Python 3.8'de yeni) bunu çözmek için başka bir yol sunar:

time_interval = [4, 6, 12]

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

5

Kümülatif toplam listesini basit bir fordöngü ile doğrusal zamanda hesaplayabilirsiniz :

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]

Standart kitaplıklar itertools.accumulatedaha hızlı bir alternatif olabilir (C'de uygulandığı için):

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

Bu kodu çalıştırmak,

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

2

Python3'te, iöğenin orijinal listeden ilk i + 1 öğelerinin toplamı olduğu bir listenin kümülatif toplamını bulmak için şunları yapabilirsiniz:

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

VEYA liste anlama özelliğini kullanabilirsiniz:

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

Çıktı

[4,10,22]

Bu doğru görünüyor ancak dokümantasyona bir bağlantı bırakabilir, bu olmadan oy veremem.
S Meaden

2

2.7'de uyuşukluk olmadan pitonik bir yol istiyorsanız, bu benim bunu yapma şeklim olurdu

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

şimdi deneyelim ve diğer tüm uygulamalara karşı test edelim

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

Listenin uzunluğuna ve performansına bağlı olarak bunun birçok cevabı olabilir. Performansı düşünmeden düşünebileceğim çok basit bir yol şudur:

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]

Bu, liste anlayışını kullanarak ve bu oldukça iyi çalışabilir, sadece burada alt diziye birçok kez ekliyorum, muhtemelen bu konuda doğaçlama yapabilir ve basitleştirebilirsiniz!

Çabalarınıza şerefe!


1

İlk olarak, alt dizilerden oluşan bir liste istersiniz:

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

Sonra sumher bir alt sırayı arayın :

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

(Bunu yapmanın en etkili yolu bu değildir, çünkü tüm önekleri tekrar tekrar ekliyorsunuz. Ancak bu muhtemelen çoğu kullanım durumu için önemli olmayacaktır ve düşünmek zorunda kalmazsanız anlamak daha kolaydır. Değişen toplamlar.)

Python 3.2 veya daha yenisini kullanıyorsanız, bunu sizin itertools.accumulateiçin yapmak için kullanabilirsiniz:

sums = itertools.accumulate(seq)

Ve 3.1 veya önceki bir sürümünü kullanıyorsanız, "eşdeğer" kaynağını doğrudan dokümanlardan kopyalayabilirsiniz ( 2.5 ve öncesine next(it)geçmek dışında it.next()).


9
Bu, ikinci dereceden zamanda çalışır (belki bu OP için önemli değildir, ama bahsetmeye değer).
Chris Taylor

Birincisi, N = 3 olduğunda, ikinci dereceden zaman kimin umurunda? Ve bunun aşırı karmaşık olduğunu düşünmüyorum. Bu, her biri bir yineleyiciyi diğerine dönüştüren ve doğrudan İngilizce açıklamayı çeviren çok basit iki adımdır. (0 uzunluklu önekin sayılmadığı alışılmadık bir dizi tanımlama yöntemi kullanıyor olması onu biraz daha karmaşık hale getiriyor… ama bu sorunun doğasında var ve bunu diziye koymanın daha iyi olacağını düşündüm. sonunda rangeyaparak etrafından dolaşmaktan [1:]ya da görmezden gelmekten daha çok.)
abarnert

1
Muhtemelen OP'nin asıl sorunu, [4,6,12]soruda yazdığı gibi, o zamandan beri kısmi toplamları elde etmek değil , bunun ne olduğunu zaten biliyor!
Chris Taylor

@ChrisTaylor: Bunu nasıl yazacağını zaten bildiğini ama "yazmanın daha kolay bir yolunu" istediğini açıkça söyledi.
abarnert

1

Bunu dene:

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]

Bu slighlty hızlı jeneratör yöntemine göre daha yukarıda @Ashwini tarafından küçük listeler için

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

Daha büyük listeler için, jeneratör kesinlikle gitmenin yoludur. . .

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
Sadece 3 öğe listesi için zamanlama yapıyorsunuz, 10 ^ 4 öğe için deneyin.
Ashwini Chaudhary

1
Doğru, daha büyük listeler için jeneratör çok daha hızlıdır!
reptilicus

-1

Biraz karmaşık, ancak işe yarıyor gibi görünüyor:

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

İç işlevin ydış sözcük kapsamındaki bildirileni değiştirebileceğini düşünmüştüm , ancak bu işe yaramadı, bu yüzden bunun yerine yapı modifikasyonu ile bazı kötü hackler oynadık. Jeneratör kullanmak muhtemelen daha zariftir.


-1

Numpy'yi kullanmak zorunda kalmadan, doğrudan dizi üzerinde döngü oluşturabilir ve yol boyunca toplamı biriktirebilirsiniz. Örneğin:

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

Sonuçlar:

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

-1

Kümülatif toplam için saf bir python oneliner:

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

Bu, özyinelemeli kümülatif toplamlardan esinlenen özyineli bir versiyondur . Bazı açıklamalar:

  1. İlk terim X[:1], önceki öğeyi içeren bir listedir ve neredeyse aynıdır [X[0]](boş listeler için şikayet eder).
  2. İkinci terimdeki özyinelemeli cumsumçağrı, mevcut öğeyi [1]ve uzunluğu bir azaltılacak olan kalan listeyi işler .
  3. if X[1:]için daha kısadır if len(X)>1.

Ölçek:

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

cumsum([])
#[]

Ve kümülatif ürün için benzer:

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

Ölçek:

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

İşte başka bir eğlenceli çözüm. Bu locals(), bir anlama diktesinden, yani liste anlama kapsamında oluşturulan yerel değişkenlerden yararlanır:

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

locals()Her yinelemenin görünümü aşağıda açıklanmıştır :

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

Küçük listeler için performans kötü değil:

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

Ve tabii ki daha büyük listeler için başarısız oluyor.

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

Yöntem çirkin ve pratik olmasa da kesinlikle eğlencelidir.


-2
lst = [4,6,12]

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

Daha verimli bir çözüm arıyorsanız (daha büyük listeler mi?), Bir jeneratör iyi bir çağrı olabilir (veya numpygerçekten mükemmelliğe önem veriyorsanız kullanın ).

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

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

-3

Bu Haskell tarzı olacaktır:

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])
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.