Project: finder, src: Finder.cc
検索エンジン本体ロジック。まだまだ改良の余地あり。
#include "Finder.h"
#include "docid.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstddef>
#include <cstdlib>
#include <vector>
#include <algorithm>
/**
* コンストラクタ
*/
Finder::Finder(const std::string &docfile, const std::string &filename, bool bTrunc) : bptree(filename, bTrunc), analyzeCount(0), bCreate(true) {
int flags = O_RDWR | O_CREAT;
if (bTrunc) {
flags |= O_TRUNC;
}
docfd = open(docfile.c_str(), flags, 0644);
fd = bptree.getFileDescriptor();
}
/**
* ドキュメントを解析して保存する
*/
void Finder::add(unsigned long long id) {
// DOC アーカイブから文書を取得する
int len8;
char *utf8 = getDocFromId(docfd, id, &len8);
if (utf8 == 0) return; // 文書がなければ終了
// 解析結果をローカルに保存
work.analyze(id, utf8, len8);
if (++analyzeCount > MAX_DOCUMENT_LIST) {
// 一定数たまったので二次媒体に移す
flush();
}
free(utf8);
}
/**
* 解析結果の hash table を B++ Tree に保存する
*/
void Finder::flush() {
work.save2BPTree(bptree);
work.clear();
analyzeCount = 0;
}
/**
* 入力文字列が文書集合に含まれているかどうか調査し、その結果を返す
*/
std::list<Document> Finder::find(const char *utf8, int len8) {
std::list<Document> result;
// UCS4 文字列に変換
int len4;
UCS4 *ucs4 = ucs4FromUtf8(utf8, len8, &len4);
if (ucs4 == 0) return result;
// 長さが足りない場合は空を返す
if (len4 < NGRAM_SIZE) {
return result;
}
// 検索文字列を含む「列」を作成する
std::vector<BPTree::ObjectStream> objStreamArray;
for (int i = 0; i <= len4 - NGRAM_SIZE; ++i) {
NGram key(ucs4 + i);
objStreamArray.push_back(bptree.find(key));
}
std::vector<DocumentList> doclistArray;
std::vector<BPTree::ObjectStream>::iterator itr = objStreamArray.begin();
for ( ; itr != objStreamArray.end(); ++itr) {
// この段階で Document がない場合は空を返す
if (itr->isEOF()) return result;
DocumentList doclist;
itr->load(doclist);
doclistArray.push_back(doclist);
}
std::vector<DocumentList::iterator> docItrArray;
std::vector<DocumentList>::iterator j = doclistArray.begin();
for ( ; j != doclistArray.end(); ++j) {
docItrArray.push_back((*j).begin());
}
Document doc;
unsigned long long nextId;
if (docItrArray[0] == doclistArray[0].end()) return result;
doc = *(docItrArray[0]);
while (find(&nextId, result, doc, objStreamArray, doclistArray, docItrArray)) {
// nextId まで読み飛ばす
while (doc.getDocid() < nextId) {
if (!incrementDocItr(objStreamArray[0], doclistArray[0], docItrArray[0])) return result;
doc = *(docItrArray[0]);
}
}
return result;
}
/**
* ObjectStream 上、「次」の Document を探索する
*/
bool Finder::incrementDocItr(BPTree::ObjectStream &os, DocumentList &doclist, DocumentList::iterator &itr) {
if (itr != doclist.end()) {
// まだ iterator が残っている
++itr;
if (itr != doclist.end()) return true;
}
// ObjectStream から取得する
if (os.isEOF()) return false; // もうなかった
if (!os.load(doclist)) return false; // load に失敗した場合
itr = doclist.begin();
return (itr != doclist.end());
}
/**
* 各「列」を比較して並行成分を除いて同じ index があったら result に追記する
*/
bool Finder::find(unsigned long long *nextId, std::list<Document> &result, Document &doc, std::vector<BPTree::ObjectStream> &objStreamArray, std::vector<DocumentList> &doclistArray, std::vector<DocumentList::iterator> &docItrArray) {
std::size_t sz = objStreamArray.size();
std::set<int> bookmarks(doc.getBookmarks());
for (int m = 1; m < sz; ++m) {
Document mdoc = *(docItrArray[m]);
while(true) {
if (mdoc.getDocid() >= doc.getDocid()) break;
if (!incrementDocItr(objStreamArray[m], doclistArray[m], docItrArray[m])) {
// 各「列」にこれ以上文書がない場合は中断する
return false;
}
mdoc = *(docItrArray[m]);
}
if (mdoc.getDocid() > doc.getDocid()) {
// 文書 ID を追い越した場合は探索元の ID をそこまで上げる
// 探索自体は継続する
*nextId = mdoc.getDocid();
return true;
}
const std::set<int> &mdocbm = mdoc.getBookmarks();
std::set<int> tmpset;
for (std::set<int>::const_iterator j = mdocbm.begin(); j != mdocbm.end(); ++j) {
int index = (*j) - m;
if (bookmarks.find(index) != bookmarks.end()) {
// 両方の集合に index が含まれていれば検索先候補とする
tmpset.insert(index);
}
}
bookmarks = tmpset;
}
// この時点で bookmarks に含まれている index は
// すべての「列」に含まれている成分
// 文書 ID と bookmarks から結果を構成する
if (!bookmarks.empty()) {
result.push_back(Document(doc.getDocid(), bookmarks.begin(), bookmarks.end()));
}
// 次の ID を探索する準備をする
if (!incrementDocItr(objStreamArray[0], doclistArray[0], docItrArray[0])) {
// これ以上はない
return false;
} else {
// nextId に次の ID を設定する
doc = *(docItrArray[0]);
*nextId = doc.getDocid();
return true;
}
}