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

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

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

Какой самый хороший способ написать такую функцию?

19
Student 27 Фев 2018 в 06:49

15 ответов

Лучший ответ

Решение

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

Код

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: // stackoverflow.com/a/49001728/416500

14
foslock 7 Мар 2018 в 18:36

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

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]
0
FatihAkici 27 Фев 2018 в 16:49

Вам нужен скользящий оконный итератор вместе со средним значением максимум двух элементов. Я попытаюсь найти общее решение, которое можно использовать со скользящим окном размером 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]
1
Sohaib Farooqi 27 Фев 2018 в 05:09

Решение только для итераторов

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

3
evamicur 27 Фев 2018 в 19:11

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

>>> 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])
[]
1
jferard 27 Фев 2018 в 17:13

В качестве векторизованного подхода с использованием 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. ])
3
Kasramvd 27 Фев 2018 в 09:15

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

[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]
8
damores 27 Фев 2018 в 04:02

Я верю в разделение кода на 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 верхних элемента

Чтобы вычислить среднее из двух самых высоких значений, вы можете использовать любой из методов, использованных в других ответах, я думаю, что 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]]

Означать

from statistics import mean
means = map(mean, top_n)
[4, 6, 6, 6, 5.5, 7, 7]
12
Maarten Fabré 27 Фев 2018 в 10:04

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

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])
0
גלעד ברקן 28 Фев 2018 в 14:22

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)
    ]
6
301_Moved_Permanently 27 Фев 2018 в 10:29

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

>>> 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
Keerthana Prabhakaran 27 Фев 2018 в 04:01

Использование алгоритма скользящего окна и стороннего more_itertools.windowed:

import statistics as stats

import more_itertools as mit


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

[stats.mean(sorted(w)[1:]) for w in mit.windowed(lst, 3)]
# [4, 6, 6, 6, 5.5, 7, 7]

См. Также соответствующую статью @Maarten Fabré.

1
pylang 27 Фев 2018 в 17:24

Используйте понимание списка

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
John H 4 Апр 2018 в 19:27

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

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]
1
Mahdi Ghelichi 27 Фев 2018 в 04:29
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
Rahul 27 Фев 2018 в 04:03