优雅地通过 averaging 缩减列表的方法?

5 投票
5 回答
1424 浏览
提问于 2025-04-16 12:47

有没有更简洁优雅的写法来实现这个函数呢?

def reduce(li):
    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

这个函数的作用是:

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

它计算了列表中偶数和奇数位置的平均值,如果列表的元素个数是奇数的话,最后一个元素就保持不变。

5 个回答

1

这里有一行代码:

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

其中 b 是你想要简化的原始列表。

补充:这是我上面代码的一个变种,可能更清晰一些,并且使用了 itertools

from itertools import izip_longest
[(0.5*(x+y) if y != None else x)  for x,y in izip_longest(*[iter(b)]* 2)]
2
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

注意,你原来的代码有两个错误:用[0,1]会得到0,而不是0.5;用[5]会得到[4],而不是[5]。

8

你实际上想做的是对你的列表应用一个移动平均,具体来说,就是用两个样本来计算平均值。数学上来说,你需要用一个窗口 [.5, .5] 来进行卷积运算,然后只取偶数位置的样本。为了避免在处理奇数长度的数组时最后一个元素被除以二,你可以把它复制一遍,这样对偶数长度的数组没有影响。

使用 numpy 这个库可以让这个过程变得很简洁:

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.])

你可以通过 list(outputarray) 将结果转换回列表。

如果你对性能有要求,使用 numpy 是非常有用的,因为它的底层是经过优化的 C 语言数学代码在处理这些计算:

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

撰写回答