Calculating a cumulative value? Sounds like a fold to me!
d = {'dan':7, 'mike':2, 'john':3}
denominator = float(sum(d.viewvalues()))
data = ((k,(v/denominator)) for (k, v) in sorted(d.viewitems(), key = lambda (k,v):v))
import functional
f = lambda (k,v), l : [(k, v+l[0][1])]+l
functional.foldr(f, [(None,0)], [('a', 1), ('b', 2), ('c', 3)])
#=>[('a', 6), ('b', 5), ('c', 3), (None, 0)]
d_cump = { k:v for (k,v) in functional.foldr(f, [(None,0)], data) if k is not None }
Functional isn't a built-in package. You could also re-jig f
to work with a right-fold, and hence the standard reduce if you wanted.
As you can see, this isn't much shorter, but it takes advantage of sequence destructuring to avoid splitting/zipping, and it uses a generator as the intermediate data
, which avoids building a list.
If you want to further minimise object creation, you can use this alternative function which modifies the initial list passed in (but has to use a stupid trick to return the appropriate value, because list.append
returns None
).
uni = lambda x:x
ff = lambda (k,v), l : uni(l) if l.insert(0, (k, v+l[0][1])) is None else uni(l)
Incidentally, the left fold is very easy using ireduce
(from this page http://www.ibm.com/developerworks/linux/library/l-cpyiter/index.html ), because it eliminates the list construction:
ff = lambda (l, ll), (k,v), : (k, v+ll)
g = ireduce(ff, data, (None, 0))
tuple(g)
#=>(('mike', 0.16666666666666666), ('john', 0.41666666666666663), ('dan', 1.0))
def ireduce(func, iterable, init=None):
if init is None:
iterable = iter(iterable)
curr = iterable.next()
else:
curr = init
for x in iterable:
curr = func(curr, x)
yield curr
This is attractive because the initial value is not included, and because generators are lazy, and so particularly suitable for chaining.
Note that ireduce
above is equivalent to:
def ireduce(func, iterable, init=None):
from functional import scanl
if init is None: init = next(iterable)
sg = scanl(func, init, iterable)
next(sg)
return sg