Làm thế nào để tìm tổng tích lũy của các số trong một danh sách?


92
time_interval = [4, 6, 12]

Tôi muốn tổng hợp các số như [4, 4+6, 4+6+12]để có được danh sách t = [4, 10, 22].

Tôi đã thử những cách sau:

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

Câu trả lời:


128

Nếu bạn đang làm nhiều công việc về số với các mảng như thế này, tôi khuyên bạn numpynên đi kèm với một hàm tổng tích lũy cumsum:

import numpy as np

a = [4,6,12]

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

Numpy thường nhanh hơn trăn thuần cho loại này, hãy xem so với của @ 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

Nhưng tất nhiên nếu đó là nơi duy nhất bạn sẽ sử dụng numpy, thì việc phụ thuộc vào nó có thể không đáng.


3
Điều này nên có một np.cumsuntrường hợp bắt đầu bằng một danh sách, để tính đến thời gian chuyển đổi.
hpaulj

3
Điểm tốt @hpaulj, đối với những người bắt đầu từ (hoặc nhắm đến) a, listtôi sẽ không giới thiệu numpy.
askewchan

Tôi không nghĩ rằng numpy là nhanh nhất stackoverflow.com/questions/15889131/…
Chris_Rands,

3
Đồng ý, như tôi đã đề cập ở trên. Tránh phản ứng như của bạn và @ hpaulj được lý do tại sao tôi đã cố gắng để hạn chế phạm vi của nó trong dòng đầu tiên và cuối cùng của câu trả lời của tôi: - /
askewchan

1
@alex: Sử dụng timeit, "nếu -nkhông được cung cấp, số vòng lặp phù hợp được tính bằng cách thử các lũy thừa liên tiếp của 10 cho đến khi tổng thời gian ít nhất là 0,2 giây." Nếu bạn mong đợi nó tạo ra sự khác biệt, bạn có thể cung cấp -n 1000để làm cho tất cả chúng tương đương nhau.
askewchan

94

Trong Python 2, bạn có thể xác định hàm trình tạo của riêng mình như sau:

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]

Và trong Python 3.2+, bạn có thể sử dụng 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 - Biểu thức gán (dự kiến ​​cho Python 3.8) cho thấy một sự thay thế thú vị total = 0; partial_sums = [total := total + v for v in values]. Tôi vẫn mong đợi accumulatesẽ nhanh hơn.
Steven Rumbalski

3
@StevenRumbalski Man, cá nhân tôi nghĩ đó là PEP tồi tệ nhất từ ​​trước đến nay. Đủ tệ ...
Ashwini Chaudhary

19

Hãy chứng kiến:

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

Sẽ xuất (như mong đợi):

[4, 10, 22]

17
Không hiệu quả. Tổng chi phí thực hiện c + [c[-1] + x]lặp đi lặp lại cộng lại tổng thời gian chạy bậc hai trong độ dài đầu vào.
user2357112 hỗ trợ Monica

Giảm là tốt cho tổng tích lũy một lần, nhưng nếu bạn đang thực hiện nhiều lệnh gọi đến hàm cumsum của mình, trình tạo sẽ hữu ích để "xử lý trước" các giá trị lũy tích_sum của bạn và truy cập chúng trong O (1) cho mỗi lần gọi tiếp theo.
Scott Skiles

17

Tôi đã làm điểm chuẩn của hai câu trả lời hàng đầu với Python 3.4 và tôi thấy rằng itertools.accumulatenhanh hơn numpy.cumsumtrong nhiều trường hợp, thường nhanh hơn nhiều. Tuy nhiên, như bạn có thể thấy từ các nhận xét, điều này có thể không phải lúc nào cũng đúng và rất khó để khám phá toàn bộ các tùy chọn. (Vui lòng thêm nhận xét hoặc chỉnh sửa bài đăng này nếu bạn có thêm kết quả điểm chuẩn quan tâm.)

Một số thời gian ...

Đối với danh sách ngắn accumulatenhanh hơn khoảng 4 lần:

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

Đối với danh sách dài accumulatehơn nhanh hơn khoảng 3 lần:

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

Nếu numpy arraykhông được truyền sang list, accumulatevẫn nhanh hơn khoảng 2 lần:

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

Nếu bạn đặt các lần nhập bên ngoài hai hàm và vẫn trả về a numpy array, accumulatethì nhanh hơn gần 2 lần:

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
Bạn sẽ không mong đợi một chiếc máy bay nhanh hơn tàu hỏa để đi khắp thị trấn, đặc biệt là bao gồm cả việc mua vé và kiểm tra an ninh. Tương tự như vậy, bạn sẽ không sử dụng numpy để xử lý một listtrong năm mặt hàng, đặc biệt nếu bạn không muốn chấp nhận đổi arraylại. Nếu danh sách được đề cập thực sự quá ngắn, thì thời gian chạy của chúng sẽ không quan trọng --- sự phụ thuộc và tính dễ đọc chắc chắn sẽ chiếm ưu thế. Nhưng việc sử dụng rộng rãi listkiểu dữ liệu số thống nhất có độ dài đáng kể sẽ là ngớ ngẩn; đối với điều đó, một numpy array sẽ thích hợp và thường nhanh hơn.
askewchan

