概括
正如 Ted Jaspers 明智地指出的那样,我在 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 中用 2 行 Twig 标记来实现:
{% set popularity = (0.02 * date().timestamp) + (0.98 * entry.popularity) %}
{% do entry.setFieldValue("popularity", popularity) %}
请注意,无需创建新的数据库表或存储无休止的事件记录来计算流行度。
需要记住的一个警告是指数移动平均线有一个旋转间隔,因此需要一些递归才能认为该值是准确的。这意味着初始条件很重要。例如,如果使用当前时间戳初始化新项目的流行度,则该项目立即成为整个集合中最流行的项目,然后最终稳定到更准确的位置。如果您想推广新内容,这可能是可取的。或者,您可能希望内容从底部向上运行,在这种情况下,您可以使用应用程序首次启动时的时间戳对其进行初始化。您还可以通过使用数据库中所有流行度值的平均值初始化该值来找到一个快乐的媒介,因此它从中间开始。
原始提案
有很多建议的算法可以根据项目的年龄和项目收到的投票、点击或购买次数来计算受欢迎程度。但是,我见过的更强大的方法通常需要过于复杂的计算和多个存储值,这会使数据库变得混乱。我一直在考虑一种非常简单的算法,它不需要存储任何变量(除了流行度值本身)并且只需要一个简单的计算。这非常简单:
p = (p + t) / 2
这里,p 是存储在数据库中的流行度值,t 是当前时间戳。首次创建项目时,必须初始化p 。有两种可能的初始化方法:
- 用当前时间戳t初始化p
- 用数据库中所有p值的平均值初始化p
请注意,初始化方法 (1) 使最近添加的项目相对于历史项目具有明显的优势,因此添加了相关性元素。另一方面,初始化方法 (2) 在与历史项目相比时将新项目视为平等。
假设您使用初始化方法 (1) 并使用当前时间戳初始化p 。当项目收到第一次投票时,p成为创建时间和投票时间的平均值。因此,流行度值p仍然代表一个有效的时间戳(假设您四舍五入到最接近的整数),但它代表的实际时间是抽象的。
使用这种方法,只需要一个简单的计算,并且只需要将一个值存储在数据库中(p)。此方法还可以防止值失控,因为给定项目的流行度永远不会超过当前时间。
在 1 天内工作的算法示例:http: //jsfiddle.net/q2UCn/
在 1 年内工作的算法示例:http: //jsfiddle.net/tWU9y/
如果您希望投票以亚秒级的间隔稳定流入,那么您将需要使用微秒时间戳,例如 PHPmicrotime()
函数。否则,标准 UNIX 时间戳将起作用,例如 PHPtime()
函数。
现在我的问题是:您认为这种方法有什么重大缺陷吗?