index.h
#ifndef INDEX_H
#define INDEX_H
#include<utility>
#include<QObject>
#include<map>
#include<string>
class QPushButton;
class QWidget;
class QLineEdit;
class QVBoxLayout;
class QHBoxLayout;
class QLabel;
using namespace std;
class index : public QObject
{
Q_OBJECT
private:
int fileNumber;//统计文件数目
int pageNumber;//当前页码数
int maxPageNumber;//最大页码数
int tablesize;
map<string, int>tmp_map; //临时map,存放一个文章的map
map<string, vector<pair<int, int> > >table; //建立单词与(文章编号,单词出现个数)的索引
QString ans;//结果字符串
QWidget *window;//主窗口
QPushButton *button;//搜索按钮
QPushButton *lastbutton;//上一页按钮
QPushButton *nextbutton;//下一页按钮
QVBoxLayout *vlayout;//垂直布局
QHBoxLayout *hlayout1;//水平布局1
QHBoxLayout *hlayout2;//水平布局2
QLineEdit *line;//输入栏
QLabel *label;//结果标签
void readAllFile();//读取所有文件
void setLayout();//窗口布局
bool isInFile(int number, string str);//判断短语是否在特定文件中
public:
index(QObject *parent = 0);
~index();
private slots:
void enableButton(const QString &text);//没有文字的时候禁用搜索按钮
void search();//搜索
void next();//下一页
void last();//上一页
};
#endif // INDEX_H
index.cpp
#include "index.h"
#include<iostream>
#include<QFile>
#include<QDir>
#include<cctype>
#include<QPushButton>
#include<QVBOxLayout>
#include<QHBOxLayout>
#include<QLabel>
#include<QWidget>
#include<QLineEdit>
#include<QByteArray>
#include<algorithm>
using namespace std;
//构造函数
index::index(QObject *parent)
: QObject(parent)
{
pageNumber = 0;
setLayout();
readAllFile();
}
//界面布局
void index::setLayout()
{
//新建控件对象
hlayout1 = new QHBoxLayout();
hlayout2 = new QHBoxLayout();
vlayout = new QVBoxLayout();
window = new QWidget();
button = new QPushButton("search");
lastbutton = new QPushButton("previous page");
nextbutton = new QPushButton("next page");
line = new QLineEdit();
label = new QLabel();
//连接信号与槽
button->setEnabled(false);
connect(line, SIGNAL(textChanged(const QString &)), this, SLOT(enableButton(const QString &)));
//没有文字的时候禁用搜索按钮
connect(button, SIGNAL(clicked()), this, SLOT(search()));//连接搜索按钮与搜索事件
connect(nextbutton, SIGNAL(clicked()), this, SLOT(next()));//连接下一页按钮与翻页事件
connect(lastbutton, SIGNAL(clicked()), this, SLOT(last()));//连接上一页按钮与翻页事件
//摆放文本框和搜索按钮
hlayout1->addStretch();
hlayout1->addWidget(line);
hlayout1->addWidget(button);
hlayout1->addStretch();
//摆放翻页按钮
hlayout2->addStretch();
hlayout2->addWidget(lastbutton);
hlayout2->addWidget(nextbutton);
//竖直布局
vlayout->addLayout(hlayout1);
vlayout->addWidget(label);
vlayout->addStretch();
vlayout->addLayout(hlayout2);
//设置参数并显示窗口
window->setLayout(vlayout);
window->setWindowTitle("Mini Search Engine");
window->setFixedSize(700, 800);
window->show();
}
//析构函数
index::~index() {
delete button;
delete line;
delete lastbutton;
delete nextbutton;
delete hlayout1;
delete hlayout2;
delete vlayout;
delete window;
}
//没有文字的时候禁用搜索按钮
void index::enableButton(const QString &text)
{
button->setEnabled(!text.isEmpty());
}
//比较函数
static bool cmp(const pair<int, int> &x, const pair<int, int> &y)
{
//定义比较方式为:如果出现次数多,排在前面
//如果出现次数一样多,编号小的在前面
if (x.second>y.second)return true;
else if (x.second == y.second) {
return x.first<y.first;
}
else return false;
}
//下一页
void index::next()
{
//未初始化或不存在下一页,返回
if (!pageNumber)return;
if (pageNumber<maxPageNumber) {
//页数加一
pageNumber++;
//文本内容存在结果字符串中,通过页码来显示对应下标的字符子串
//分为最大页码和非最大页码两种情况分析
if (pageNumber == maxPageNumber) {
int size = (9 * tablesize + tablesize / 8 + 1) % 2190;
label->setText(ans.mid(2190 * (pageNumber - 1), size));
}
else {
label->setText(ans.mid(2190 * (pageNumber - 1), 2190));
}
}
return;
}
//上一页
void index::last()
{
//未初始化或不存在上一页,返回
if (pageNumber == 0 || pageNumber == 1)return;
pageNumber--;//页数减一
//文本内容存在结果字符串中,通过页码来显示对应下标的字符子串
label->setText(ans.mid(2190 * (pageNumber - 1), 2190));
return;
}
//搜索
void index::search()
{
bool flag = false;//判断输入是否是短语 (非单词)
int count = 0;//记录短语的个数
int number1 = 0;//第一个阈值,用于衡量是否是噪声单词
int number2 = 0;//第二个阈值,用于衡量是否是噪声单词
QString text = line->text();//获取文本框文字
string str = text.toStdString();//转换为string类型
string tmp_str = str;
std::transform(str.begin(), str.end(), str.begin(), ::tolower);//转换为小写
ans.clear();//清空答案字符串
unsigned int in = str.find(" ");//寻找输入字符串是否含有空格
//如果有,它是短语
if (in != string::npos) {
flag = true;
str = str.substr(0, in); //更新str为短语的第一个单词,原短语在tmp_str中
}
if (table.find(str) == table.end()) { //如果文档中不存在该单词
ans = "don't exist!";//输出不存在,并返回
label->setText(ans);
return;
}
unsigned int i;
//在搜索的是单词的情况下,对用户搜索的单词,按其在文档中出现次数排序
if (!flag)sort(table[str].begin(), table[str].end(), cmp);
//检测是否是噪声单词
if(!flag){
//number2统计了单词在所有文档中出现的总次数
//number1统计了单词在所有文档中出现3次以上的次数
for (i = 0; i<table[str].size(); i++){
number2++;
if(table[str][i].second>3)number1++;
}
//当number2大于总文档数的0.1倍或者number1大于总文档数的0.35时
//我们认为它是噪声词汇
if(number1>fileNumber*0.1||number2>fileNumber*0.35){
ans = "noisy word";
label->setText(ans);
return;
}
}
//遍历单词出现的每个文档
for (i = 0; i<table[str].size(); i++) {
//如果搜索的是短语
if (flag) {
//寻找是否存在短语,不存在继续搜索,存在则继续处理该字符串
if (!isInFile((int)table[str][i].first, tmp_str))continue;
//超过8个单词换行
if (count % 8 == 0)ans = ans + "\n";
count++;
}
//如果搜索的是单词
else if (i % 8 == 0)ans = ans + "\n";//超过8个单词换行
//将文章编号输出到窗口,根据数字大小做对应的补0操作,以达到对齐
if (table[str][i].first<10)
ans = ans + " 00" + QString::number(table[str][i].first);
else if (table[str][i].first<100)
ans = ans + " 0" + QString::number(table[str][i].first);
else
ans = ans + " " + QString::number(table[str][i].first);
//如果搜索的是短语,不输出出现次数
if (flag) {
ans = ans + " ";
}
//如果搜索的是单词,输出出现次数
//同样,根据数字大小做对应的补0操作,以达到对齐
else {
if (table[str][i].second<10)
ans = ans + "(" + QString::number(table[str][i].second) + ") ";
else if (table[str][i].second<100)
ans = ans + "(" + QString::number(table[str][i].second) + ") ";
else
ans = ans + "(" + QString::number(table[str][i].second) + ")";
}
}
//在是单词和短语的不同情况下,设置tablesize大小
if (flag) {
tablesize = count;
if (count == 0) { //未搜索到输出不存在
ans = "don't exist!";
label->setText(ans);
return;
}
}
else tablesize = (int)table[str].size();
//计算得到一页显示的字符串大小
int size = std::min(9 * tablesize + tablesize / 8 + 1, 2190);
//显示一页的字符串
label->setText(ans.mid(0, size));
//计算得到最大页码数
maxPageNumber = (9 * tablesize + tablesize / 8 + 1) / 2190 + 1;
//初始化pagenumber为1
pageNumber = 1;
}
//判断短语是否在特定文件中
bool index::isInFile(int number, string str)
{
if (str == "")return false;
QString name;
//格式化文件名称
if (number<10)name = "00" + QString::number(number);
else if (number<100)name = "0" + QString::number(number);
else name = QString::number(number);
//打开文件
QFile *file = new QFile();
QString path("C:/txt/text (" + name + ").txt");
file->setFileName(path);
file->open(QIODevice::ReadOnly);
while (!file->atEnd()) {//遍历文件
QString qstr = file->readAll();//读取所有内容
string s = qstr.toStdString();//化为string类型
//找到子串,返回真,否则返回假
if (s.find(str) != string::npos) {
return true;
}
else return false;
}
return false;
}
//读取所有文件
void index::readAllFile()
{
QString tmp_str = "C:/txt";//根目录,可以修改
fileNumber = 1;//统计文件数目
QFile *file = new QFile();//声明文件
//得到根目录下所有文件的名称,存于list中
QDir d(tmp_str);
d.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks | QDir::AllDirs);
const QFileInfoList list = d.entryInfoList();
if(list.isEmpty()){
ans = "Error:\nPlease put the file 'txt' in disk C\nfor example: C:\\txt";
label->setText(ans);
return;
}
QFileInfoList::const_iterator iterator = list.begin();
iterator += 2;//跳过空文件名
while (iterator != list.end()) {//遍历list
QString path(tmp_str + "/" + (*iterator).fileName());//得到完整文件路径名
if (path == tmp_str + "/." || path == tmp_str + "/..")continue;//跳过无意义文件名
//打开文件
file->setFileName(path);
file->open(QIODevice::ReadOnly);
while (!file->atEnd()) {//遍历文件
QString qstr = file->readAll();//读取所有内容
QByteArray ba = qstr.toLatin1();
char *ch = ba.data();//转换为char* ,方便对每个字符进行底层操作
string str = "";//str用于记录每个单词
bool flag = false;//flag用于记录是否有不期望的单引号字符
for (int i = 0; ch[i] != '\0'; i++) {//遍历每个字符
if (isupper(ch[i])) {//大写先转为小写,再存入单词字符串
ch[i] -= 'A' - 'a';
str = str + ch[i];
}
else if (islower(ch[i])) {//小写直接存入单词字符串
str = str + ch[i];
}
//读到单引号,同样存入单词字符串,但设flag,在以后抛弃该单词
else if (ch[i] == '\'') {
str = str + ch[i];
flag = true;
}
else {//读到换行符 空格等
if (str == "")continue;//跳过空字符串
if (flag) {//抛弃含单引号的单词
str = "";
flag = false;
continue;
}
//第一次读到这个单词,初始化次数为1
if (tmp_map.find(str) == tmp_map.end()) {
tmp_map[str] = 1;
}
//非第一次读到这个单词,次数加1
else tmp_map[str]++;
//单词字符串重置为0,开始存储下一个单词
str = "";
}
}
}
map<string, int >::iterator it;//声明索引迭代器
//将临时的索引存入正式的索引中
for (it = tmp_map.begin(); it != tmp_map.end(); it++) {
table[it->first].push_back(make_pair(fileNumber, it->second));
}
file->close();//关闭文件
iterator++; //文件迭代器加一
tmp_map.clear();//清空临时索引
fileNumber++;//文件编号加一
}
return;
}
main.cpp
#include "index.h"
#include <QApplication>
using namespace std;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
index w;
return a.exec();
}
本文介绍了一个简易搜索引擎的实现过程,包括文件读取、单词处理、噪声词过滤、短语搜索等功能。通过对用户输入的单词或短语进行搜索,系统能够返回包含这些单词或短语的文章编号及其出现次数。





