[Qt C++] Mini Search Engine (倒排文件索引)

本文介绍了一个简易搜索引擎的实现过程,包括文件读取、单词处理、噪声词过滤、短语搜索等功能。通过对用户输入的单词或短语进行搜索,系统能够返回包含这些单词或短语的文章编号及其出现次数。



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();
}


评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值