18
\$\begingroup\$

It's a very simple question, but I wonder if there is a more pythonic way to add each previous elements in a list like this (for example with a list comprehension maybe) :

input : L = [4, 2, 1, 3]
output : new_L = [4, 6, 7, 10]

Which I've done this way :

def add_one_by_one(L):
    new_L = []
    for elt in L:
        if len(new_L)>0:
            new_L.append(new_L[-1]+elt)
        else:
            new_L.append(elt)
    return new_L

new_L = add_one_by_one(L)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Are you using Python 2, Python 3 or both? \$\endgroup\$
    – Peilonrayz
    Dec 9, 2016 at 9:54
  • \$\begingroup\$ I'm using mostly Python 2 \$\endgroup\$
    – Natha
    Dec 9, 2016 at 10:13

7 Answers 7

19
\$\begingroup\$

Note that Python's official style-guide, PEP8, recommends using lower_case names for variables, so I changed all your Ls to l and all your new_L to new_l.

l = [4, 2, 1, 3]

You should keep track of the cumulative sum in a variable. This way you avoid having to test whether the new_l already has an element in it:

def add_one_by_one(l):
    new_l = []
    cumsum = 0
    for elt in l:
        cumsum += elt
        new_l.append(cumsum)
    return new_l

As an alternative, you could use a generator to avoid having to build the list inside the function (if you are just going to iterate over it later, this is the most memory-efficient way):

def add_one_by_one_gen(l):
    cumsum = 0
    for elt in l:
        cumsum += elt
        yield cumsum

new_l = list(add_one_by_one_gen(l))

# This takes basically no additional memory (only one float/int):
for x in add_one_by_one_gen(l):
    print x

(Replace print x with print(x) in Python 3.x.)

Probably the fastest way to do it would be using the numpy function cumsum:

import numpy as np

new_l = np.cumsum(l)
\$\endgroup\$
6
  • 11
    \$\begingroup\$ Not everyone has, needs or wants to use NumPy. Let's not make it the JQuery to JavaScript questions of Stack Overflow. \$\endgroup\$
    – Peilonrayz
    Dec 9, 2016 at 9:48
  • 7
    \$\begingroup\$ @Peilonrayz I agree. This is why I proposed two non-numpy alternatives (where the generator is probably even better if the OP does not actually need the cumulative sums all at once). \$\endgroup\$
    – Graipher
    Dec 9, 2016 at 9:49
  • 3
    \$\begingroup\$ BTW, list(itertools.accumulate(l)) is faster than numpy.cumsum(l). I only tried when l was a Python list, as that's the use-case. \$\endgroup\$
    – Peilonrayz
    Dec 9, 2016 at 10:17
  • \$\begingroup\$ @Peilonrayz Good to know. Makes sense that it only starts to be faster when used on bumpy arrays \$\endgroup\$
    – Graipher
    Dec 9, 2016 at 11:55
  • 6
    \$\begingroup\$ @Graipher No, arrays are always bumpy in Python. \$\endgroup\$
    – wizzwizz4
    Dec 9, 2016 at 17:38
25
\$\begingroup\$

In Python 3 itertools gained a new function accumulate that does what you want. I'd recommend that you instead used this function. Or if you can't if you're in Python 2 to upgrade. This would single handedly change your code to:

from itertools import accumulate

new_l = accumulate(l)

If you however done this as a learning exercise, then I'd instead use iterators. I'd first change l to an iterator, via iter. Which would allow you to use next to remove the default value. After this I would then loop through the iterator and yield rather than new_list.append the new values. This can allow you to get something like:

def accumulate_sum(l):
    l = iter(l)
    try:
        total = next(l)
    except StopIteration:
        return
    yield total
    for item in l:
        total += item
        yield total

Which funnily enough is almost exactly the same to how it's done in itertools.accumulate. If you wanted to at a later date use a different function rather than addition, then you could pass that as a function, to call on each iteration.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I didn't know about the iter function, very useful. \$\endgroup\$
    – Natha
    Dec 9, 2016 at 10:17
