Bagaimana cara menemukan jumlah kumulatif angka dalam daftar?


92
time_interval = [4, 6, 12]

Saya ingin merangkum angka-angka seperti [4, 4+6, 4+6+12]untuk mendapatkan daftarnya t = [4, 10, 22].

Saya mencoba yang berikut ini:

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

Jawaban:


128

Jika Anda melakukan banyak pekerjaan numerik dengan array seperti ini, saya sarankan numpy, yang dilengkapi dengan fungsi penjumlahan kumulatif cumsum:

import numpy as np

a = [4,6,12]

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

Numpy seringkali lebih cepat daripada python murni untuk hal semacam ini, lihat dibandingkan dengan @ 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

Tapi tentu saja jika itu satu-satunya tempat Anda akan menggunakan numpy, mungkin tidak ada gunanya bergantung padanya.


3
Ini harus memiliki np.cumsunkasus yang dimulai dengan daftar, untuk memperhitungkan waktu konversi.
hpaulj

3
Poin bagus @hpaulj, bagi mereka yang memulai dari (atau membidik) a listsaya tidak akan merekomendasikan numpy.
askewchan

Saya tidak berpikir numpy adalah yang tercepat stackoverflow.com/questions/15889131/…
Chris_Rands

3
Setuju, seperti yang saya sebutkan di atas. Menghindari reaksi seperti Anda dan @ hpaulj adalah alasan saya mencoba membatasi cakupannya di baris pertama dan terakhir jawaban saya: - /
askewchan

1
@alex: Menggunakan timeit, "jika -ntidak diberikan, jumlah pengulangan yang sesuai dihitung dengan mencoba pangkat 10 berturut-turut hingga total waktu setidaknya 0,2 detik." Jika Anda mengharapkannya untuk membuat perbedaan, Anda dapat menyediakan -n 1000untuk membuat semuanya setara.
askewchan

94

Di Python 2 Anda dapat menentukan fungsi generator Anda sendiri seperti ini:

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]

Dan dengan Python 3.2+ Anda dapat menggunakan 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 (diharapkan untuk Python 3.8) menunjukkan alternatif yang menarik total = 0; partial_sums = [total := total + v for v in values]. Saya masih berharap accumulatebisa lebih cepat.
Steven Rumbalski

3
@StevenRumbalski Man, menurut saya itu PEP terburuk yang pernah ada. Cukup buruk ...
Ashwini Chaudhary

19

Melihat:

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

Akan menghasilkan (seperti yang diharapkan):

[4, 10, 22]

17
Tidak efisien. Total biaya untuk melakukan c + [c[-1] + x]berulang-ulang menambahkan hingga total kuadrat waktu proses dalam panjang input.
user2357112 mendukung Monica

reduce baik untuk jumlah kumulatif satu kali, tetapi jika Anda melakukan banyak panggilan ke fungsi cumsum, generator akan berguna untuk "memproses sebelumnya" nilai kumulatif_sum Anda dan mengaksesnya dalam O (1) untuk setiap panggilan berikutnya.
Scott Skiles

17

Saya melakukan benchmark dari dua jawaban teratas dengan Python 3.4 dan saya menemukan itertools.accumulatelebih cepat daripada numpy.cumsumdalam banyak keadaan, seringkali jauh lebih cepat. Namun, seperti yang Anda lihat dari komentar, mungkin tidak selalu demikian, dan sulit untuk menjelajahi semua opsi secara mendalam. (Jangan ragu untuk menambahkan komentar atau mengedit posting ini jika Anda memiliki hasil benchmark lebih lanjut yang menarik.)

Beberapa pengaturan waktu ...

Untuk daftar pendek accumulatesekitar 4 kali lebih cepat:

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

Untuk daftar yang lebih panjang accumulatesekitar 3 kali lebih cepat:

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

Jika numpy arraytidak ditransmisikan list, accumulatemasih sekitar 2 kali lebih cepat:

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

