这一节将介绍搜索功能入口程序TSESearch.cpp的第四步和第五步——检索关键词+结果排序。
其实,检索关键词非常简单,因为已经建立了倒排表mapBuckets,它是一个map结构,检索某个关键词,就是用map的find方法查询(下面代码中的第27行),没有什么需要介绍的。也因此,TSE中将检索关键词和结果排序在一起实现,也就是main函数中调用的函数:iQuery.GetRelevantRst(vecTerm,mapBuckets,setRelevantRst)。
下面就看该函数的源代码,分析TSE中检索关键词和结果排序的实现。TSE的结果排序方法很简单,就是采用的词频(Term frequency)进行排序的,关于词频排序可以到网上搜索TF-IDF词条进行学习,这里不做详细说明。简单说,词频排序就是:网页中某个关键词出现次数(词频)越高,说明该网页与该关键词相关性越高,因而在结果排序中越优。
下面的代码中加入了详细的注释(以“LB_C”开始的注释为我加入的)进行说明。
//LB_c: vecTerm中存储的用户查询字串分词以后的关键词,mapBucket为倒排表,setRelevantRst存储最终的查询的结果(里面存储// 的是结果网页的docid)bool CQuery::GetRelevantRst(vector<string> &vecTerm, map<string,string> &mapBuckets, set<string> &setRelevantRst) const{//LB_c: 临时存储已经查询的结果set<string> setSRst;bool bFirst=true;//LB_c: 分别对vecTerm中的每一个关键词进行查询vector<string>::iterator itTerm = vecTerm.begin();for ( ; itTerm != vecTerm.end(); ++itTerm ){//LB_c: setRelevantRst为已查询的结果,将setRelevantRst存入setSRst中,后面将用到。setSRst.clear();copy(setRelevantRst.begin(), setRelevantRst.end(), inserter(setSRst,setSRst.begin()));//LB_c: mapRstDoc是一个用于临时统计的map,string对应一个docID,int是该docID出现的次数(也就是当前关键词在docid// 的网页中出现的次数,也成为"词频",后面将称为"词频"), 后面是根据词频值对搜索结果进行排序的(即关键词出现次数// 越多的网页应该越"优")。map<string,int> mapRstDoc;string docid;int doccnt;//LB_c: 在倒排表中查询关键词(*itTerm)map<string,string>::iterator itBuckets = mapBuckets.find(*itTerm);//LB_c: 在倒排表中找到了该关键词if (itBuckets != mapBuckets.end()){//LB_c: 获取该关键词出现的文档ID列表(即倒排表记录的第二项,忘记了的朋友可以看看第2节中倒排文件的结构)string strBucket = (*itBuckets).second;string::size_type idx;idx = strBucket.find_first_not_of(" ");strBucket = strBucket.substr(idx);//LB_c: 循环从文档ID列表字符串中获取一个文档ID,并计算词频,插入mapRstDoc中while ( (idx = strBucket.find(" ")) != string::npos ) {docid = strBucket.substr(0,idx);if (docid.empty()) continue;doccnt = 0;//LB_c: 计算词频//LB_c: 到mapRstDoc中查询该docid是否出现过map<string,int>::iterator it = mapRstDoc.find(docid);//LB_c: 如果docid出现过if ( it != mapRstDoc.end() ){//LB_c: 获取词频((*it).second)加1存入doccntdoccnt = (*it).second + 1;//LB_c: 从mapRstDoc把该条记录删除,下面将重新插入mapRstDoc.erase(it);}//LB_c: 将该条记录重新插入到mapRstDoc,其实先删除再插入这条记录的结果就是docid的词频加了1//LB_c: 这里应该有点问题! 如果docid没出现过,那么doccnt的值为0,则插入到mapRstDoc的对应于docid// 的词频0,所以前面doccnt的初值是不是应该为1呢?mapRstDoc.insert( pair<string,int>(docid,doccnt) );//LB_c: 去掉分析过的docid更新strBucket,继续分析下一个文档IDstrBucket = strBucket.substr(idx+1);}//LB_c: 下面这部分是处理strBucket中最后一个docid,因为while循环结束时,最后一个docid还没有处理// remember the last onedocid = strBucket;doccnt = 0;map<string,int>::iterator it = mapRstDoc.find(docid);if ( it != mapRstDoc.end() ){doccnt = (*it).second + 1;mapRstDoc.erase(it);}mapRstDoc.insert( pair<string,int>(docid,doccnt) );}//LB_c: 这一部分处理完后,mapRstDoc存储的是一系列docid和该docid的词频。// sort by term frequencty//LB_c: 这部分是对刚才的带有词频的文档查询结果mapRstDoc进行了排序,排序结果存入到newRstDoc中。//LB_c: 注意newRstDoc的类型,第一个域为int表示docid的词频,第二个域是string表示docid,第三个域// 是排序规则----以键值(词频)的降序排列,注意newRstDoc是multimap,也就是键值可以重复。multimap<int, string, greater<int> > newRstDoc;map<string,int>::iterator it0 = mapRstDoc.begin();for ( ; it0 != mapRstDoc.end(); ++it0 ){newRstDoc.insert( pair<int,string>((*it0).second,(*it0).first) );}//LB_c: 这部分是将当前关键词(*itTerm)的排序查询结果newRstDoc插入到最终的查询结果setRelevantRst中,// 这里要参考前面的关键词查询结果。multimap<int,string>::iterator itNewRstDoc = newRstDoc.begin();//LB_c: 将最终的查询结果setRelevantRst清空setRelevantRst.clear();//LB_c: 循环读取newRstDoc中的每一条记录(这些记录是按docid的词频排序的),根据情况插入到最终结果中for ( ; itNewRstDoc != newRstDoc.end(); ++itNewRstDoc ){//LB_c: 获取该条记录的docidstring docid = (*itNewRstDoc).second;//LB_c: 如果当前关键词是第一个查询的关键词,则直接插入到结果集中if (bFirst==true) {setRelevantRst.insert(docid);continue;}//LB_c: 如果不是第一个关键词查询,则看已查询结果集setSRst中是否有该docid,也就是前面查询的关键词// 有没有出现在docid的网页中。这里也体现了TSE搜索的规则: 只有所有关键词都出现的网页才有效,部分关键// 词出现的网页不作为搜索结果。如果setSRst中有该docid说明docid的网页也包含前面查询的关键词, 则将// docid插入到最终结果集setRelevantRst中。if ( setSRst.find(docid) != setSRst.end() ){setRelevantRst.insert(docid);}}//LB_c: 这里思考一下! 首先将setRelevantRst清空,然后将当前关键词(*itTerm)的排序结果newRstDoc插入到最终结果集// setRelevantRst中。也就是最终的结果排序是以最后一个查询关键词的词频排序的,即最后一个关键词出现次数多的网页// 排在前面。bFirst = false;}return true;}
这一节之后,关于TSE查询服务子系统的介绍就只剩下最后一节——显示搜索结果了,本来想年前把它写完的,由于明天回家过年了,是在没有时间了,所以只能年后再继续了。回家happy去啦~~ 也预祝各位读者新年快乐,工作顺利~~
By: