用户在输入搜索关键词时,根据已经输入的部分,自动提示出后面可能需要输入的词,既减少了用户输入的劳动量,也可以提示用户当前有哪些备选,有效的降低输错的几率,提升搜索的体验。
在第一期中,我们做了一个汉字提示的功能,从User Log中提取出每天搜索频率最高的词,和这些词命中的搜索结果数,更新到服务器中,然后提供补全提示功能。数据每天凌晨增量更新一次,保证数据的实时性,相比其他产品的补全功能,我们的数据要及时准确的多。
系统结构:
系统的性能:
由于结构简单,使用UDP通信,后台全部使用内存树的结构,处理能力>3000个/秒
数据结构和算法:
1.原始数据 <<string keyword,int freq,int cnt>........>
keyword按照unsigned char建立节点:0~255 最多可以建立 255棵树
下层子节点按照1/4字节(0~16)建立子节点,主要是存储效率和查询效率上考虑,同时使用一个tag标识节点是否是最后一个节点
2.树结构示意图:
3.例如,原始数据中的字符串:我是中国人,分解后:0XA0B0C1D2...这样的十六进制串,按照2中的格式存储到树中
4.用户输入,用户每次按下键盘,触发js的keydown事件,发送一个请求到CGI,CGI将用户输入的内容送到SuggestSvr,suggestsvr分解用户输入串,根据编码去树中查询,最后将结果压入优先级队列中,返回搜索频率最高的前10个结果给用户,值得一提的是,我们对返回结果的用户的KeyWord做了分词处理,以保证用户的搜索结果更加丰富
在实际的运营环境中,我们从log中看到,用户直接输入拼音进行搜索导致失败的比率比较高,例如输入“shouji”来搜索“手机”,在上个版本中,我们做了一个优化,增加了对拼音的支持。
由于汉语的复杂性,支持拼音比仅仅支持汉字要复杂的多,即使在一些比较大型的搜索引擎上,不完善的地方也随处可见。
1.汉字到拼音映射表:
我们准备了一个基本词表,每个汉字->拼音的映射,词表非常小,只有几K,格式如下:
<拼音,汉字 freq,汉字 freq,汉字 freq>
例如<a 阿 13555 啊 13444 呵 13333......>
2.汉字短语到拼音的转化:
根据1中的映射表,我们可以建立这样一个二维数组:
char PY2HZ[65535][32]={.....}
一维下标表示一个汉字的编码,单个汉字是2字节组成,单个汉字的取值范围刚好在[0,65535],可以直接通过数组下标获取到汉字对应的拼音
3.建立索引:
1)针对用户日志中的keyword,首先转换成拼音
例如:我是中国人,根据每个汉字的编码获取到单个汉字的拼音,汉字直接用"‘"分隔,wo’shi‘zhong’guo‘ren
2)建内存树
以每个字母为一个节点建树,{a~z}{A~Z}最多64颗树,实际上远小于这个数字,一些不可见字符
typdef struct NODE{
char value;
Node *pBrother;
Node *pChild;
char ctag; //-3表示到达一个汉字拼音的结尾,-2表示当前节点的字母是一个声母
DocInfo *pDoc;//指向当前短语对应的文档列表,如果短语没有结束,指针为NULL
}Node,*pNode;
typdef DOCINFO{
int iDocId;
int iFreq;
}DocInfo,*pDocInfo;
结构示意图:“手表”和“手机”的存储结构
4)检索时同样把用户输入的拼音串在树中查找
几个挑战:
1.一字多音,例如阿,可以读a,可以读e,这就导致在二维数组中,对“阿”查找拼音的时候比较麻烦,这里的做法是根据频率选择一个搜索频率比较高的
2.同音短语,比如:手机和收集,对应的拼音都是"shou'ji",这种情况处理起来比较简单,在上面的Node结构中,有一个DocInfo *pDocInfo,以数组的方式记录了拼音对应的所有同音短语
3.简拼,比如:sj,对应的可能是"手机",“收集”,“四级"........这种情况,在上面的Node结构中,有一个ctag=-2标识声母,通过这种方式,可以很好的处理简拼
4.混拼,比如:nuojy,对应的是“诺基亚”,混拼的处理方式和简拼的方式比较类似
5.对于一些英文和拼音的处理,电子商务中包含很多英文名的商品名,如何区分英文和拼音还有简拼、混拼,其实这里的关键在于音节的划分,处理上没有太大的区别