Jika Anda meletakkan impor di luar kedua fungsi dan masih mengembalikan a numpy array, accumulatemasih hampir 2 kali lebih cepat:

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
Anda tidak akan mengharapkan pesawat terbang lebih cepat daripada kereta api untuk melintasi kota, terutama termasuk pembelian tiket dan pemeriksaan keamanan. Demikian pula Anda tidak akan menggunakan numpy untuk memproses salah satu listdari lima item, terutama jika Anda tidak ingin menerima sebagai arraygantinya. Jika daftar yang dipermasalahkan sangat pendek, maka waktu berjalannya tidak akan berpengaruh - ketergantungan dan keterbacaan pasti akan mendominasi. Tetapi penggunaan yang luas dari listtipe data numerik seragam dengan panjang yang signifikan akan menjadi konyol; untuk itu, numpy array akan cocok, dan biasanya lebih cepat.
askewchan

@askewchan baik saya tidak hanya menemukan ini untuk daftar pendek dan pertanyaan OP meminta daftar sebagai output daripada array numpy. Mungkin Anda dapat mengedit jawaban Anda agar lebih jelas kapan setiap penggunaan sesuai :)
Chris_Rands

@askewchan Sebenarnya saya telah mengedit jawaban saya dengan perbandingan yang jauh lebih rinci. Dalam keadaan apa pun, apakah saya merasa numpylebih cepat, kecuali saya telah melewatkan sesuatu?
Chris_Rands

2
Ya ampun, ya memang :) Saya tidak akan mengatakan Anda telah melewatkan sesuatu, tetapi perbandingannya sulit dilakukan secara terpisah tanpa mempertimbangkan input dan output Anda. Sebagian besar waktu dalam sum2fungsi Anda mungkin diubah lmenjadi array. Coba waktu a = np.array(l)dan np.cumsum(a)secara terpisah. Kemudian mencoba a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. Dalam program yang melakukan proses numerik lain (seperti pembuatan atau pemuatan ldi tempat pertama) data kerja Anda mungkin sudah ada dalam array, dan pembuatannya akan menjadi biaya konstan.
askewchan

1
@askewchan Saya mendapat ide yang sama seperti Anda dan oleh karena itu saya melakukan waktu a = np.array (l). Untuk sum2 tanpa transformasi ke list, dan dengan array numpy sebagai input, sum2 adalah 5 kali lebih cepat terima sum1 di komputer saya dalam kasus daftar / array panjang.
Mantxu

9

Coba ini: fungsi akumulasi, bersama dengan penambahan operator melakukan penambahan yang berjalan.

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

5
Anda tidak perlu meneruskan operator.addkarena operasi default adalah penambahan.
Eugene Yarmash

8

Ekspresi tugas dari PEP 572 (baru di Python 3.8) menawarkan cara lain untuk menyelesaikan ini:

time_interval = [4, 6, 12]

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

5

Anda dapat menghitung daftar jumlah kumulatif dalam waktu linier dengan forloop sederhana :

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]

Pustaka standar itertools.accumulatemungkin merupakan alternatif yang lebih cepat (karena diimplementasikan di 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

Menjalankan kode ini memberi

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

2

Di Python3, Untuk mencari jumlah kumulatif dari daftar di mana ielemen th adalah jumlah dari elemen i + 1 pertama dari daftar asli, Anda dapat melakukan:

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

ATAU Anda dapat menggunakan pemahaman daftar:

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

Keluaran

[4,10,22]

Ini terlihat benar tetapi dapat menjatuhkan tautan ke dokumentasi, tanpanya saya tidak dapat memberi suara positif.
S Meaden

2

Jika Anda menginginkan cara pythonic tanpa numpy bekerja di 2.7 ini akan menjadi cara saya melakukannya

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

sekarang mari kita coba dan mengujinya terhadap semua implementasi lainnya

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

Mungkin ada banyak jawaban untuk ini tergantung pada panjang daftar dan kinerjanya. Satu cara yang sangat sederhana yang dapat saya pikirkan tanpa memikirkan pertunjukannya adalah ini:

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]

Ini dengan menggunakan pemahaman daftar dan ini mungkin bekerja cukup baik hanya saja di sini saya menambahkan subarray berkali-kali, Anda mungkin bisa berimprovisasi pada ini dan membuatnya sederhana!

Bersulang untuk usaha Anda!


1

Pertama, Anda ingin menjalankan daftar selanjutnya:

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

Kemudian Anda cukup memanggil sumsetiap berikutnya:

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

(Ini bukan cara yang paling efisien untuk melakukannya, karena Anda menambahkan semua awalan berulang kali. Tapi itu mungkin tidak masalah untuk sebagian besar kasus penggunaan, dan lebih mudah untuk dipahami jika Anda tidak perlu memikirkan total lari.)

