Zennie
Zennie

Reputation: 375

Cumulative addition in a list based on an indices list

Say I have list, list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]

And another list, ind_list = [0, 4, 7]

I want to create a third list that will contain the cumulative sum of the first list which "resets" on every index from ind_list.

To clarify, the result should be res_list = [100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

Upvotes: 10

Views: 894

Answers (8)

b-fg
b-fg

Reputation: 4137

A slightly simpler answer only using NumPy:

import numpy as np
list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [2, 4, 7]
res_list = []

for i, n in enumerate(ind_list):
    if i == 0:
        n_prev=0
    else:
        n_prev = ind_list[i-1]
    for k in range(n-n_prev):
        res_list.append(np.sum(list_a[n_prev:n_prev+k+1]))
    if i == len(ind_list)-1:
        for k in range(len(list_a)-n):
            res_list.append(np.sum(list_a[n:n+k+1]))

print(res_list)

Output

[100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

Upvotes: 1

hiro protagonist
hiro protagonist

Reputation: 46899

Or just create a generator:

def generate_running_sum(summands, index_list):
    current_sum = 0
    for i, summand in enumerate(summands):
        if i in set(index_list):
            current_sum = 0
        current_sum += summand
        yield current_sum

Applied to your data:

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

res_list = list(generate_running_sum(summands=list_a, index_list=ind_list))
print(res_list)

Upvotes: 2

Albin Paul
Albin Paul

Reputation: 3419

I did it with a combination of zip and np.cumsum.

I considered indexes from [0-4),[4-7) and [7-(len(list))).

And then I found the cumulative sum of each slice of the list and then put it in another list:

import numpy as np

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

def createcumsum(list_a, ind_list):
    # For adding the end of the list to the indexes if it doesn't exist
    if ind_list[-1] != len(list_a):
        ind_list.append(len(list_a))
    res_list=[]
    # Considers the indexes from 0-4, 4-7, 7-(endoflist)
    for x,y in zip(ind_list, ind_list[1:]):
        # Take cumulativesum on the above mentioned slices
        res_list.extend(np.cumsum(list_a[x:y]))
    return res_list
print(createcumsum(list_a, ind_list))

Output

[100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

Upvotes: 1

Binyamin Even
Binyamin Even

Reputation: 3382

Use the following:

cs= np.cumsum(list_a)
for i in ind_list:
    if i==0:
        continue
    cs[i:]-=cs[i-1]

Result:

cs
>>array([100, 105, 106, 108, 200, 203, 204, 300, 306, 312])

Upvotes: 10

Divakar
Divakar

Reputation: 221614

It's NumPy tagged and finding vectorized solutions is fun, so here's one -

def intervaled_cumsum(list_a, ind_list):
    a = np.array(list_a)
    a[ind_list[1:]] -= np.add.reduceat(a,ind_list)[:-1]
    return a.cumsum()

Sample run -

In [54]: list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]

In [55]: ind_list = [0, 4, 7]

In [56]: intervaled_cumsum(list_a, ind_list)
Out[56]: array([100, 105, 106, 108, 200, 203, 204, 300, 306, 312])

Upvotes: 3

timgeb
timgeb

Reputation: 78750

Maximum itertools abuse!

Setup

from itertools import accumulate, chain, islice, tee

def pairwise(iterable):
    'pairwise recipe from itertools docs'
    it1, it2 = tee(iterable)
    next(it2)
    return zip(it1, it2)

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

Solution

ind_list.append(None)
result = list(chain.from_iterable(accumulate(islice(list_a, start, stop))
              for start, stop in pairwise(ind_list)))

print(result)

Output

[100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

The idea behind using itertools facilities whenever possible here is to avoid creating intermediary list-slices which unnecessarily consume memory.

~edit~~

Memory efficient Python 2.7 solution

from itertools import chain, islice, izip, tee

def pairwise(iterable):
    'pairwise recipe from itertools docs'
    it1, it2 = tee(iterable)
    next(it2)
    return izip(it1, it2)

def my_cumsum(iterable):
    s = 0
    for x in iterable:
        s += x
        yield s

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

ind_list.append(None)
result = list(chain.from_iterable(my_cumsum(islice(list_a, start, stop))
              for start, stop in pairwise(ind_list)))

print(result)

Upvotes: 1

petruz
petruz

Reputation: 535

I had to expand the ind_list manually:

ind_list = [0, 4, 7] + [len(list_a)]

Just running a nested loop with the nested range set to the ind_list above:

count, final = 0, []
for i in range(len(ind_list)-1):
    count = 0
    for i in range(ind_list[i],ind_list[i+1]):
        count += list_a[i]
        final.append(count)

Upvotes: 1

user2390182
user2390182

Reputation: 73470

Some itertools to the rescue:

from itertools import accumulate as acc, chain

list_a = [100, 5, 1, 2, 200, 3, 1, 300, 6, 6]
ind_list = [0, 4, 7]

list(chain(*(acc(list_a[x:y]) for x, y in zip(ind_list, ind_list[1:]+[None]))))
# [100, 105, 106, 108, 200, 203, 204, 300, 306, 312]

Upvotes: 4

Related Questions