引入:
几乎所有搞开发的人都对Stack Overflow不陌生,它是一个专门的Q&A类型的网站http://dbanotes.net/startup/stack_overflow_success.html (类似这种网站的例子还有很多,比如新浪问问,百度搜搜等)你如果遇到了技术问题,你可以在这里提问,然后就有专家或者路人去回答你的问题,其他人还可以对别人的回答以及这个提的问题本身进行评价,然后这些所有信息,比如问题被点击了多少次,多少人提过问题,对问题的评价是好的还是坏的,有多少人参与了回答,回答的得分是多少,这问题提了多久了,这问题最近一次回答是多久前等信息都会被搜集起来,然后用一个叫做“热点度” 的专业词汇来衡量,如果热点度高,那么说明这个问题是热门问题,那么问题更可能出现在关键字搜索后的问题排名中,我们这文章的目的就是来研究热点问题的排名算法,我还专门用java实现了这个算法,并且和网上的搜索结果相匹配。
实践:
比如我搜索liferay-theme关键字,会得到以下的结论:http://stackoverflow.com/questions/tagged/liferay-theme?sort=newest&pageSize=15 ,我们看到了一组热点问题排名如下:
我们通过研究,发现这个Stack Overflow 创始人之一的Jeff Atwood公布过排名算法如下:
我们现在就用java程序来实现这个算法,然后和真实的搜索结果比较(貌似我干的事情很蛋疼):
首先找到这个算法公式的多个自变量:
我们创建一个VO值对象来包含这些自变量,这些自变量作用参见注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
package
com.algoresearch;
import
java.util.Date;
/**
*
* Description: the value object for given hot question
*
* @author charles.wang
* @created Oct 15, 2013 3:32:25 PM
*
*/
public
class
HotQuestionInfoVO {
protected
long
viewTimes;
//问题的浏览次数
protected
long
agreeTicketNo;
//问题的赞成票数
protected
long
rejectTicketNo;
//问题的反对票数
protected
long
answerTimes;
//问题被回答的次数
protected
long
[] answerScores;
//每个回答的得分
protected
long
lastQuestionPostMillis ;
//问题提出至今的时间(单位:秒)
protected
long
lastAnswerPostMillis;
//问题最近回答至今的时间(单位:秒)
/**
*
* @param viewTimes
* @param agreeTicketNo
* @param rejectTicketNo
* @param answerTimes
* @param answerScores
* @param lastQuestionPostMillis
* @param lastAnswerPostMillis
*/
public
HotQuestionInfoVO(
final
long
viewTimes,
final
long
agreeTicketNo,
final
long
rejectTicketNo,
final
long
answerTimes,
final
long
[] answerScores,
final
long
lastQuestionPostMillis,
final
long
lastAnswerPostMillis){
this
.viewTimes =viewTimes;
this
.agreeTicketNo =agreeTicketNo;
this
.rejectTicketNo =rejectTicketNo;
this
.answerTimes = answerTimes;
this
.answerScores = answerScores;
this
.lastQuestionPostMillis =lastQuestionPostMillis;
this
.lastAnswerPostMillis = lastAnswerPostMillis;
}
@Override
public
String toString(){
StringBuilder sb =
new
StringBuilder();
sb.append(
"该问题被浏览了: "
+ viewTimes+
" 次"
+
"\n"
);
sb.append(
"该问题被赞成的投票数为: "
+agreeTicketNo+
"\n"
);
sb.append(
"该问题被反对的投票数为: "
+rejectTicketNo+
"\n"
);
sb.append(
"该问题被回答的次数为: "
+answerTimes+
" 次"
+
"\n"
);
sb.append(
"该问题被提出的时间为: "
+(
new
Date(lastQuestionPostMillis)).toString()+
"\n"
);
sb.append(
"该问题被最后一次解答的时间为: "
+(
new
Date(lastAnswerPostMillis)).toString()+
"\n"
);
return
sb.toString();
}
}
|
然后,我们来创建计算类,这个类职责是通过以上一些因素来得到相应的热点问题的评分,注释很详细了,自己看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/*
*/
package com.algoresearch;
/**
*
* Description: utility class that can calculate the "hot" value of given hot question based on factors
*
* @author charles.wang
* @created Oct 15, 2013 3:43:45 PM
*
*/
public
class
HotQuestionScoreCalculator {
public
static
double
calculateHotQuestionScoreValue (HotQuestionInfoVO vo){
//Step1: 计算问题浏览次数对于热点的影响:
// 浏览次数(viewTimes)越多,说明越是热点,而对于10取对数的作用则是弱化频繁被浏览造成的指数虚高因素
double
factor1 = Math.log10(vo.viewTimes) *
4
;
//Step2: 计算问题的参与度以及好评/差评对于热点的影响:
// 如果一个问题,被好评的多,被差评的少,那么问题越"热" (agreeTicketNo-rejectTicketNo),
// 此外,回答次数也很重要,因为被回答次数越多,说明有更多的人参与了这个问题的思考,从而使该问题变"热"
double
factor2 = (vo.agreeTicketNo-vo.rejectTicketNo)*vo.answerTimes/
5
;
//Step3:计算回答的质量对于热点的影响:
// 每个问题的回答都有一个得分,所有问题的总分越高,说明问题越有价值,也就越"热"
long
sum=0L;
for
(
long
i : vo.answerScores)
sum+=i;
double
factor3=(
double
)sum;
//以上几个是放在分子上的正相关因素,下面几个是放在分母上的反相关因素
//Step4:计算距今发表问题时间和距今最近回答问题时间对于热点的影响:
// 显然,发表问题越久远,其热点度越低,上次回答问题越久远,其热点度越低。
// 距今发表问题时间-最近回答问题时间表示了问题的闲置度,显然这个值越小,说明很少有人继续光顾,热点越低。
long
questionExistingTime=(System.currentTimeMillis()-vo.lastQuestionPostMillis)/
1000
;
long
answerUpdatedTime=(System.currentTimeMillis()-vo.lastAnswerPostMillis)/
1000
;
double
factor4 = Math.pow((questionExistingTime+
1
) - ( (questionExistingTime-answerUpdatedTime)/
2
),
1.5
);
return
(factor1+factor2+factor3)/factor4;
}
}
|
最后,我们按照上文截图的例子进行验证,我们就取这3个来比较,所以在Main类中,我们创建了3个VO,然后调用计算方法得出3个热点度,最后比较这些热点度是否是和图示一样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
/*
*/
package com.algoresearch;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
/**
*
* Description:
*
* @author charles.wang
* @created Oct 15, 2013 4:24:47 PM
*
*/
public
class
MainClass {
public
static
void
main(String [] args){
//模拟search liferay-theme的结果:
//搜索地址为 http://stackoverflow.com/questions/tagged/liferay-theme?sort=newest&pageSize=15
//第一条热门问题
long
answerScores1[]= {-2L,1L,4L,1L};
Calendar questionAskTime1 = Calendar.getInstance();
questionAskTime1.set(
2012
,
10
,
17
,
13
,
59
,
27
);
Calendar questionLastAnswerTime1 = Calendar.getInstance();
questionLastAnswerTime1.set(
2013
,
7
,
3
,
18
,
55
,
37
);
HotQuestionInfoVO hotQuestion1 =
new
HotQuestionInfoVO (2000L,4L,1L,4L,answerScores1,questionAskTime1.getTimeInMillis(),questionLastAnswerTime1.getTimeInMillis());
System.out.println(hotQuestion1);
double
hotQuestionValue1 = HotQuestionScoreCalculator.calculateHotQuestionScoreValue(hotQuestion1);
//第二条热门问题
long
answerScores2[]= {3L,4L};
Calendar questionAskTime2 = Calendar.getInstance();
questionAskTime2.set(
2012
,
4
,
4
,
8
,
33
,
4
);
Calendar questionLastAnswerTime2 = Calendar.getInstance();
questionLastAnswerTime2.set(
2012
,
4
,
4
,
11
,
53
,
17
);
HotQuestionInfoVO hotQuestion2 =
new
HotQuestionInfoVO (1000L,3L,0L,2L,answerScores2,questionAskTime2.getTimeInMillis(),questionLastAnswerTime2.getTimeInMillis());
System.out.println(hotQuestion2);
double
hotQuestionValue2 = HotQuestionScoreCalculator.calculateHotQuestionScoreValue(hotQuestion2);
//第三条热门问题
long
answerScores3[]= {7L};
Calendar questionAskTime3 = Calendar.getInstance();
questionAskTime3.set(
2012
,
2
,
19
,
16
,
23
,
38
);
Calendar questionLastAnswerTime3 = Calendar.getInstance();
questionLastAnswerTime3.set(
2012
,
02
,
20
,
14
,
18
,
57
);
HotQuestionInfoVO hotQuestion3 =
new
HotQuestionInfoVO (496L,3L,0L,1L,answerScores3,questionAskTime3.getTimeInMillis(),questionLastAnswerTime3.getTimeInMillis());
System.out.println(hotQuestion3);
double
hotQuestionValue3 = HotQuestionScoreCalculator.calculateHotQuestionScoreValue(hotQuestion3);
if
( (hotQuestionValue1>hotQuestionValue2 ) && (hotQuestionValue2>hotQuestionValue3) ){
System.out.println(
"热点问题排名和期望一样"
);
}
else
{
System.out.println(
"热点问题排名不和期望一样"
);
}
}
}
|
我们运行结果,果然这个排名和我们图上的一致:
总结:
其实我做的这个无聊的实验只是用于验证这个公式是否真的和创始人说的那样正确的应用到了网站,当然了,这里得到证实。我们反观公式,运用数学公式,可以得到以下结论:
对于Stack Overflow 热点问题的热点度,它:
(1)和浏览次数成正比,浏览次数越多,热点度越高
(2)和问题的参与度成正比,参与问题回答的人越多,热点度越高
(3)和问题本身被评价的好坏正相差成正比,好评越多,差评越少,热点度越高,说明这是个好问题
(4)和提供答案的人给的答案水平成正比,答案正确给分高,错误可能是负分,所以给的正确答案或者参考信息越多,热点越高,而如果一些人恶意给答案或者乱给错误答案导致答案评分低或者负数,则会影响热点度。
(5)和问题提出的距今时间成反比,这个问题被提出的越早,热点度越低。
(6)和问题被最后回答的时间成反比,这个问题如果很久没新的答案了,那么热点度降低。
其他的Q&A类型网站我没研究过,虽然我估计公式不一定一样,但是这些结论我相信还是正确的。