@askewchan à Tôi không chỉ tìm thấy điều này cho các danh sách ngắn và câu hỏi của OP yêu cầu một danh sách dưới dạng đầu ra chứ không phải là một mảng phức tạp. Có lẽ bạn có thể chỉnh sửa câu trả lời của bạn sẽ được rõ ràng hơn trên khi mỗi lần sử dụng là thích hợp :)
Chris_Rands

@askewchan Trên thực tế, tôi đã chỉnh sửa câu trả lời của mình với một so sánh chi tiết hơn nhiều. Trong mọi trường hợp, tôi có thấy numpymình nhanh hơn không, trừ khi tôi bỏ qua điều gì đó?
Chris_Rands

2
Ôi trời, đúng vậy :) Tôi sẽ không nói rằng bạn đã bỏ qua điều gì đó, nhưng việc so sánh khó có thể thực hiện một cách riêng lẻ nếu không xem xét đầu vào và đầu ra của bạn. Hầu hết thời gian trong sum2hàm của bạn có thể là chuyển đổi lthành một mảng. Hãy thử thời gian a = np.array(l)np.cumsum(a)riêng biệt. Sau đó thử a = np.tile(np.arange(1, 6), 1000)vs l = [1,2,3,4,5]*1000. Trong một chương trình tiến hành các quy trình số khác (như tạo hoặc tải lngay từ đầu) dữ liệu làm việc của bạn có thể đã nằm trong một mảng và việc tạo sẽ là một chi phí không đổi.
askewchan

1
@askewchan Tôi có cùng ý tưởng với bạn và do đó tôi đã tính thời gian a = np.array (l). Đối với sum2 không có chuyển đổi thành danh sách và với một mảng numpy làm đầu vào, sum2 nhanh hơn 5 lần cảm ơn sum1 trong máy tính của tôi trong trường hợp danh sách / mảng dài.
Mantxu

9

Hãy thử điều này: hàm tích lũy, cùng với toán tử thêm thực hiện phép cộng đang chạy.

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

5
Bạn không cần phải vượt qua operator.addvì dù sao thì thao tác mặc định cũng được bổ sung.
Eugene Yarmash

8

Biểu thức gán từ PEP 572 (mới trong Python 3.8) cung cấp một cách khác để giải quyết vấn đề này:

time_interval = [4, 6, 12]

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

5

Bạn có thể tính toán danh sách tổng tích lũy theo thời gian tuyến tính bằng một forvòng lặp đơn giản :

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]

Thư viện tiêu chuẩn itertools.accumulatecó thể là một giải pháp thay thế nhanh hơn (vì nó được triển khai trong 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

Chạy mã này cho

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

2

Trong Python3, Để tìm tổng tích lũy của một danh sách trong đó iphần tử thứ là tổng của i + 1 phần tử đầu tiên từ danh sách ban đầu, bạn có thể thực hiện:

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

HOẶC bạn có thể sử dụng khả năng hiểu danh sách:

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

Đầu ra

[4,10,22]

Điều này có vẻ đúng nhưng có thể làm rơi một liên kết đến tài liệu, nếu không có điều đó, tôi không thể ủng hộ.
S Meaden

2

Nếu bạn muốn một con trăn theo cách mà không cần làm việc trong 2.7 thì đây sẽ là cách của tôi

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

bây giờ chúng ta hãy thử nó và kiểm tra nó với tất cả các triển khai khác

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

Đầu tiên, bạn muốn có một danh sách các chuỗi con đang chạy:

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

Sau đó, bạn chỉ cần gọi summỗi dãy con:

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

(Đây không phải là cách hiệu quả nhất để làm điều đó, bởi vì bạn đang thêm tất cả các tiền tố lặp đi lặp lại. Nhưng điều đó có lẽ sẽ không thành vấn đề đối với hầu hết các trường hợp sử dụng và sẽ dễ hiểu hơn nếu bạn không cần phải nghĩ đến tổng số đang chạy.)

Nếu bạn đang sử dụng Python 3.2 hoặc mới hơn, bạn có thể sử dụng itertools.accumulateđể làm điều đó cho mình:

sums = itertools.accumulate(seq)

Và nếu bạn đang sử dụng 3.1 hoặc phiên bản cũ hơn, bạn chỉ có thể sao chép nguồn "tương đương với" ngay từ tài liệu (ngoại trừ thay đổi next(it)thành it.next()2.5 trở về trước).


9
Điều này chạy trong thời gian bậc hai (có thể điều đó không quan trọng đối với OP, nhưng đáng nói).
Chris Taylor,

Đầu tiên, khi N = 3, ai quan tâm đến thời gian bậc hai? Và tôi không nghĩ nó quá phức tạp. Đó là hai bước rất đơn giản, mỗi bước chuyển đổi một trình lặp này thành một trình lặp khác, dịch trực tiếp mô tả bằng tiếng Anh. (Thực tế là anh ấy đang sử dụng một cách không phổ biến để xác định chuỗi, trong đó tiền tố độ dài 0 không được tính, làm cho nó phức tạp hơn một chút… nhưng đó là cố hữu của vấn đề và tôi nghĩ tốt hơn nên đặt nó vào rangehơn để hack xung quanh nó bằng cách làm [1:]ở cuối, hoặc bỏ qua nó).
abarnert

1
Có lẽ vấn đề thực sự của OP không phải là lấy tổng một phần của [4,6,12]từ đó, như anh ấy đã viết trong câu hỏi, anh ấy đã biết đó là gì!
Chris Taylor,

@ChrisTaylor: Anh ấy nói rõ ràng rằng anh ấy đã biết cách viết cái này, nhưng muốn "một cách dễ dàng hơn để viết nó".
abarnert

1

Thử đi:

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

1

Có thể có nhiều câu trả lời cho điều này tùy thuộc vào độ dài của danh sách và hiệu suất. Một cách rất đơn giản mà tôi có thể nghĩ mà không cần nghĩ đến hiệu suất là:

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]

