文本查询编程实验


本文是本人大一期间在校学习C++课程时所撰写的实验报告的摘录,由于刚上大学,刚接触计算机编程方面的相关知识,故可能会有很多不足甚至错误的地方,还请各位看官指出及纠正。
本文所涉及到的“教材”指:
电子工业出版社《C++ Primary中文版(第5版)》
如需转载或引用请标明出处。

设计思路

文本查询程序

该程序要求用户在一个给定的文本中输入要查询的单词,查询的结果是该单词在文本中出现的次数及其所在的行的列表。根据这个要求,可以预见到该程序需要做到:

  • 当程序读入文件时,要记住单词出现的每一行。因此,程序需要逐行读取输入的文件,并且将每一行分解为独立的单词;
  • 输出结果时,要能提取每个单词所关联的行号,且行号必须按一定顺序出现且无重复,还要能够打印给定行号中的文本。

数据结构

使用之前学过的各种标准库类型可以简单且方便地实现上述要求:

  • 使用一个存储string地vector用于保存输入文本地拷贝,其中的每一个元素是输入文本对应的每一行;
  • 使用一个istringstream,将每一行切分为一个个的单词;
  • 使用一个set来保存每个单词在文本中出现的行号,从而保证了输出的规范性;
  • 使用一个map来讲每个单词与它出现行号的集合set关联起来,以方便地提取任意单词的行号集合。

上述的vector、set和map使用智能共享指针shared_ptr来进行相关操作。

此外,定义一个TextQuery类用于执行读入文本、查询等的相关操作,还定义一个QueryResult类用于表示查询的结果。这两个类有助于模块化代码,使整个解决方案更加有效。

代码细节

总的来说,整个程序的运行分成以下五步:

  1. 读取指定文本,按行划分,存入vector
  2. 划分每一行,形成从每个单词到其出现的位置的集合的映射
  3. 用户输入单词,执行查询操作
  4. 从已分好的映射中读取对应的集合
  5. 输出结果

其它地方还实现了规范化文本、新定义begin、end函数等操作,具体细节详见代码注释部分。

源代码

TextQuery.h

#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include <memory>
#include <string>
#include <vector>
#include <map>	//使用map
#include <set>	//使用set
#include <fstream>
#include "QueryResult.h"

class QueryResult; //因为TextQuery有成员函数返回QueryResult,故需事先声明                                                                                                                                                                  
class TextQuery
{
public:
	typedef std::vector<std::string>::size_type line_no;	//line_no是的vector的size_type,用于指示行号
	TextQuery(std::ifstream&);								//TextQuery的构造函数
	QueryResult query(const std::string&) const;			//执行查询操作
    void display_map();										//用于打印查找映射的文本的调试函数
private:
    std::shared_ptr<std::vector<std::string>> file;					//file指向存储文本每一行的vector
    std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;	//将每个要查询的单词映射到存储其出现行号的集合
    static std::string cleanup_str(const std::string&);				//将文本中的标点符号删去,并小写所有字母
};
#endif

TextQuery.cpp

#include "TextQuery.h"
#include "make_plural.h"	//使用复数形式输出函数

#include <cstddef>
#include <memory>
#include <sstream>	//使用istringstream,将行切分为单词
#include <string>                                                                                                                                                                
#include <vector>
#include <map>		//使用map
#include <set>		//使用set
#include <iostream>
#include <fstream>
#include <cctype>	//使用字符识别函数
#include <cstring>
#include <utility>

using namespace std;

//下面使用typedef简化代码 
//wmType是将查询单词映射到出现行号的map
typedef map<string, shared_ptr<set<TextQuery::line_no>>> wmType;
//wmIter是wmType的常量迭代器
typedef wmType::const_iterator wmIter;
//lineType是指向存储行号的set的共享指针
typedef shared_ptr<set<TextQuery::line_no>> lineType;
//lineIter是存储行号的set的常量迭代器
typedef set<TextQuery::line_no>::const_iterator lineIter;
                                                                                                                                                            
//构造函数:读取文本并建立起每一种单词映射到其出现的行号的集合map
TextQuery::TextQuery(ifstream &is): file(new vector<string>)
{
    string text;						//指示当前行
    while (getline(is, text))			//对于文本中的每一行
	{
		file->push_back(text);			//将该行存储到file中
		int n = file->size() - 1;		//n为当前这一行的行号
		istringstream line(text);		//string读入流line,用于将当前行划分为单词
		string word;					//指示当前单词  
		while (line >> word)			//对于当前行的每一个单词
		{
            word = cleanup_str(word);				//规范单词
            //如果该单词是第一次出现,则会在wm新建一个该单词映射到行号的键值对
            lineType &lines = wm[word];				//lines指向一个存储行号的set
            if (!lines)								//当第一次遇见单词时,lines指向NULL
                lines.reset(new set<line_no>);		//因此分配一个新的set用于存储该单词出现的行号
            lines->insert(n);						//将出现的行号放入到lines中
		}
	}
}                                                                                                                                                                 

