Python遍历数组并找出前k个元素的平均值

19
假设我有一个Python数组a=[3, 5, 2, 7, 5, 3, 6, 8, 4]。我的目标是每次迭代3个元素,返回这3个元素中前2个元素的平均值。
使用上述数组,在我的迭代步骤中,前三个元素是[3, 5, 2],前两个元素的平均值是4。接下来的三个元素是[5, 2, 7],前两个元素的平均值是6。接下来的三个元素是[2, 7, 5],再次前两个元素的平均值为6......
因此,上述数组的结果将为[4, 6, 6, 6, 5.5, 7, 7]
如何编写这样一个函数最好呢?

如果数组少于3个元素怎么办? - jpmc26
1
我最初考虑的问题是针对一个长度为m的输入数组,我们每次迭代n个元素,同时找到前k个元素的平均值,使得m >= n >= k。以上方式是为了简单起见而表述的问题。我希望能够推广一个适用于一般情况的好解决方案。 - darkgbm
3
我建议关闭此问题,因为它只是在寻求代码,可能是一道作业问题。 - Izkata
我的解决方案似乎比(foslock的)被接受的答案快4-5倍 :) https://repl.it/repls/AliveTechnoGraph - גלעד ברקן
15个回答

14

解决方案

您可以利用列表的切片来操作子集元素。只需获取每个三个元素的子列表,排序以找到前两个元素,然后找到简单平均值(也称为平均数)并将其添加到结果列表中。

代码

def get_means(input_list):
    means = []
    for i in xrange(len(input_list)-2):
        three_elements = input_list[i:i+3]
        sum_top_two = sum(three_elements) - min(three_elements)
        means.append(sum_top_two/2.0)
    return means

例子

您可以如下所示查看您的输入示例(和期望结果):

print(get_means([3, 5, 2, 7, 5, 3, 6, 8, 4]))
# [4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]

更多内容...

还有一些出色的答案,涉及更多性能方面的问题,其中一个使用生成器来避免大型内存列表:https://dev59.com/qVUM5IYBdhLWcg3wUu8P#49001728


12
你可以使用 sum_top_two = sum(three_elements) - min(three_elements) 来避免排序。 - gyre
3
将函数转换成生成器可能是一个改进,方法是将 means.append 行替换为 yield。如果 input_list 很大,这样做更加节省内存。 - thegreatemu
1
@gyre 好建议!就我个人而言,我喜欢模仿自己在列表上的操作方式(通过在脑海中对三个元素进行排序来获取前两个)。 - foslock
@thegreatemu 也提出了一个好建议,而且编写的函数非常容易转换为返回生成器。我在这里返回一个列表,因为这是问题所要求的。 - foslock
1
我的解决方案似乎快了4-5倍 :) https://repl.it/repls/AliveTechnoGraph - גלעד ברקן

12

我相信将代码分为两部分。这里是获取滑动窗口、获取前两个元素并计算平均值。最干净的方法是使用生成器。

滑动窗口

evamicur的答案上略微变化,使用teeislicezip创建窗口:

def windowed_iterator(iterable, n=2):
    iterators = itertools.tee(iterable, n)
    iterators = (itertools.islice(it, i, None) for i, it in enumerate(iterators))
    yield from zip(*iterators)

windows = windowed_iterator(iterable=a, n=3)
[(3, 5, 2), (5, 2, 7), (2, 7, 5), (7, 5, 3), (5, 3, 6), (3, 6, 8), (6, 8, 4)]

前两个元素

要计算最大的两个数的平均数,您可以使用其他答案中使用的任何方法,我认为heapq是最清晰的。

from heapq import nlargest
top_n = map(lambda x: nlargest(2, x), windows)

或等价地

top_n = (nlargest(2, i) for i in windows)
[[5, 3], [7, 5], [7, 5], [7, 5], [6, 5], [8, 6], [8, 6]]

均值(mean)

from statistics import mean
means = map(mean, top_n)
[4, 6, 6, 6, 5.5, 7, 7]

我喜欢你的窗口迭代器函数,我在我的机器上测试了它们,对于range(10**5),窗口大小为3和前2个元素,它们的性能相当。当我增加窗口大小和最大数量时,deque似乎可以更好地扩展。 - evamicur
哎呀,我计时失误了,对于大窗口来说deque似乎稍微快一点,不过在这种情况下nlargest仍然占主导地位。 - evamicur
我的解决方案似乎快了60-80倍 :) https://repl.it/repls/TragicNextCleantech - גלעד ברקן

8
以下代码可以实现您所需的功能:
[sum(sorted(a[i:i + 3])[-2:]) / 2 for i in range(len(a) - 2)]

给定你的a=[3, 5, 2, 7, 5, 3, 6, 8, 4],返回:

[4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]

6

itertools 提供了一个简洁的方法,可以从任何可迭代对象中提取一对项,而不仅限于索引。你可以稍微调整它以提取三个项目:

def tripletwise(iterable):
    a, b, c = itertools.tee(iterable, 3)
    next(b, None)
    next(itertools.islice(c, 2, 2), None)
    return zip(a, b, c)

使用这个方法,您可以简化迭代所有三元组的过程:
def windowed_means(iterable):
    return [
        (sum(window) - min(window)) / 2.0
        for window in tripletwise(iterable)
    ]

3

仅使用迭代器的解决方案

