Резюме
Как мудро заметил Тед Джасперс, методология, которую я описал в исходном предложении еще в 2012 году, на самом деле является частным случаем экспоненциальная скользящая средняя. Прелесть этого подхода в том, что его можно вычислить рекурсивно, то есть вам нужно сохранить только одно значение популярности для каждого объекта, а затем вы можете рекурсивно настраивать это значение при возникновении события. Нет необходимости записывать каждое событие.
Это единое значение популярности представляет все прошлые события (в пределах используемого типа данных), но более старые события начинают иметь экспоненциальное меньшее значение по мере того, как учитываются новые события. Этот алгоритм будет адаптироваться к разным временным шкалам и будет реагировать на меняющиеся объемы трафика. . Каждый раз, когда происходит событие, новое значение популярности можно рассчитать по следующей формуле:
(a * t) + ((1 - a) * p)
a
- коэффициент от 0 до 1 (более высокие значения дисконтируют более старые события быстрее)t
- текущая отметка времениp
- текущее значение популярности (например, хранится в базе данных)
Разумные значения для a
будут зависеть от вашего приложения. Хорошее место для начала - a=2/(N+1)
, где N
- количество событий, которые должны существенно повлиять на результат. Например, на веб-сайте с низким трафиком, где событием является просмотр страницы, вы можете ожидать сотен просмотров страниц в течение нескольких дней. Выбор N=100
(a≈0.02
) был бы разумным выбором. Для веб-сайта с высоким трафиком вы можете ожидать миллионов просмотров страниц в течение нескольких дней, и в этом случае N=1000000
(a≈0.000002
) будет более разумным. Значение a
, вероятно, со временем потребуется постепенно корректировать.
Чтобы проиллюстрировать, насколько прост этот алгоритм популярности, вот пример того, как его можно реализовать в Craft CMS в двух строках разметки Twig:
{% set popularity = (0.02 * date().timestamp) + (0.98 * entry.popularity) %}
{% do entry.setFieldValue("popularity", popularity) %}
Обратите внимание, что нет необходимости создавать новые таблицы базы данных или хранить бесконечные записи о событиях, чтобы рассчитать популярность.
Следует иметь в виду одно предостережение: экспоненциальные скользящие средние имеют интервал увеличения скорости вращения, поэтому требуется несколько рекурсий, прежде чем значение можно будет считать точным. Это означает, что начальное состояние важно. Например, если популярность нового элемента инициализируется с использованием текущей метки времени, элемент сразу становится самым популярным элементом во всем наборе, прежде чем в конечном итоге займет более точное положение. Это может быть желательно, если вы хотите продвигать новый контент. В качестве альтернативы вы можете захотеть, чтобы контент продвигался вверх снизу, и в этом случае вы можете инициализировать его с меткой времени, когда приложение было впервые запущено. Вы также можете найти золотую середину, инициализировав значение со средним значением всех значений популярности в базе данных, чтобы оно начиналось прямо посередине.
Исходное предложение
Существует множество предлагаемые алгоритмы для расчета популярности на основе возраста товара и количества голосов, кликов или покупок, которые получает товар. Однако более надежные методы, которые я видел, часто требуют слишком сложных вычислений и нескольких хранимых значений, которые загромождают базу данных. Я обдумывал чрезвычайно простой алгоритм, который не требует хранения каких-либо переменных (кроме самого значения популярности) и требует только одного простого вычисления. Это до смешного просто:
p = (p + t) / 2
Здесь p - значение популярности, хранящееся в базе данных, а t - текущая отметка времени. Когда элемент создается впервые, необходимо инициализировать p. Есть два возможных метода инициализации:
- Инициализировать p текущей меткой времени t
- Инициализировать p средним значением всех значений p в базе данных.
Обратите внимание, что метод инициализации (1) дает недавно добавленным элементам явное преимущество перед историческими элементами, таким образом добавляя элемент релевантности. С другой стороны, метод инициализации (2) рассматривает новые элементы как равные по сравнению с историческими элементами.
Допустим, вы используете метод инициализации (1) и инициализируете p с текущей меткой времени. Когда элемент получает свой первый голос, p становится средним значением времени создания и времени голосования. Таким образом, значение популярности p по-прежнему представляет собой действительную метку времени (при условии, что вы округлились до ближайшего целого числа), но фактическое время, которое оно представляет, является абстрагированным.
При использовании этого метода требуется только одно простое вычисление, и только одно значение необходимо сохранить в базе данных (p). Этот метод также предотвращает неконтролируемые значения, поскольку популярность данного элемента никогда не может превышать текущее время.
Пример работы алгоритма в течение 1 дня: http://jsfiddle.net/q2UCn/
Пример работы алгоритма в течение 1 года: http://jsfiddle.net/tWU9y/
Если вы ожидаете, что голоса будут постоянно поступать с интервалами менее секунды, тогда вам нужно будет использовать микросекундную метку времени, такую как функция PHP microtime()
. В противном случае будет работать стандартная временная метка UNIX, например функция PHP time()
.
Теперь у меня вопрос: видите ли вы какие-либо серьезные недостатки в этом подходе?