Python итерации через массив, находя среднее значение верхних k элементов

Предположим, что у меня есть массив Python a=[3, 5, 2, 7, 5, 3, 6, 8, 4]. Моя цель состоит в том, чтобы итерировать через этот массив 3 элемента за один раз, возвращая среднее из двух верхних элементов из трех элементов.

Используя вышеприведенный массив, во время моего этапа итерации первые три элемента [3, 5, 2] а среднее из верхних 2 элементов равно 4. Следующие три элемента: [5, 2, 7] а среднее из верхние 2 элемента - 6. Следующие три элемента - [2, 7, 5] а среднее из верхних 2 элементов - снова 6....

Следовательно, результатом для вышеупомянутого массива будет [4, 6, 6, 6, 5.5, 7, 7].

Каков наилучший способ написать такую функцию?

Ответы

Ответ 1

Решение

Вы можете использовать некоторые причудливые нарезки вашего списка, чтобы манипулировать подмножествами элементов. Просто возьмите каждый из трех подрежимов элементов, сортируйте, чтобы найти два верхних элемента, а затем найдите простой средний (ака средний) и добавьте его в список результатов.

Код

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]

И больше...

Есть еще несколько замечательных ответов, которые позволяют получить более эффективные ответы, в том числе с использованием генератора, чтобы избежать больших списков памяти: fooobar.com/questions/15230409/...

Ответ 2

Я верю в разделение кода на 2 части. Здесь это будет получение скользящего окна, получение двух верхних элементов и вычисление среднего значения. Самый чистый способ сделать это - использовать генераторы

Раздвижное окно

Небольшое изменение ответа evamicur с использованием tee, islice и zip для создания окна:

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

верхние 2 элемента

для вычисления среднего значения 2-го уровня вы можете использовать любой из методов, используемых в других ответах, я думаю, что heapq on является самым ясным

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

имею в виду

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

Ответ 3

Следующий код делает то, что вам нужно:

[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]

Ответ 4

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

Ответ 5

Итераторное решение

Решение 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 vs deque не имеет большого значения.

Ответ 6

В качестве векторизованного подхода, использующего 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. ])

Ответ 7

Использовать список

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]

Ответ 8

Вы можете попробовать это!

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

Ответ 9

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

Ответ 10

Вы также можете посмотреть на него с точки зрения генераторов:

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]

Ответ 11

Вам нужен скользящий оконный итератор вместе со средним числом максимум двух элементов. Я попытаюсь создать общее решение, которое можно использовать с скользящим окном размера n где n - любое положительное действительное число.

from itertools import islice

def calculate_means(items, window_length=3):
     stop_seq = window_length - 1
     sliding_window = [sorted(islice(items[x:],window_length),reverse=True) for x in range(len(items)-stop_seq)]
     return [sum(a[:stop_seq])/stop_seq for a in sliding_window]

>>> calculate_means([3, 5, 2, 7, 5, 3, 6, 8, 4])
>>> [4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]

Ответ 12

Для записи здесь есть функциональная версия:

>>> f=lambda values:[] if len(values)<=2 else [(sum(values[:3])-min(values[:3]))/2]+f(values[1:])
>>> f([3, 5, 2, 7, 5, 3, 6, 8, 4])
[4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]
>>> f([3, 5, 2])
[4.0]
>>> f([3, 5])
[]

Ответ 14

Не nlog(n) свои под-списки, эта операция - nlog(n) ! Вместо этого найдите самые большие два числа с алгоритмом O(n). Это повысит эффективность вашего решения. Повышение эффективности будет более заметным, если вы будете работать над большей проблемой "найти сумму вершины m из движущегося окна k элементов" для больших m и k.

def largestTwoMeans(myList):
    means = []
    for i in xrange(len(myList)-2):
        subList = myList[i:i+3]
        first, second = -float("inf"), -float("inf")
        for f in subList:       
            if f >= first:
                first, second = f, first
            elif first > f > second:
                second = f
        means.append((first+second)/2.0)
    return means

print largestTwoMeans(myList)
Out[222]: [4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]

Вот версия генератора:

def largestTwoMeans(myList):
    for i in xrange(len(myList)-2):
        subList = myList[i:i+3]
        first, second = -float("inf"), -float("inf")
        for f in subList:       
            if f >= first:
                first, second = f, first
            elif first > f > second:
                second = f
        yield (first+second)/2.0

print list(largestTwoMeans(myList))
Out[223]: [4.0, 6.0, 6.0, 6.0, 5.5, 7.0, 7.0]

Ответ 15

Чтобы отсортировать три числа, нам нужно не более трех сравнений. Чтобы найти самое низкое из трех чисел, нам нужно только два путем quickselect. Нам также не нужно делать копии подписок:

a,b,c

a < b
? (a < c ? a : c)
: (b < c ? b : c)
def f(A):
  means = [None] * (len(A) - 2)

  for i in xrange(len(A) - 2):
    if A[i] < A[i+1]:
      means[i] = (A[i+1] + A[i+2]) / 2.0 if A[i] < A[i+2] else (A[i] + A[i+1]) / 2.0
    else:
      means[i] = (A[i] + A[i+2]) / 2.0 if A[i+1] < A[i+2] else (A[i] + A[i+1]) / 2.0

  return means

print f([3, 5, 2, 7, 5, 3, 6, 8, 4])