C++Primer 15章:文本查询程序再探

第一篇blog

半路出家程序员,之前自己做笔记,但总觉得不过瘾。
最近在看c++,第15章综合性较强,相对于前面章节内容较难理解,索性写第一篇blog,把细节介绍清楚,加深印象。

也算是对自己的约束,前两年浪费的时间太多了,这几天思考了很多问题,最后的结论就是踏实的完备自己的技术栈。
开始吧~

需求分析

- 以如下文本为例进行操作:

Alice Emma has long flowing red hair.
Her Daddy says when the wind blows
through her hair, it looks almost alive,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
"Daddy, shush, there is no such thing,"
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

- 支持的操作包括:

  1. 单词查询, 用于得到匹配某个给定string 的所有行:
  2. 逻辑非查询, 得到不匹配查询条件的所有行;
  3. 逻辑或查询, 返回匹配两个条件中任意一个的行:
  4. 逻辑与查询, 返回匹配全部两个条件的行:
  5. 混合逻辑查询 ,返回满足要求的行;

- 例如:
通过如下操作

 fiery & bird | wind

可得

Executing Query for: ((fiery & bird) | wind)
((fiery & bird) | wind) occurs 3 times
(line 2) Her Daddy says when the wind blows
(line 4) like a fiery bird in flight
(line 5) A beau七iful fiery bird, he tells her,

简要分析过程

  1. 类的设计参照书本,这里只写自己的理解;

  2. 5种操作中,1-4是基础操作,1-4实现了,5自然就实现了;

  3. 共同点:
    - 都需要传入需要查询的对象
    - 都需要查询操作
    - 都需要行号与关键词的关系
    - 都需要打印查询结果

  4. 不同点:
    - 不同运算符对应的查询对象格式不同(与、或、非、单个单词)
    - 不同运算符所需的查询操作不同

  5. 综上,不同点与共同点对应的部分在基类中应该用虚函数,并且在派生类中分别实现。

代码实现

来自12章的基础功能

这部分基础功能12章实现的。
查询返回的结果为QueryResult类,包含了所查的内容、行号set、行字符串vector

/*输出结果,供输出使用*/
class QueryResult
{
	friend ostream& print(ostream&, const QueryResult&);
public:
	using line_no = vector<string>::size_type;//行号,等同unsigned 类
	friend ostream& operator<<(ostream&, const QueryResult&);//运算符<<定义,输出查询结果

public:
	QueryResult(const string& s, shared_ptr<std::set<line_no>> set,
		shared_ptr<vector<string>> v)
		: sought(s), lines(set), file(v) //构造函数,初始化变量
	{
	}
	auto begin() { return lines->begin(); }//set的第一个
	auto end() { return lines->end(); }//set最后元素的下一个
	auto get_file() { return file; }//按行保存文本
private:
	string sought;//查询的内容
	shared_ptr<std::set<line_no>> lines;//用set保存行号,自动排序
	shared_ptr<vector<string>> file;//按行保存文本
};

TextQuery类,重点在构造函数和query查询函数;

class TextQuery
{
public:
	using line_no = vector<string>::size_type;//行号,等同unsigned 类
	TextQuery(ifstream&);//构造函数,参数为输入文件
	QueryResult query(const string&) const;//具体的查询函数

private:
	shared_ptr<vector<string>> file;//保存输入的vector的智能指针
	map<string, shared_ptr<set<line_no>>> result;//单词及其对应的行号(一个单词会多行出现,所以行号是一个set)
};

在构造函数中完成以下功能
1、将输入文件的文本按行分解,记录当前的行号;
2、将每一行字符串逐个单词分解,再将每个单词与对应的行号成对保存在map中。

TextQuery::TextQuery(ifstream& is) : file(new vector<string>)//新建空的vector存放文件中的文本
{
	string text;//一行字符串
	while (getline(is, text))//需要添加头文件文件流#include <fstream>,否则会报错没有匹配的参数
	{
		file->push_back(text);//当前行字符串存到vector里面
		int n = file->size() - 1;//当前行号
		istringstream line(text);//分解行文本为单词
		string word;//一个单词字符串
		while (line >> word)
		{
			auto& lines = result[word];//根据当前word返回一个shared_ptr<set<line_no>>,即存放行号的set
			if (!lines)//如果之前没有出现过该关键词,此值为空
			{
				lines.reset(new set<line_no>);//分配一个新的shared_ptr
			}
			lines->insert(n);//将当前行号n插入到set中
		}
	}
}

查询函数中完成以下功能
用关键词(搜索对象)查找上述构造函数中的map,找到对应的行号set,如果结果不存在,返回默认值。

QueryResult TextQuery::query(const string& sought) const 
{
	static shared_ptr<set<line_no>> nodata(new set<line_no>);
	auto loc = result.find(sought);//查找关键词,返回元素对应的迭代器

	if (loc == result.end())//查找结束
	{
		return QueryResult(sought, nodata, file);
	}
	else
	{
		return QueryResult(sought, loc->second, file);//loc->second根据当前word返回一个shared_ptr<set<line_no>>,即存放行号的set
	}
}

新功能

Query_base类
Query是Query_base的对外接口,需要访问private,所以声明成友元。

//基类
class Query;
class Query_base
{
	friend class Query;
protected:
	using line_no = TextQuery::line_no;
	virtual ~Query_base() = default;
private:
	virtual QueryResult eval(const TextQuery&) const = 0;//返回查询结果
	virtual string rep() const = 0;//生成要查询的内容
};

