摘要
正如Ted Jaspers所指出的那样,我在2012年原始提案中描述的方法实际上是指数移动平均线的一种特殊情况。这种方法的美妙之处在于它可以进行递归计算,这意味着您只需要为每个对象存储一个单独的受欢迎度值,然后当事件发生时可以递归地调整此值。不需要记录每个事件。
这个单一的受欢迎度值代表了所有过去的事件(在使用的数据类型的限制范围内),但随着新事件的加入,旧事件的影响会呈指数级下降。这个算法将适应不同的时间尺度,并对不同的流量变化做出反应。每次发生事件时,可以使用以下公式计算新的受欢迎度值:
(a * t) + ((1 - a) * p)
a
— 系数介于0和1之间(较高的值更快地减少旧事件的影响)t
— 当前时间戳p
— 当前的受欢迎度值(例如存储在数据库中)
a
will depend on your application. A good starting place is a=2/(N+1)
, where N
is the number of events that should significantly affect the outcome. For example, on a low-traffic website where the event is a page view, you might expect hundreds of page views over a period of a few days. Choosing N=100
(a≈0.02
) would be a reasonable choice. For a high-traffic website, you might expect millions of page views over a period of a few days, in which case N=1000000
(a≈0.000002
) would be more reasonable. The value for a
will likely need to be gradually adjusted over time.为了说明这个流行度算法的简单性,以下是在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。有两种可能的初始化方法:
- 使用当前时间戳t初始化p
- 使用数据库中所有p值的平均值初始化p
假设您使用初始化方法(1),并使用当前时间戳初始化p。当该项收到第一个投票时,p成为创建时间和投票时间的平均值。因此,流行度值p仍表示有效的时间戳(假设四舍五入到最近的整数),但它所代表的实际时间是抽象的。
使用此方法,只需要进行一次简单的计算,并且数据库中只需要存储一个值(p)。该方法还可以防止运行值,因为给定项的流行度永远不能超过当前时间。
算法在1天内工作的示例:http://jsfiddle.net/q2UCn/ 算法在1年内工作的示例:http://jsfiddle.net/tWU9y/ 如果您希望投票以每秒的子级间隔稳定流入,则需要使用微秒时间戳,例如PHP的microtime()函数。否则,标准的UNIX时间戳将起作用,例如PHP的time()函数。
现在我的问题是:您是否认为这种方法存在任何主要缺陷?