dreeves
dreeves

Reputation: 26932

reducelist in Python: like reduce but giving the list of intermediate results

You know the handy reduce function in Python. For example, you could use it to sum up a list like so (pretend there isn't the built-in sum):

reduce(lambda x,y: x+y, [1,2,3,4], 0)

which returns (((0+1)+2)+3)+4 = 10.

Now what if I wanted a list of the intermediate sums? In this case, [1,3,6,10].

Here's an ugly solution. Is there something more pythonic?

def reducelist(f, l, x): 
  out = [x]
  prev = x
  for i in l:
    prev = f(prev, i)
    out.append(prev)
  return out

Upvotes: 14

Views: 3048

Answers (3)

jimhark
jimhark

Reputation: 5046

Note: Somehow I missed @DSM's answer before I wrote this. Go read and up vote that one instead, I just did. Come back if you want a longer answer.

Python has this, it's called accumulate and it's implemented in the itertools standard library module starting in Python 3.2. The optional second argument, 'func', was added in 3.3.

import itertools

l = [1,2,3,4]
out = itertools.accumulate(l)

In this case out is an iterable. If you need a list, then

out = list(itertools.accumulate(l))

The accumulate() function can be used to generate a running total, or 'accumulated sums'. The default function is addition. We can pass in a function as the second argument:

import itertools
import operator

l = [1,2,3,4]
factorial = itertools.accumulate(l, operator.mul)

Here we pass in operator.mul for multiplication to generate a running product. The operator module exports a set of efficient functions corresponding to the intrinsic operators of Python.

Of course we're not limited to functions defined in the operator module. You can use any function that accepts 2 parameters of the type of the elements in the first parameter. You can get creative, but here I'll do the opposite and explicitly implement the default addition/sum behavior using a lambda:

import itertools

l = [1,2,3,4]
out = itertools.accumulate(l, lambda a, b: a + b)

Finally, since you asked, I think using accumulate is more Pythonic then your looping example.

Upvotes: 1

halex
halex

Reputation: 16403

If you make your solution to a generator it's shorter and it better obeys functional programming style. I would add an default value of 0 for x too:

def reducelist(f, lst, x=0): 
  prev = x
  for i in lst: 
    prev = f(prev, i)
    yield prev

That is definitely more pythonic.

Upvotes: 9

DSM
DSM

Reputation: 353059

My favourite, if you're recent enough:

Python 3.2.1 (default, Jul 12 2011, 22:22:01) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import itertools
>>> itertools.accumulate([1,2,3,4])
<itertools.accumulate object at 0x1006baad0>
>>> list(itertools.accumulate([1,2,3,4]))
[1, 3, 6, 10]

accumulate also accepts a function argument [even more recent, though-- 3.3]:

>>> list(itertools.accumulate([1,2,3,4], lambda x,y: x+y))
[1, 3, 6, 10]
>>> list(itertools.accumulate([1,2,3,4], lambda x,y: x+y+1))
[1, 4, 8, 13]

Upvotes: 18

Related Questions