10

What is the best way to replicate this simple function using a list comprehension (or another compact approach)?

import numpy as np

sum=0
array=[]
for i in np.random.rand(100):
   sum+=i
   array.append(sum)
Pierz
  • 7,064
  • 52
  • 59
  • Are you using numpy by any chance? I know numpy has a nice function for something like this. – arshajii Nov 26 '13 at 16:26
  • 1
    I wouldn't use a list comprehension - elements are expected to be independent from each other, and in this case they are not. – Izkata Nov 26 '13 at 16:27
  • 1
    Why would you want to turn this into a list comprehension? It is far more readable to keep this in a separate loop. Make it `array = [0]`, `for i in rand(100): array.append(i + array[-1])` instead, perhaps. – Martijn Pieters Nov 26 '13 at 16:28
  • 1
    Is this the same as http://stackoverflow.com/questions/13221896/python-partial-sum-of-numbers? – doctorlove Nov 26 '13 at 16:29
  • I did mean rand(100). I have been using numpy but a solution without would be nice too. – Pierz Nov 26 '13 at 16:29
  • Where is the `rand()` function declared? It is not a builtin function. – Hannes Ovrén Nov 26 '13 at 16:30
  • I'm not tracking a rand() function in python. Random.randint() maybe? So you want an array of random size? – hyleaus Nov 26 '13 at 16:32

5 Answers5

11

In Python 3, you'd use itertools.accumulate():

from itertools import accumulate

array = list(accumulate(rand(100)))

Accumulate yields the running result of adding up the values of the input iterable, starting with the first value:

>>> from itertools import accumulate
>>> list(accumulate(range(10)))
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

You can pass in a different operation as a second argument; this should be a callable that takes the accumulated result and the next value, returning the new accumulated result. The operator module is very helpful in providing standard mathematical operators for this kind of work; you could use it to produce a running multiplication result for example:

>>> import operator
>>> list(accumulate(range(1, 10), operator.mul))
[1, 2, 6, 24, 120, 720, 5040, 40320, 362880]

The functionality is easy enough to backport to older versions (Python 2, or Python 3.0 or 3.1):

# Python 3.1 or before

import operator

def accumulate(iterable, func=operator.add):
    'Return running totals'
    # accumulate([1,2,3,4,5]) --> 1 3 6 10 15
    # accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
    it = iter(iterable)
    total = next(it)
    yield total
    for element in it:
        total = func(total, element)
        yield total
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • This one works but I was looking for something like a list comprehension for compact use - like on the ipython command line. – Pierz Nov 26 '13 at 17:36
  • @Pierz: I used `list()` on the `accumulate()` iterator to give you a quick list of values. You can still also use it in a list comprehension, `[v for v in accumulate(rand(100))]`; you cannot do this with *just* a list comprehension however, because you don't have access to the previous element(s) generated so far. – Martijn Pieters Nov 26 '13 at 17:38
  • True it's an elegant approach but I wasn't sure if there was a neat way to have the accumulated sum in a list comprehension. Agreed it's probably not best practice but when working on the command line it's handy to use compact forms. – Pierz Nov 26 '13 at 17:44
4

Since you're already using numpy, you can use cumsum:

>>> from numpy.random import rand
>>> x = rand(10)
>>> x
array([ 0.33006219,  0.75246128,  0.62998073,  0.87749341,  0.96969786,
        0.02256228,  0.08539008,  0.83715312,  0.86611906,  0.97415447])
>>> x.cumsum()
array([ 0.33006219,  1.08252347,  1.7125042 ,  2.58999762,  3.55969548,
        3.58225775,  3.66764783,  4.50480095,  5.37092001,  6.34507448])
DSM
  • 342,061
  • 65
  • 592
  • 494
1

Ok, you said you did not want numpy but here is my solution anyway. It seems to me that you are simply taking the cumulative sum, thus use the cumsum() function.

import numpy as np
result = np.cumsum(some_array)

For a random example

result = np.cumsum(np.random.uniform(size=100))
Hannes Ovrén
  • 21,229
  • 9
  • 65
  • 75
1

Here is a concept to abuse the original intent of the := "walrus operator" introduced in python 3.8. It assigns variables as part of a larger expression. My impression of the intent was to save the user from calculating something in the test portion of an "if" and then have to calculate it again in the executable portion. But it is not local to the if so you can use the variable anytime after it is defined. So this method uses an if in the list comprehension that is always True to serve as a place to do the re-assignment. Unfortunately "+:=" is not an operator so you have to do the long hand addition assignment instead of the += :

import numpy as np

sum=0
array=[sum for i in np.random.rand(100) if (sum:=sum+i) or True]
1

An update to the above answer, figured out an even simpler, better way of doing it. Not sure why it did not dawn on me before. Items using the walrus operator := which is always enclosed in () have a return, unlike regular assignment. So on the command line test=0 executes silently but (test:=0) prints 0 to the screen. So in that respect it is like a function in that it has a return. so it can go in the content portion of the list compression instead of using it in a disposable "if" with the or True.
so the code becomes:

import numpy as np

sum=0
array=[(sum:=sum+i) for i in np.random.rand(100)]
  • Please mind the fact that you could have edited the old answer to add this part. This would have prevented future user to need to look for the previous answer or simply not find this one. – GregoirePelegrin Mar 15 '23 at 14:31