Jika Anda menggunakan Python 3.2 atau yang lebih baru, Anda dapat menggunakannya itertools.accumulateuntuk Anda:

sums = itertools.accumulate(seq)

Dan jika Anda menggunakan 3.1 atau sebelumnya, Anda dapat langsung menyalin sumber "setara dengan" dari dokumen (kecuali untuk mengubah next(it)ke it.next()untuk 2.5 dan sebelumnya).


9
Ini berjalan dalam waktu kuadrat (mungkin itu tidak masalah untuk OP, tapi perlu disebutkan).
Chris Taylor

Pertama, ketika N = 3, siapa yang peduli dengan waktu kuadrat? Dan menurut saya itu tidak terlalu rumit. Itu dua langkah yang sangat sederhana, masing-masing mengubah satu iterator menjadi yang lain, langsung menerjemahkan deskripsi bahasa Inggris. (Fakta bahwa dia menggunakan cara yang tidak biasa untuk menentukan deret, di mana awalan panjang 0 tidak dihitung, memang membuatnya sedikit lebih rumit ... tapi itu melekat dalam masalah, dan saya pikir lebih baik meletakkannya di rangedaripada [1:]
meretasnya

1
Agaknya masalah OP yang sebenarnya bukanlah mendapatkan sebagian [4,6,12]karena, seperti yang dia tulis di pertanyaan, dia sudah tahu apa itu!
Chris Taylor

@ChrisTaylor: Dia secara eksplisit mengatakan bahwa dia sudah tahu bagaimana menulis ini, tetapi menginginkan "cara yang lebih mudah untuk menulisnya".
abarnert

1

Coba ini:

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]

Ini adalah slighlty lebih cepat daripada metode pembangkit di atas dengan @Ashwini untuk daftar kecil

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

Untuk daftar yang lebih besar, generator adalah cara yang pasti. . .

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
Anda waktu untuk hanya 3 daftar item, coba untuk 10 ^ 4 item.
Ashwini Chaudhary

1
Benar, untuk daftar yang lebih besar, generator jauh lebih cepat!
Reptilicus

-1

Agak hacky, tetapi sepertinya berhasil:

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

Saya memang berpikir bahwa fungsi bagian dalam akan dapat memodifikasi yang ydideklarasikan di lingkup leksikal luar, tetapi itu tidak berhasil, jadi kami memainkan beberapa peretasan jahat dengan modifikasi struktur sebagai gantinya. Mungkin lebih elegan menggunakan generator.


-1

Tanpa harus menggunakan Numpy, Anda dapat melakukan loop langsung pada array dan mengakumulasikan jumlahnya di sepanjang jalan. Sebagai contoh:

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

Hasil dalam:

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

-1

Oneliner python murni untuk jumlah kumulatif:

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

Ini adalah versi rekursif yang terinspirasi oleh jumlah kumulatif rekursif . Beberapa penjelasannya:

  1. Istilah pertama X[:1]adalah daftar yang berisi elemen sebelumnya dan hampir sama [X[0]](yang akan dikeluhkan untuk daftar kosong).
  2. cumsumPanggilan rekursif dalam istilah kedua memproses elemen saat ini [1]dan daftar yang tersisa yang panjangnya akan dikurangi satu.
  3. if X[1:]lebih pendek untuk if len(X)>1.

Uji:

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

cumsum([])
#[]

Dan simular untuk produk kumulatif:

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

Uji:

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

Inilah solusi menyenangkan lainnya. Ini mengambil keuntungan dari locals()diktik pemahaman, yaitu variabel lokal yang dihasilkan di dalam cakupan pemahaman daftar:

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

Berikut locals()tampilan untuk setiap iterasi:

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

Performa tidak buruk untuk daftar kecil:

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

Dan jelas tidak cocok untuk daftar yang lebih besar.

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

Meski metodenya jelek dan tidak praktis, itu pasti menyenangkan.


-2
lst = [4,6,12]

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

Jika Anda mencari solusi yang lebih efisien (daftar yang lebih besar?), Generator bisa menjadi pilihan yang baik (atau hanya digunakan numpyjika Anda benar-benar peduli dengan kinerja).

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

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

-3

Ini akan menjadi gaya 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])
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.