Query类

class Query
{
	friend Query operator~(const Query&);
	friend Query operator|(const Query&, const Query&);
	friend Query operator&(const Query&, const Query&);
public:
	//Query(const string&);
	Query(const string& s) : q(new WordQuery(s)) {}//单个单词
	QueryResult eval(const TextQuery& t) const
	{
		return q->eval(t);
	}//虽然q是Query_base类的,但是由于其中存在纯虚函数,所以Query_base类型的对象不存在,所以实际是指向四种继承类之一,eval函数也是继承类重定义之后的。
	string rep() const { return q->rep(); }//对应4种类的需要查询的对象
private:
	Query(shared_ptr<Query_base> query) : q(query) {}//是为了And、Or、Not类准备的构造函数
	shared_ptr<Query_base> q;//由于Query_base是基类,所以他的智能指针可以指向其继承类
};

WordQuery类
eval函数调用TextQuery的query;
rep函数直接返回所要查询的对象(单个单词);

//查找单个字符串
class WordQuery : public Query_base
{
	friend class Query;
	WordQuery(const string& s) :query_word(s) {}
	QueryResult eval(const TextQuery& t) const
	{
		return t.query(query_word);//返回查询结果
	}
	string rep() const { return query_word; }//返回当前查找的单词
	string query_word;
};

BinaryQuery类
对于与、或、非运算,实际实现的方式还是处理单个单词的字符串,然后将各个单词的查询结果进行与、或、非操作;
这个类表示与、或运算符的左右两元素组合;

class BinaryQuery : public Query_base
{
protected:
	BinaryQuery(const Query& left, const Query& right, string s) :
		lhs(left), rhs(right), opSym(s) {}
	string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; }
	Query lhs, rhs;
	string opSym;
};

AndQuery类
假设实际使用中查询Query q = Query("fiery") & Query("bird")
这个过程流程为:

  1. 执行 Query(“fiery”) 构造函数,Query(const string& s) : **q**(new WordQuery(s)),在这之前先执行构造函数WordQuery(const string& s) :query_word(s),这里的q是WordQuery类;
  2. &运算符重载,返回AndQuery的对象;
  3. 执行Query(shared_ptr<Query_base> query)构造函数(Query q);

eval函数中,

  1. 先调用从BinaryQuery继承的rhs.eval,从上述分析可得,最终调用的是
    return t.query(query_word);//返回查询结果,核心业务处理的还是单个单词的查询结果。
  2. 调用标准库函数set_intersection实现交集功能。
class AndQuery : public BinaryQuery
{
	friend Query operator&(const Query&, const Query&);
	AndQuery(const Query& left, const Query& right) : BinaryQuery(left, right, "&") {}
	QueryResult eval(const TextQuery&) const;
};

Query operator&(const Query& lhs, const Query& rhs)
{
	return shared_ptr<Query_base>(new AndQuery(lhs, rhs));
}
QueryResult AndQuery::eval(const TextQuery& text) const
{
	auto right = rhs.eval(text), left = lhs.eval(text);
	auto ret_lines = make_shared<set<line_no>>();
	/*核心:交集放入ret_lines*/
	set_intersection(left.begin(), left.end(), right.begin(), right.end(),
		inserter(*ret_lines, ret_lines->begin()));
	return QueryResult(rep(), ret_lines, left.get_file());
}

OrQuery类
类似AndQuery,只是交集操作改成并集操作;

class OrQuery : public BinaryQuery
{
	friend Query operator|(const Query&, const Query&);
	OrQuery(const Query& left, const Query& right) : BinaryQuery(left, right, "|") {}
	QueryResult eval(const TextQuery&) const;
};

Query operator|(const Query& lhs, const Query& rhs)
{
	return shared_ptr<Query_base>(new OrQuery(lhs, rhs));
}
QueryResult OrQuery::eval(const TextQuery& text) const
{
	auto right = rhs.eval(text), left = lhs.eval(text);
	auto ret_lines = make_shared<set<line_no>>(left.begin(), left.end());//拷贝左侧运算结果
	ret_lines->insert(right.begin(), right.end());//加上右侧运算结果,并集
	return QueryResult(rep(), ret_lines, left.get_file());
}


NotQuery类
eval函数中先进行单个单词的查询,返回一个set,里面存储着关键词出现的行号,将set中不存在的行号放入最终查询结果,则取反操作完成。

class NotQuery : public Query_base
{
	friend Query operator~(const Query&);
	NotQuery(const Query& q) : query(q) {}
	string rep() const { return "~(" + query.rep() + ")"; }//实际最后用的都是WordQuery::rep() 
	QueryResult eval(const TextQuery&) const;
	Query query;
};

Query operator~(const Query& operand)
{
	return shared_ptr<Query_base>(new NotQuery(operand));//返回一个新建的Query_base指针,指针指向NotQuery,调用Query构造函数
}
QueryResult NotQuery::eval(const TextQuery& text) const
{
	auto result = query.eval(text);//查询结果
	auto ret_lines = make_shared<set<line_no>>();//行号集合
	auto beg = result.begin(), end = result.end();
	auto sz = result.get_file()->size();//总行数       
	for (size_t n = 0; n != sz; ++n)
	{
		if (beg == end || *beg != n)
			ret_lines->insert(n);//如果不在查询结果中,则放入结果
		else if (beg != end)
			++beg;
	}
	return QueryResult(rep(), ret_lines, result.get_file());
}

练习源码:gitee

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值