Запуск среднего уровня в Python
Есть ли способ pythonic для создать список, содержащий среднюю некоторую функцию?
Прочитав веселый маленький кусочек о марсиане, черных ящиках и распределении Коши, я подумал, что было бы интересно рассчитать пробег среднее значение распределения Коши:
import math
import random
def cauchy(location, scale):
p = 0.0
while p == 0.0:
p = random.random()
return location + scale*math.tan(math.pi*(p - 0.5))
# is this next block of code a good way to populate running_avg?
sum = 0
count = 0
max = 10
running_avg = []
while count < max:
num = cauchy(3,1)
sum += num
count += 1
running_avg.append(sum/count)
print running_avg # or do something else with it, besides printing
Я думаю, что этот подход работает, но мне любопытно, может ли быть более элегантный подход к созданию этого списка running_avg
, чем использование циклов и счетчиков (например, ).
Есть несколько связанных вопросов, но они адресуют более сложные проблемы (размер небольшого окна, экспоненциальный вес) или не относятся к Python:
Ответы
Ответ 1
Вы можете написать генератор:
def running_average():
sum = 0
count = 0
while True:
sum += cauchy(3,1)
count += 1
yield sum/count
Или, учитывая генератор чисел Коши и функцию полезности для генератора текущей суммы, вы можете иметь точное выражение генератора:
# Cauchy numbers generator
def cauchy_numbers():
while True:
yield cauchy(3,1)
# running sum utility function
def running_sum(iterable):
sum = 0
for x in iterable:
sum += x
yield sum
# Running averages generator expression (** the neat part **)
running_avgs = (sum/(i+1) for (i,sum) in enumerate(running_sum(cauchy_numbers())))
# goes on forever
for avg in running_avgs:
print avg
# alternatively, take just the first 10
import itertools
for avg in itertools.islice(running_avgs, 10):
print avg
Ответ 2
Вы можете использовать сопрограммы. Они похожи на генераторы, но позволяют отправлять значения. Coroutines был добавлен в Python 2.5, так что это не будет работать в версиях до этого.
def running_average():
sum = 0.0
count = 0
value = yield(float('nan'))
while True:
sum += value
count += 1
value = yield(sum/count)
ravg = running_average()
next(ravg) # advance the corutine to the first yield
for i in xrange(10):
avg = ravg.send(cauchy(3,1))
print 'Running average: %.6f' % (avg,)
Как понимание списка:
ravg = running_average()
next(ravg)
ravg_list = [ravg.send(cauchy(3,1)) for i in xrange(10)]
редактирует:
- Использование функции
next()
вместо метода it.next()
. Это значит, что он также будет работать с Python 3. Функция next()
также была перенесена обратно в Python 2.6+.
В Python 2.5 вы можете либо заменить вызовы на it.next()
, либо самостоятельно определить функцию next
.
(Спасибо Адам Паркин)
Ответ 3
У меня есть два возможных решения для вас. Оба являются всего лишь средними средними функциями, которые работают с любым списком чисел. (может быть сделано для работы с любым итерабельным)
Генератор на основе:
nums = [cauchy(3,1) for x in xrange(10)]
def running_avg(numbers):
for count in xrange(1, len(nums)+1):
yield sum(numbers[:count])/count
print list(running_avg(nums))
Основано на списке (на самом деле тот же код, что и раньше):
nums = [cauchy(3,1) for x in xrange(10)]
print [sum(nums[:count])/count for count in xrange(1, len(nums)+1)]
Генератор-компоновщик Генератор на основе:
Изменить: этот, который я только что проверил, чтобы проверить, могу ли я легко решить мое решение, совместимое с генераторами, и какова будет его производительность. Это то, что я придумал.
def running_avg(numbers):
sum = 0
for count, number in enumerate(numbers):
sum += number
yield sum/(count+1)
Посмотрите статистику производительности ниже, хорошо стоит.
Технические характеристики:
Редактирование: я также решил протестировать Orip интересное использование нескольких генераторов, чтобы увидеть влияние на производительность.
Используя timeit и следующие (1,000,000 итераций 3 раза):
print "Generator based:", ', '.join(str(x) for x in Timer('list(running_avg(nums))', 'from __main__ import nums, running_avg').repeat())
print "LC based:", ', '.join(str(x) for x in Timer('[sum(nums[:count])/count for count in xrange(1, len(nums)+1)]', 'from __main__ import nums').repeat())
print "Orip's:", ', '.join(str(x) for x in Timer('list(itertools.islice(running_avgs, 10))', 'from __main__ import itertools, running_avgs').repeat())
print "Generator-compatabile Generator based:", ', '.join(str(x) for x in Timer('list(running_avg(nums))', 'from __main__ import nums, running_avg').repeat())
Получаю следующие результаты:
Generator based: 17.653908968, 17.8027219772, 18.0342400074
LC based: 14.3925321102, 14.4613749981, 14.4277560711
Orip's: 30.8035550117, 30.3142540455, 30.5146529675
Generator-compatabile Generator based: 3.55352187157, 3.54164409637, 3.59098005295
Смотрите комментарии для кода:
Orip genEx based: 4.31488609314, 4.29926609993, 4.30518198013
Результаты отображаются в секундах и показывают, что генератор-совместимый генератор LC будет последовательно быстрее, ваши результаты могут отличаться. Я ожидаю, что огромная разница между моим оригинальным генератором и новым заключается в том, что сумма не рассчитывается "на лету".