8
\$\begingroup\$

1 Using Python 2

1.1 Ninja way

Is fun and good for learning python, but don't go for it in production code

from operator import iadd

l = [4,2,1,3] 
reduce(lambda result, x: iadd(result, [result[-1] + x]), l, [0])[1:]

1.2 Explicit way

I will just copy @Grapier solution for this because I would do the same:

def add_one_by_one_gen(l):
    cumsum = 0
    for elt in l:
        cumsum += elt
        yield cumsum

2 Using Python 3

from itertools import accumulate

accumulate(l)
\$\endgroup\$
9
  • \$\begingroup\$ For the first solution, why would you want to use iadd instead of just result+[result[-1]+x]? \$\endgroup\$
    – kalj
    Dec 9, 2016 at 13:03
  • 1
    \$\begingroup\$ @kalj there is no particular reason for that, you can use result + result[...] if you want to, which is a more obvious way to do that. \$\endgroup\$
    – Alex
    Dec 9, 2016 at 13:09
  • \$\begingroup\$ @Alex is the reduce really that hard to read? You could always define an 'ungolfed' named function to pass to reduce if it required annotations. I don't write python daily, but summing a list says reduce to me in any HOF language...or is there some other reason you wouldn't use it in prod? \$\endgroup\$ Dec 9, 2016 at 14:45
  • 2
    \$\begingroup\$ @JaredSmith This kind of abuse had led to reduce being moved to functools. Note that he isn't exactly summing a list - he traverses the list with an array, adding new elements to that array. Things like this are better done with iteration, or comprehension (well, not in this case, but in general), leaving reduce for tasks where set of homogenous elements is combined into one element of similar "type" (like summing a list, if we didn't have a sum for that). \$\endgroup\$ Dec 9, 2016 at 14:56
  • 1
    \$\begingroup\$ @JaredSmith > heavily-nested data structure yield from is great, instead of nested comprehensions you can have composition of named generators which explicitly say what they do and what parts of outer scope they use. As for HOFs, they are incredibly fun, but when I don't want to be asked how exactly my code works I try to keep shenanigans toned down. \$\endgroup\$ Dec 9, 2016 at 15:44
2
\$\begingroup\$

Since you asked for a Pythonic solution and I see none so far, I propose:

new_L = [sum(L[:i+1]) for i in range(len(L))]

It's certainly less efficient than an accumulator -- it's \$O(\frac{n^2}{2})\$ vs \$O(n)\$ -- but it uses a list comprehension as you suggested.

\$\endgroup\$
1
  • \$\begingroup\$ Welcome to the site! Indeed this oneliner (or, more precisely, lack of more efficient oneliners) demonstrates that sometimes syntax gets in your way - unless you resort to [element + this_list()[-1] for element in L], lending some magic from here. \$\endgroup\$ Dec 9, 2016 at 23:20
2
\$\begingroup\$

Iterate through the array, adding the previous value to the next. The same way you would do it in C. Simple, short, reasonably efficient, and no fancy tricks.

def add_one_by_one(L):
    new_L = list(L)
    for i in range(1, len(new_L)):
        new_L[i] += new_L[i-1]
    return new_L

print(add_one_by_one([4, 2, 1, 3]))

Of course, in Python 3 using accumulate is clearly superior.

\$\endgroup\$
2
\$\begingroup\$

As Matthew Cotton posted, using a list comprehension is a very clean one-liner.

You can also do it recursively. Here is what I came up with.

def accum(L):
    if len(L)<2: return L
    return accum(L[:-1]) + [sum(L)]
\$\endgroup\$
1
\$\begingroup\$

Hereby another 'Pythonic' variant of Matthew's answer which does not require the use of both range() and len():

[sum(L[:i[0]+1]) for i in enumerate(L)]

enumerate is handy to use since it creates tuples containing the index and value at that index.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.