开发者

Elegant way of reducing list by averaging?

Is there a more elegant way of writing this function?

def reduce(li):
    开发者_JAVA技巧result=[0 for i in xrange((len(li)/2)+(len(li)%2))]
    for i,e in enumerate(li):
        result[int(i/2)] += e
    for i in range(len(result)):
        result[i] /= 2
    if (len(li)%2 == 1):
        result[len(result)-1] *= 2
    return result

Here, what it does:

a = [0,2,10,12]
b = [0,2,10,12,20]
reduce(a)
>>> [1,11]
reduce(b)
>>> [1,11,20]

It is taking average of even and odd indexes, and leaves last one as is if list has odd number of elements


what you actually want to do is to apply a moving average of 2 samples trough your list, mathematically you convolve a window of [.5,.5], then take just the even samples. To avoid dividing by two the last element of odd arrays, you should duplicate it, this does not affect even arrays.

Using numpy it gets pretty elegant:

import numpy as np

np.convolve(a + [a[-1]], [.5,.5], mode='valid')[::2]
array([  1.,  11.])

np.convolve(b + [b[-1]], [.5,.5], mode='valid')[::2]
array([  1.,  11.,  20.])

you can convert back to list using list(outputarray).

using numpy is very useful if performance matters, optimized C math code is doing the work:

In [10]: %time a=reduce(list(np.arange(1000000))) #chosen answer
CPU times: user 6.38 s, sys: 0.08 s, total: 6.46 s
Wall time: 6.39 s

In [11]: %time c=np.convolve(list(np.arange(1000000)), [.5,.5], mode='valid')[::2]
CPU times: user 0.59 s, sys: 0.01 s, total: 0.60 s
Wall time: 0.61 s


def reduce(li):
    result = [(x+y)/2.0 for x, y in zip(li[::2], li[1::2])]
    if len(li) % 2:
        result.append(li[-1])
    return result

Note that your original code had two bugs: [0,1] would give 0 rather than 0.5, and [5] would give [4] instead of [5].


Here's a one-liner:

[(0.5*(x+y) if y != None else x)  for x,y in map(None, *(iter(b),) * 2)]

where b is your original list that you want to reduce.

Edit: Here's a variant on the code I have above that maybe is a bit clearer and relies on itertools:

from itertools import izip_longest
[(0.5*(x+y) if y != None else x)  for x,y in izip_longest(*[iter(b)]* 2)]


Here's another attempt at it that seems more straightforward to me because it's all one pass:

def reduce(li):

    result = []
    it = iter(li)

    try:
        for i in it:
            result.append((i + next(it)) / 2)
    except StopIteration:
        result.append(li[-1])

    return result


Here's my try, using itertools:

import itertools

def reduce(somelist):
    odds = itertools.islice(somelist, 0, None, 2)
    eves = itertools.islice(somelist, 1, None, 2)
    for (x,y) in itertools.izip(odds,evens):
        yield( (x + y) / 2.0)
    if len(somelist) % 2 != 0 : yield(somelist[-1])

>>> [x for x in reduce([0, 2, 10, 12, 20]) ]
[1, 11, 20]

See also: itertools documentation.

Update: Fixed to divide by float rather than int.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