//移除标点符号并将字母小写化,以便查询可以无视大小写
string TextQuery::cleanup_str(const string &word)
{
    string ret;
    for (string::const_iterator it = word.begin(); it != word.end(); ++it)
	{
        if (!ispunct(*it))			//如果不是标点符号
            ret += tolower(*it);	//则小写化该字符
    }
    return ret;						//返回处理后的单词
}

QueryResult TextQuery::query(const string &sought) const
{
	static lineType nodata(new set<line_no>);		//nodata是指向空行号集的lineType,在找不到单词时返回
    wmIter loc = wm.find(cleanup_str(sought));		//loc接受map成员函数find的返回值

	if (loc == wm.end())	//如果没找到
		return QueryResult(sought, nodata, file);			//则用nodata构造一个查询结果QueryResult作为返回值
	else                                                                                                                                                            
		return QueryResult(sought, loc->second, file);	//否则返回用对应行号集合set构造的查询结果QueryResult
}

ostream &print(ostream &os, const QueryResult &qr)
{
    //如果单词找到了,则输出出现次数和所有出现的位置
    os << qr.sought << " occurs " << qr.lines->size() << " "
       << make_plural(qr.lines->size(), "time", "s") << endl;

    //输出该单词出现的每一行
	for (lineIter num = qr.lines->begin(); num != qr.lines->end(); ++num) 
		//编号从0开始,因此输出要加1
        os << "\t(line " << *num + 1 << ") " << *(qr.file->begin() + *num) << endl;
	return os;
}

//用于调试代码的函数
void TextQuery::display_map()
{
    wmIter iter = wm.begin(), iter_end = wm.end();
                                                                                                                                                                  
    //对于map中的每个单词
    for ( ; iter != iter_end; ++iter) {
        cout << "word: " << iter->first << " {";

        //获取位置向量作为常量引用以避免复制它
        lineType text_locs = iter->second;
        lineIter loc_iter = text_locs->begin(),
                        loc_iter_end = text_locs->end();

        //打印此单词的所有行号
        while (loc_iter != loc_iter_end)
        {
            cout << *loc_iter;
            if (++loc_iter != loc_iter_end)
                 cout << ", ";
         }
         cout << "}\n";	//结束输出
    }
    cout << endl;//结束打印整个map
}

QueryResult.h

#ifndef QUERYRESULT_H
#define QUERYRESULT_H
#include <memory>
#include <string>
#include <vector>
#include <set>	//使用set
#include <iostream>

//QueryResult类表示查询结果
class QueryResult
{
friend std::ostream& print(std::ostream&, const QueryResult&);		//声明友元函数print
public:
	typedef std::vector<std::string>::size_type line_no;				//line_no是的vector的size_type,用于指示行号
	typedef std::set<line_no>::const_iterator line_it;					//set存储行号,line_it是它的迭代器
	QueryResult(std::string s,		//构造查询结果类
	            std::shared_ptr<std::set<line_no> > p, 
	            std::shared_ptr<std::vector<std::string> > f):
		sought(s), lines(p), file(f) { }
	std::set<line_no>::size_type size() const  { return lines->size(); }	//返回单词出现的次数
	line_it begin() const { return lines->begin(); }							//返回行号集合的开头迭代器                                                                                                                                                          
	line_it end() const   { return lines->end(); }								//返回行号集合的尾后迭代器
	std::shared_ptr<std::vector<std::string>> get_file() { return file; }
private:
	std::string sought;										//指示本次查询结果对应的单词
	std::shared_ptr<std::set<line_no>> lines;			//lines指向存储行号的集合set
	std::shared_ptr<std::vector<std::string>> file;		//file是指向存储文本的vector
};

std::ostream &print(std::ostream&, const QueryResult&);	//打印查询结果
#endif

make_plural.h

#include <cstddef>
#include <string>
#include <iostream>

using namespace std;

#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H

//输出时如果ctr大于1,则输出word对应的复数形式                                                                                                                                                                  
inline string make_plural(size_t ctr, const string &word, const string &ending)
{
	return (ctr > 1) ? word + ending : word;
}

#endif

querymain.cpp

#include <string>
#include <fstream>
#include <iostream>

using namespace std;

#include <cstdlib>  //使用EXIT_FAILURE
#include "TextQuery.h"
#include "make_plural.h"

void runQueries(ifstream &infile)
{
	//infile是我们想要查询的文本的文件输入流
TextQuery tq(infile);//存储文本,并构造其中每个单词到其出现位置集合的映射                                                                                                                                                   
    //提示用户输入,并输出查询结果
    while (true)
	{
        cout << "enter word to look for, or q to quit: ";
        string s;
        if (!(cin >> s) || s == "q") break;		//当输入空行或“q”时关闭程序
        print(cout, tq.query(s)) << endl;		//输出查询结果
    }
}

//该程序接受一个参数,此参数指定要查询的文件
int main(int argc, char **argv)
{
	//infile是我们想要查询的文本的文件输入流
    ifstream infile;
	//由于open函数无返回值,因此我们使用逗号运算符去查询infile调用open后的状态
    if (argc < 2 || !(infile.open(argv[1]), infile))
	{	//如果文件打开失败,将错误输出到标准错误流
        cerr << "No input file!" << endl;
        return EXIT_FAILURE;
}                                                                                                                                                     
	runQueries(infile);		//打开成功,进行查询

	system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值