foslok的解决方案绝对没问题,但我想尝试一下使用生成器的版本。它只在遍历原始列表时存储一个长度为window_size的双端队列,然后找到n_largest值并计算其平均值。

import itertools as it
from collections import deque
from heapq import nlargest
from statistics import mean

def windowed(iterable, n):
    _iter = iter(iterable)
    d = deque((it.islice(_iter, n)), maxlen=n)
    yield tuple(d)
    for i in _iter:
        d.append(i)
        yield tuple(d)

a = [3, 5, 2, 7, 5, 3, 6, 8, 4]
means = [mean(nlargest(2, w)) for w in windowed(a, 3)]
print(means)   

结果:

[4, 6, 6, 6, 5.5, 7, 7]

因此,要更改元素数量(窗口大小)或n个最大元素,只需更改相应函数的参数即可。这种方法还避免了切片的使用,因此可以更轻松地应用于您无法或不想切片的可迭代对象。

时间记录

def deque_version(iterable, n, k):
    means = (mean(nlargest(n, w)) for w in windowed(iterable, k))
    for m in means:
        pass

def tee_version(iterable, n, k):
    means = (mean(nlargest(n, w)) for w in windowed_iterator(iterable, k))
    for m in means:
        pass

a = list(range(10**5))


n = 3 
k = 2
print("n={} k={}".format(n, k))
print("Deque")
%timeit deque_version(a, n, k)
print("Tee")
%timeit tee_version(a, n, k)

n = 1000 
k = 2
print("n={} k={}".format(n, k))
print("Deque")
%timeit deque_version(a, n, k)
print("Tee")
%timeit tee_version(a, n, k)

n = 50
k = 25
print("n={} k={}".format(n, k))
print("Deque")
%timeit deque_version(a, n, k)
print("Tee")
%timeit tee_version(a, n, k)


result:

n=3 k=2
Deque
1.28 s ± 3.07 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Tee
1.28 s ± 16.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
n=1000 k=2
Deque
1.28 s ± 8.72 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Tee
1.27 s ± 2.92 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
n=50 k=25
Deque
2.46 s ± 10.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Tee
2.47 s ± 2.45 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

显然,itertools的tee和deque并没有太大区别。


我的解决方案似乎快了50倍 :) https://repl.it/repls/TragicNextCleantech - גלעד ברקן
1
我没有测量时间,但你的可能更快,尽管它不太通用。 - evamicur
我很确定tee通常使用双端队列实现,因此可以解释可忽略的性能差异。 - gyre
我之前没有听说过这个,但是这很有道理! - evamicur

3

作为使用Numpy的矢量化方法,您可以执行以下操作:

np.sort(np.column_stack((a[:-2], a[1:-1], a[2:])))[:,-2:].mean(axis=1)

示例:

In [13]: a=np.array([3, 5, 2, 7, 5, 3, 6, 8, 4])

In [14]: np.sort(np.column_stack((a[:-2], a[1:-1], a[2:])))[:,-2:].mean(axis=1)
Out[14]: array([4. , 6. , 6. , 6. , 5.5, 7. , 7. ])

1
使用列表推导式
from statistics import mean

yourList=[3, 5, 2, 7, 5, 3, 6, 8, 4]

k = 3

listYouWant = [mean(x) for x in [y[1:k] for y in [sorted(yourList[z:z+k]) for z in xrange(len(yourList)) if z < len(yourList) -(k-1)]]]

产生 [4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]


1

你可以尝试这个!

>>> a
[3, 5, 2, 7, 5, 3, 6, 8, 4]
>>> n
3
>>> m
2
>>> [sum(sorted(a[i*n:i*n+n])[1:])/m for i in range(len(a)/n)]
[4, 6, 7]

那就是,
>>> a
[3, 5, 2, 7, 5, 3, 6, 8, 4]
>>> n
3
>>> [i for i in range(len(a)/n)]
[0, 1, 2]
>>> m=2
>>> [a[i*n:i*n+n] for i in range(len(a)/n)]
[[3, 5, 2], [7, 5, 3], [6, 8, 4]]
>>> [sorted(a[i*n:i*n+n]) for i in range(len(a)/n)]
[[2, 3, 5], [3, 5, 7], [4, 6, 8]]
>>> [sorted(a[i*n:i*n+n])[1:] for i in range(len(a)/n)]
[[3, 5], [5, 7], [6, 8]]
>>> [sum(sorted(a[i*n:i*n+n])[1:]) for i in range(len(a)/n)]
[8, 12, 14]
>>> [sum(sorted(a[i*n:i*n+n])[1:])/m for i in range(len(a)/n)]
[4, 6, 7]

1
a=[3, 5, 2, 7, 5, 3, 6, 8, 4]
mean_list = [
    mean(x)
        for x in [
            y[1:3]
                for y in [
                    sorted(a[z:z+3])
                        for z in range(len(a))
                            if z < len(a) -2
                ]
        ]
]

1
你也可以从生成器的角度来看待它:
a=[3, 5, 2, 7, 5, 3, 6, 8, 4]

def gen_list():
    for i in range(0, len(a) - 3):
        yield sorted(a[i:i + 3], reverse=True)

apply_division = map(lambda x: sum(x[:2]) / len(x[:2]), gen_list())


if __name__=="__main__":
    result = list(apply_division)
    print(result)
[4.0, 6.0, 6.0, 6.0, 5.5, 7.0]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接