【发布时间】:2012-06-23 02:14:06
【问题描述】:
总结
正如 Ted Jaspers 明智地指出的那样,我在 2012 年的原始提案中描述的方法实际上是 exponential moving average 的一个特例。这种方法的美妙之处在于它可以递归计算,这意味着您只需为每个对象存储一个流行度值,然后您可以在事件发生时递归地调整该值。无需记录每个事件。
这个单一的流行度值代表所有过去的事件(在所使用的数据类型的限制内),但随着新事件的考虑,旧事件的重要性开始呈指数级降低。该算法将适应不同的时间尺度,并将响应不同的交通量。每次事件发生时,新的人气值可以使用以下公式计算:
(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) %}
请注意,无需创建新的数据库表或存储无穷无尽的事件记录来计算流行度。
要记住的一个警告是,指数移动平均线有一个旋转间隔,因此需要几次递归才能认为该值是准确的。这意味着初始条件很重要。例如,如果使用当前时间戳初始化新项目的流行度,则该项目立即成为整个集合中最流行的项目,然后最终稳定到更准确的位置。如果您想推广新内容,这可能是可取的。或者,您可能希望内容从底部向上运行,在这种情况下,您可以使用应用程序首次启动时的时间戳对其进行初始化。您还可以通过使用数据库中所有流行度值的平均值初始化该值来找到一个快乐的媒介,因此它从中间开始。
原提案
有很多 suggested algorithms 用于根据项目的年龄和项目获得的投票、点击或购买次数来计算受欢迎程度。但是,我见过的更强大的方法通常需要过于复杂的计算和多个存储值,这会使数据库变得混乱。我一直在考虑一个非常简单的算法,它不需要存储 任何 变量(除了流行度值本身)并且只需要 一个 简单的计算。这非常简单:
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/
如果您希望投票以亚秒级的间隔稳定地流入,那么您将需要使用微秒时间戳,例如 PHP microtime() 函数。否则,标准 UNIX 时间戳将起作用,例如 PHP time() 函数。
现在我的问题是:您认为这种方法有什么重大缺陷吗?
【问题讨论】:
-
如果您允许人们“不喜欢”项目,这不需要 only 将 p 存储在数据库中。您还必须存储每个“赞”的记录。否则,用户可以一遍又一遍地点赞,然后不喜欢,然后喜欢,然后不喜欢,以增加他们的投票。正如您所说,您只想在收到第一次投票时更改项目的 p 。这意味着您需要跟踪所有投票。
-
@AlSweigart 好点。该算法可能仅适用于单向投票系统(例如,页面视图是一个正方向的“投票”)。它可能与双向投票系统不太兼容。
标签: algorithm popularity