前言
昨天同学用定时任务重新抽取了博客的文章,大概有3500条,然后今天发现热榜的评分好像不太好,所以对热榜进行了很多次的调整。
原本热榜文章评分的算法是:将博客发布的时间转化为10位的时间戳 +4000博客的点赞量 +2000博客的阅读量
这两个权重参数的设置也是很不好把握的。
之后将热榜的评分修改成 :10位的时间戳截取掉第一位变成9位 +8000博客的点赞量 +3000博客的阅读量
发现效果还是不理想。
然后同学给我推荐了一个算法——Hacker News的热门排名算法,我在项目中实现了这种计算方法,然后发现,效果还是不错的,所以直接将热榜的算法替换成了Hacker News算法。
Hacker News算法介绍
他的数学公式其实很简单:
(1)得票数P
在其他条件不变的情况下,得票越多,排名越高。
为什么是P-1?网络上的一种解释是,很多文章作者在提交的时候会给自己投上一票。其实更重要的原因是文章发布初期的投票数对排名影响非常的,仅仅是自己给自己投的一票,也占非常大的作用。
假设P不去减去1,那公式为: p / (t + 2)^1.5
如果一个作者发布完就给自己投票,那么文章的得分为1/(0+2)^1.5=0.3535 。假设另外一篇文章发布了8小时,那么需要多少的投票呢?x/(8+2)^1.5>0.3535 X>11.17~ 即一天前的帖子要有12票才能超过新提交的文章,这显然不合理。
这个具体减多少还要视网络环境而定,要是换在国内,估计P-100还不够。另外如果你不期望“高投票文章”与“低投票文章差距过大,可以在得票数上加一个小于1的指数,比如(P-1)^0.8。
这里我使用的参数是点赞量*3+浏览量 作为p值,并且没有加指数进行缩减
(2)距离发帖时间T
在其他条件不变的情况下,越是新发表的帖子,排名越高。或者说,一个帖子的排名,会随着时间不断下降。
从前一张图可以看到,经过24小时之后,所有帖子的得分基本上都小于1,这意味着它们都将跌到排行榜的末尾,保证了排名前列的都将是较新的内容。
如果,用户的第一个投票是在当前,1小时,2小时获得时,这个曲线的变化是什么呢?如下图,曲线斜率从大到小分别是当前、1小时、2小时。可以看到第一个投票的作用不断弱化,其权重不断降低。
由于文章的发布,多半是要差距很多天的,所以我这里T的选择是与当前时间相差了多少周,如果用小时的话,会导致评分非常的小
(3)重力因子G
它的数值大小决定了排名随时间下降的速度。从下图可以看到,三根曲线的其他参数都一样,G的值分别为1.5、1.8和2.0。G值越大,曲线越陡峭,排名下降得越快。
为什么G=1.5,首先,G是干嘛的。毫无疑问,G这个数字既非时间,也非评价,其实它的主要目的是控制更新频率。G的值越大,score的衰减速度越快,排行的更新越频繁。所以,确定G值需要观察系统内部投票数在时间上的分布,然后根据需要的更新频次确定G的合理取值。越火爆、用户互动越频繁的社区,为了保证排行的稳定性(不要频繁大量的刷新),G值趋向于比较低。这就是为什么Hacker News从一开始的1.8修改成1.5,过段时间可能就变成1.2了。
算法的实现
//一次点击率可以增加的得分
private static int READ_COUNT_K = 1;
//一次点赞增加的评分
private static int LIKE_COUNT_K = 3;
public double calculateHackerNewsScore(Article article)
{
String updateTime = article.getUpdateTime();
int readCount = article.getReadCount();
int likeCount = article.getLikeCount();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = formatter.parse(updateTime);
} catch (ParseException e) {
date = new Date();
}
//发帖时间的时间戳
double updateTimeStamp = date.getTime() ;
System.out.println("发帖时间的时间戳"+updateTimeStamp);
//当前时间的时间戳
double nowTimeStamp = System.currentTimeMillis();
System.out.println("当前时间的时间戳"+nowTimeStamp);
//用来获取两个时间相差的毫秒数
double l=nowTimeStamp -updateTimeStamp;
//相差的天
double day=l/(24*60*60*1000);
//相差的小时
double hour=(day*24);
double week = day/7;
double month = week/4;
/**
* HackerNews评分 分子 点击权重为1 点赞权重为3 分母为 距离发帖时间相差的时间+2的G次方
*/
double score = ((readCount *READ_COUNT_K + likeCount *LIKE_COUNT_K) -1)/Math.pow((week +2),G);
score *= 10000;
System.out.println("文章" +article.getId() +" 发帖时间为" +updateTime +" 与当前时间相差" + week +"星期 评分为"+score);
return score;
}
用Hacker News算法算出分数后,发现分数特别小
所以就讲所有分数都乘了10000,得到最后的得分