Đây là bằng cách sử dụng khả năng hiểu danh sách và điều này có thể hoạt động khá tốt, chỉ là ở đây tôi đang thêm nhiều lần vào mảng con, bạn có thể ứng biến điều này và làm cho nó đơn giản!

Chúc mừng nỗ lực của bạn!


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

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

Đây là slighlty nhanh hơn so với phương pháp phát trên bởi @Ashwini cho các danh sách nhỏ

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

Đối với các danh sách lớn hơn, trình tạo là cách chắc chắn. . .

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
Bạn đang tính chỉ cho danh sách 3 mục, hãy thử cho 10 ^ 4 mục.
Ashwini Chaudhary

1
Đúng, đối với danh sách lớn hơn, trình tạo nhanh hơn nhiều!
reptilicus

-1

Hơi hacky, nhưng có vẻ hoạt động:

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

Tôi đã nghĩ rằng hàm bên trong sẽ có thể sửa đổi được ykhai báo trong phạm vi từ vựng bên ngoài, nhưng điều đó không hoạt động, vì vậy thay vào đó chúng tôi chơi một số vụ hack khó chịu với sửa đổi cấu trúc. Nó có lẽ là thanh lịch hơn để sử dụng một máy phát điện.


-1

Không cần phải sử dụng Numpy, bạn có thể lặp trực tiếp trên mảng và tích lũy tổng trên đường đi. Ví dụ:

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

Kết quả trong:

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

-1

Một oneliner python thuần túy cho tổng tích lũy:

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

Đây là một phiên bản đệ quy lấy cảm hứng từ tổng tích lũy đệ quy . Một số giải thích:

  1. Thuật ngữ đầu tiên X[:1]là một danh sách chứa phần tử trước đó và gần giống như [X[0]](sẽ phàn nàn về danh sách trống).
  2. Lời cumsumgọi đệ quy trong số hạng thứ hai xử lý phần tử hiện tại [1]và danh sách còn lại có độ dài sẽ giảm đi một.
  3. if X[1:]ngắn hơn cho if len(X)>1.

Kiểm tra:

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

cumsum([])
#[]

Và mô phỏng cho sản phẩm tích lũy:

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

Kiểm tra:

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

Đây là một giải pháp thú vị khác. Điều này tận dụng lợi thế của lệnh đọc locals()hiểu, tức là các biến cục bộ được tạo bên trong phạm vi hiểu danh sách:

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

Đây là giao locals()diện cho mỗi lần lặp:

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

Hiệu suất không khủng khiếp đối với các danh sách nhỏ:

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

Và rõ ràng là giảm dần cho các danh sách lớn hơn.

>>> 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ặc dù phương pháp này xấu và không thực tế, nhưng nó chắc chắn là thú vị.


-2
lst = [4,6,12]

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

Nếu bạn đang tìm kiếm một giải pháp hiệu quả hơn (danh sách lớn hơn?), Máy phát điện có thể là một cuộc gọi tốt (hoặc chỉ sử dụng numpynếu bạn thực sự quan tâm đến hiệu suất).

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

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

-3

Đây sẽ là kiểu 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])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.