《C++ Primer》第15章 15.9节习题答案

《C++ Primer》第15章 面向对象程序设计

15.9节 文本查询程序再探 习题答案

练习15.31:已知s1、s2、s3和s4都是string,判断下面的表达式分别创建了什么样的对象:

(a) Query(s1) | Query(s2) & ~Query(s3);
(b) Query(s1) | (Query(s2) & ~Query(s3));
(c) (Query(s1) | Query(s2)) | ( Query(s3) & Query(s4)));

【出题思路】

熟悉对象创建的工作机理。

【解答】

(a)共创建12个对象:6个Query_base对象以及其相关联的句柄。6个Query_base对象分别是3个WordQuery对象,1个NotQuery对象,1个AndQuery对象,1个OrQuery对象。(

b)与(a)同。

(c)共创建 14个对象:7个Query_base对象及其相关联的句柄。7个Query_ base对象分别是4个WordQuery对象,2个AndQuery对象,1个OrQuery对象。

练习15.32:当一个Query类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?

【出题思路】

理解类层次中的拷贝、移动、赋值和销毁行为。

【解答】

Query类未定义自己的拷贝/移动控制成员,当进行这些操作时,执行默认语义。而其唯一的数据成员是Query_base的shared_ptr,因此,当拷贝、移动、赋值或销毁一个Query对象时,会调用shared_ptr的对应控制成员,从而实现多个Query对象正确共享一个Query_base。而shared_ptr的控制成员调用Query_base的控制成员时,由于指向的可能是Query_base的派生类对象,因此可能在类层次中进行相应的拷贝/移动操作,调用Query_base的派生类的相应控制成员。

练习15.33:当一个Query_base类型的对象被拷贝、移动、赋值或销毁时,将分别发生什么?

【出题思路】

理解类层次中的拷贝、移动、赋值和销毁行为。

【解答】

Query_base是一个虚基类,不允许直接声明其对象。当其派生类对象进行这些操作时,会调用Query_base的相应控制成员。而Query_base没有定义自己的拷贝/移动控制成员,实际上它没有任何数据成员,无须定义这些操作,因此进行这些操作时,执行默认语义,什么也不会发生。

练习15.34:针对图15.3(第565页)构建的表达式:(a)列举出在处理表达式的过程中执行的所有构造函数。(b)列举出cout<<q所调用的rep。(c)列举出q.eval()所调用的eval。

【出题思路】

本题要求熟练掌握构造函数及其具体操作内容。

【解答】

(a)处理表达式Query("fiery")&Query("bird")|Query("wind")所执行的构造函数如下:

WordQuery(std::string &)
Query(const std::string &)
WordQuery(std::string &)
Query(const std::string &)
WordQuery(std::string &)
Query(const std::string &)
BinaryQuery(Query, Query, std::string)
AndQuery(Query, Query)
BinaryQuery(Query, Query, std::string)
Query(std::shared_ptr<Query_base> query)
BinaryQuery(Query, Query, std::string)
OrQuery(Query, Query)
Query(std::shared_ptr<Query_base> query)

(b)执行cout<<q各个类的rep函数的调用次序为:

BinaryQuery, Query, WordQuery, Query, BinaryQuery, Query, WordQuery,
Query, WordQuery, BinaryQuery, Query, WordQuery, Query, WordQuery,
BinaryQuery, Query, WordQuery, Query, BinaryQuery, Query, WordQuery,
Query, WordQuery, Query, BinaryQuery, Query, WordQuery, Query, BinaryQuery,
Query, WordQuery, Query, WordQuery

(c)计算q.eval时所调用的eval函数如下:

Query类的eval,OrQuery类的eval,AndQuery类的eval,WordQuery类的eval

练习15.35:实现Query类和Query_base类,其中需要定义rep而无须定义eval。

【出题思路】

本题是类构造的练习。

【解答】

storyDataFile.txt文件内容如下:
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?"
//TextQuery.h
#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include <memory>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <fstream>


class QueryResult
{
    
    friend std::ostream& print(std::ostream&, const QueryResult&);
public:
    typedef std::vector<std::string>::size_type line_no;
    typedef std::set<line_no>::const_iterator 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->cbegin();
    }
    line_it end() const
    {
        return lines->cend();
    }
    std::shared_ptr<std::vector<std::string>> get_file()
    {
        return file;
    }
private:
    std::string sought;  // word this query represents
    std::shared_ptr<std::set<line_no>> lines; // lines it's on
    std::shared_ptr<std::vector<std::string>> file;  //input file
};

std::ostream &print(std::ostream&, const QueryResult&);

class QueryResult; // declaration needed for return type in the query function
class TextQuery
{
public:
	using line_no = std::vector<std::string>::size_type;
	TextQuery(std::ifstream&);
    QueryResult query(const std::string&) const; 
    void display_map();        // debugging aid: print the map
    
private:
    std::shared_ptr<std::vector<std::string>> file; // input file
    // maps each word to the set of the lines in which that word appears
    std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;

	// canonicalizes text: removes punctuation and makes everything lower case
    static std::string cleanup_str(const std::string&);
};
#endif
//TextQuery.cpp
#include "TextQuery.h"
#include "make_plural.h"

#include <cstddef>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <fstream>
#include <cctype>
#include <cstring>
#include <utility>

using std::size_t;
using std::shared_ptr;
using std::istringstream;
using std::string;
using std::getline;
using std::vector;
using std::map;
using std::set;
using std::cerr;
using std::cout;
using std::cin;
using std::ostream;
using std::endl;
using std::ifstream;
using std::ispunct;
using std::tolower;
using std::strlen;
using std::pair;
//把每一行放入text, 存入file(vector), 组成word和行号(set)的映射
// read the input file and build the map of lines to line numbers
TextQuery::TextQuery(ifstream &is): file(new vector<string>)
{
    string text;
    while (getline(is, text)) {       // for each line in the file
		file->push_back(text);        // remember this line of text
		int n = file->size() - 1;     // the current line number
		istringstream line(text);     // separate the line into words
		string word;               
		while (line >> word) {        // for each word in that line
            word = cleanup_str(word);
            // if word isn't already in wm, subscripting adds a new entry
            auto &lines = wm[word]; // lines is a shared_ptr 
            if (!lines) // that pointer is null the first time we see word
                lines.reset(new set<line_no>); // allocate a new set
            lines->insert(n);      // insert this line number
		}
	}
}

// not covered in the book -- cleanup_str removes
// punctuation and converts all text to lowercase so that
// the queries operate in a case insensitive manner
string TextQuery::cleanup_str(const string &word)
{
    string ret;
    for (auto it = word.begin(); it != word.end(); ++it) {
        if (!ispunct(*it))
            ret += tolower(*it);
    }
    return ret;
}

QueryResult TextQuery::query(const string &sought) const
{
	// we'll return a pointer to this set if we don't find sought
	static shared_ptr<set<line_no>> nodata(new set<line_no>); 

    // use find and not a subscript to avoid adding words to wm!
    auto loc = wm.find(cleanup_str(sought));

	if (loc == wm.end()) 
		return QueryResult(sought, nodata, file);  // not found
	else 
		return QueryResult(sought, loc->second, file);
}

ostream &print(ostream & os, const QueryResult &qr)
{
    // if the word was found, print the count and all occurrences
    os << qr.sought << " occurs " << qr.lines->size() << " "
       << make_plural(qr.lines->size(), "time", "s") << endl;

    // print each line in which the word appeared
	for (auto num : *qr.lines) // for every element in the set 
		// don't confound the user with text lines starting at 0
        os << "\t(line " << num + 1 << ") " 
		   << *(qr.file->begin() + num) << endl;

	return os;
}

// debugging routine, not covered in the book
void TextQuery::display_map()
{
    auto iter = wm.cbegin(), iter_end = wm.cend();

    // for each word in the map
    for ( ; iter != iter_end; ++iter) {
        cout << "word: " << iter->first << " {";

        // fetch location vector as a const reference to avoid copying it
        auto text_locs = iter->second;
        auto loc_iter = text_locs->cbegin(),
                        loc_iter_end = text_locs->cend();

        // print all line numbers for this word
        while (loc_iter != loc_iter_end)
        {
            cout << *loc_iter;

            if (++loc_iter != loc_iter_end)
                 cout << ", ";

         }

         cout << "}\n";  // end list of output this word
    }
    cout << endl;  // finished printing entire map
}

//make_plural.h

#include <cstddef>
using std::size_t;

#include <string>
using std::string;

#include <iostream>
using std::cout;
using std::endl;

#ifndef MAKE_PLURAL_H
#define MAKE_PLURAL_H

// return the plural version of word if ctr is greater than 1
inline
string make_plural(size_t ctr, const string &word, 
                               const string &ending)
{
	return (ctr > 1) ? word + ending : word;
}

#endif
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <iterator>
#include <set>//multiset
#include <map>

#include "TextQuery.h"

using namespace std;


/*抽象基类, 没有public成员*/
class Query_base
{
    friend class Query;
protected:
    using line_no = TextQuery::line_no;//用于eval函数
    virtual ~Query_base() = default;

private:
    //eval返回与当前Query匹配的QueryResult
    virtual QueryResult eval (const TextQuery &) const = 0; //纯虚函数
    //rep是表示查询的一个string
    virtual std::string rep() const = 0;
};

class Query
{
    //这些运算符需要访问接受shared_ptr的构造函数,而该函数是私有的
    friend Query operator~ (const Query &); //取反
    friend Query operator| (const Query&, const Query&); //取或
    friend Query operator& (const Query&, const Query&); //取交

public:
    Query(const std::string &);//构建一个新的WordQuery
    //接口函数:调用对应的Query_base操作
    QueryResult eval(const TextQuery &t) const
    {
        return q->eval(t);
    }

    std::string rep() const
    {
        return q->rep();
    }

private:
    Query(shared_ptr<Query_base> query)
    :q(query)
    {

    }
    shared_ptr<Query_base> q;
};

//重载输出(<<)操作符
std::ostream & operator<<(std::ostream &os, const Query &query)
{
    return os << query.rep();
}
//---------------------------操作符--------------
//单词查询类
class WordQuery : public Query_base
{
    friend class Query;
    WordQuery (const std::string &s) : query_word (s)
    {
    }
    QueryResult eval (const TextQuery &t) const
    {
        return t.query(query_word);
    }
    std::string rep() const
    {
        return query_word;
    };
    std::string query_word;
};


//Query接口实现动态绑定WordQuery
inline Query::Query (const std::string &s) : q(new WordQuery(s))
{
}
//取反查询
class NotQuery : public Query_base
{
    friend Query operator~ (const Query &); //友元是取反函数
    NotQuery (const Query &q) : query(q)
    {
    }
    std::string rep() const
    {
        return "~("+query.rep()+")";
    }
    QueryResult eval (const TextQuery &t) const;
    Query query;
};

//实现取反操作, 动态绑定NotQuery对象
//最终使用的是WordQuery类, Query构建需要WordQuery, 再传入NotQuery;
inline Query operator~ (const Query &operand)
{
    return std::shared_ptr<Query_base> (new NotQuery(operand));
}
//二元查询, 没有eval, 则继承纯虚函数
class BinaryQuery : public Query_base
{
protected:
    BinaryQuery (const Query &l, const Query &r, std::string s) :
    lhs(l), rhs(r), opSym(s)
    {
    }
    std::string rep() const
    {
        return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")";
    }
    Query lhs, rhs;
    std::string opSym;
};

//取并查询
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;
};

inline Query operator& (const Query& lhs, const Query& rhs)
{
    return std::shared_ptr<Query_base>(new AndQuery(lhs, rhs));
}

//取或查询
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;
};

inline Query operator| (const Query& lhs, const Query& rhs)
{
    return std::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 = std::make_shared<std::set<line_no> >(left.begin(), left.end());
    ret_lines->insert(right.begin(), right.end());
    return QueryResult(rep(), ret_lines, left.get_file());
}

QueryResult AndQuery::eval(const TextQuery& text) const
{
    auto left = lhs.eval(text), right = rhs.eval(text); //调用的是WordQuery的eval
    auto ret_lines = std::make_shared<std::set<line_no>>();
    std::set_intersection(left.begin(), left.end(), right.begin(), right.end(),
                          std::inserter(*ret_lines, ret_lines->begin()));
    return QueryResult(rep(), ret_lines, left.get_file());
}

QueryResult NotQuery::eval(const TextQuery& text) const
{
    auto result = query.eval(text); //调用WordQuery.eval;
    auto ret_lines = std::make_shared<std::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());
}
bool get_word(std::string& str)
{
    std::cout << "enter word to look for, or q to quit: " << std::endl;
    if ( !(std::cin >> str) || str == "q")
    {
        std::cout << str;
        return false;
    }
    else
    {
        std::cout << str;
        return true;
    }
}


int main(int argc, const char *argv[])
{
    std::ifstream infile;
    infile.open("storyDataFile.txt");
    TextQuery file = infile;

    Query q = Query("fiery") & Query("bird") | Query("wind");
    const auto results = q.eval(file);
    std::cout << "\nExecuting Query for: " << q << endl;
    print(cout, results) << endl;
    infile.close();
    return 0;
}

运行结果:

 练习15.36:在构造函数和rep成员中添加打印语句,运行你的代码以检验你对本节第一个练习中(a)、(b)两小题的回答是否正确。

【出题思路】

检验类层次中成员函数的调用关系。

【解答】

在各个类的构造函数和rep中添加打印语句即可。注意,Query类有一个public和一个private共两个构造函数。

练习15.37:如果在派生类中含有shared_ptr<Query_base>类型的成员而非Query类型的成员,则你的类需要做出怎样的改变?

【出题思路】

练习类层次的不同实现方式。

【解答】

书中的实现方式是用Query类封装了Query_base指针,管理实际查询处理用到的不同Query类型对象。

如果不使用Query类,则涉及使用Query类型的地方,都要改成Query_base指针。如创建单个词查询时,就必须创建WordQuery类而不是Query对象。几个重载的布尔运算符也不能再针对Query对象,而需针对Query_base指针,从而复杂的查询请求无法写成目前的简单形式,而需逐个运算完成,将结果赋予Query_base指针,然后再进行下一步运算。资源管理方面也需要重新设计。因此,当前的设计仍是最佳方式。

练习15.38:下面的声明合法吗?如果不合法,请解释原因;如果合法,请指出该声明的含义。

BinaryQuery a = Query("fiery") & Query("bird");
AndQuery b = Query("fiery") & Query("bird");
OrQuery c = Query("flery") & Query("bird")

【出题思路】

理解虚函数和类层次的概念。

【解答】

第一条声明不合法,因为BinaryQuery中的eval是纯虚函数。

第二条声明不合法,不能将Query转换为AndQuery。

第三条声明不合法,不能将Query转换为OrQuery。

练习15.39:实现Query类和Query_base类,求图15.3(第565页)中表达式的值并打印相关信息,验证你的程序是否正确。

【出题思路】

练习复杂类层次的实现。

【解答】

参考书中本节内容和配套网站中的代码即可。

练习15.40:在OrQuery的eval函数中,如果rhs成员返回的是空集将发生什么?如果lhs是空集呢?如果lhs和rhs都是空集又将发生什么?

【出题思路】

理解并集的计算过程。

【解答】

OrQuery的eval从lhs和rhs获取范围来构造set(或向其插入),而set的构造和插入操作可以正确处理空范围,因此无论lhs和rhs的结果是否为空集,eval都能得到正确结果。

练习15.41:重新实现你的类,这次使用指向Query_base的内置指针而非shared_ptr。请注意,做出上述改动后你的类将不能再使用合成的拷贝控制成员。

【出题思路】

练习在复杂程序中自己实现资源管理。

【解答】

关键是在没有了shared_ptr的帮助后,要为Query类设计拷贝控制成员,管理内存。

除了将成员q的类型改为Query_base*外,还需增加引用计数成员int* uc,并增加拷贝构造函数、拷贝赋值运算符和析构函数。具体方法参考第13章的练习即可。当然,其他用到q的地方也需要进行修改。

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <memory>
#include <algorithm>
#include <iterator>
#include <set>//multiset
#include <map>

#include "TextQuery.h"

using namespace std;

//抽象基类, 没有public成员
class Query_base
{
    friend class Query;

protected:
    using line_no = TextQuery::line_no;//用于eval函数
    virtual ~Query_base() = default;

private:
    //eval返回与当前的Query匹配的QueryResult
    virtual QueryResult eval(const TextQuery &) const = 0; //纯虚函数
    //rep是表示查询的一个string
    virtual std::string rep() const = 0;
};

class Query
{
    friend Query operator~(const Query &);//取反
    friend Query operator|(const Query &, const Query &);//取或
    friend Query operator&(const Query &, const Query &);//取与

public:
    Query(const std::string &);//构建一个新的WordQuery
    //拷贝构造函数
    Query(const Query &query)
    :q(query.q), uc(query.uc)
    {
        ++*uc;
    }
    Query& operator=(const Query& query);//拷贝赋值运算符

    ~Query();

    //接口函数:调用对应的Query_base操作
    QueryResult eval(const TextQuery &t) const
    {
        return q->eval(t);
    }
    std::string rep() const
    {
        return q->rep();
    }

private:
    Query(Query_base *query)
    :q(query), uc(new int(1))
    {

    }
    Query_base *q;
    int *uc;
};



inline Query::~Query()
{
    if(--*uc == 0)
    {
        delete q;
        delete uc;
    }
}

inline Query& Query::operator=(const Query& query)
{
    ++*query.uc;
    if(--*uc == 0)
    {
        delete q;
        q = NULL;
        delete uc;
        uc = NULL;
    }
    q = query.q;
    uc = query.uc;
    return *this;
}

//---------------------------操作符--------------
//单词查询类
class WordQuery: public Query_base
{
    friend class Query;
    WordQuery(const std::string &s)
    :query_word(s)
    {

    }
    QueryResult eval(const TextQuery &t) const
    {
        return t.query(query_word);
    }
    std::string rep() const
    {
        return query_word;
    }
    std::string query_word;
};

inline Query::Query(const std::string &s)
:q(new WordQuery(s)), uc(new int(1))
{

}

//重载输出(<<)操作符
std::ostream& operator << (std::ostream &os, const Query &query)
{
    return os << query.rep();
}

//取反查询
class NotQuery: public Query_base
{
    friend Query operator~(const Query &);//友元是取反函数
    NotQuery(const Query &q)
    :query(q)
    {

    }
    std::string rep() const
    {
        return "~(" + query.rep() + ")";
    }
    QueryResult eval(const TextQuery &t) const;
    Query query;
};

//实现取反操作, 动态绑定NotQuery对象
//最终使用的是WordQuery类, Query构建需要WordQuery, 再传入NotQuery;
inline Query operator~(const Query &operand)
{
    return new NotQuery(operand);
}

//二元查询, 没有eval, 则继承纯虚函数
class BinaryQuery: public Query_base
{
protected:
    BinaryQuery(const Query &l, const Query &r, std::string s)
    :lhs(l), rhs(r), opSym(s)
    {

    }
    std::string rep() const
    {
        return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")";
    }
    Query lhs, rhs;
    std::string opSym;
};

//取并查询
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;
};

inline Query operator&(const Query &lhs, const Query &rhs)
{
    return new AndQuery(lhs, rhs);
}

//取或查询
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;
};

inline Query operator|(const Query &lhs, const Query &rhs)
{
    return new OrQuery(lhs, rhs);
}

QueryResult OrQuery::eval(const TextQuery &text) const
{
    auto right = rhs.eval(text);
    auto left = lhs.eval(text);
    auto ret_lines = std::make_shared<std::set<line_no>>(left.begin(), left.end());
    ret_lines->insert(right.begin(), right.end());
    return QueryResult(rep(), ret_lines, left.get_file());
}

QueryResult AndQuery::eval(const TextQuery &text) const
{
    auto left = lhs.eval(text), right = rhs.eval(text);//调用的是WordQuery的eval
    auto ret_lines = std::make_shared<std::set<line_no>>();
    std::set_intersection(left.begin(), left.end(), right.begin(), right.end(),
                          std::inserter(*ret_lines, ret_lines->begin()));
    return QueryResult(rep(), ret_lines, left.get_file());
}

QueryResult NotQuery::eval(const TextQuery &text) const
{
    auto result = query.eval(text);//调用WordQuery.eval;
    auto ret_lines = std::make_shared<std::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());
}

bool get_word(std::string &str)
{
    std::cout << "enter word to look for, or q to quit:" << std::endl;
    if(!(std::cin >> str) || str == "q")
    {
        std::cout << str;
        return false;
    }
    else
    {
        std::cout << str;
        return true;
    }
}

int main(int argc, const char *argv[])
{
    std::ifstream infile;
    infile.open("storyDataFile.txt");
    TextQuery file = infile;
    Query q = Query("fiery") & Query("bird") | Query("wind");
    const auto results = q.eval(file);
    std::cout << "\nExecuting Query for:" << q << endl;
    print(cout, results) << endl;
    infile.close();
    return 0;
}

运行结果:

 练习15.42:从下面的几种改进中选择一种,设计并实现它:(a)按句子查询并打印单词,而不再是按行打印。(b)引入一个历史系统,用户可以按编号查阅之前的某个查询,并可以在其中增加内容或者将其与其他查询组合。(c)允许用户对结果做出限制,比如从给定范围的行中挑出匹配的进行显示。

【出题思路】

本题练习复杂程序的设计和实现。

【解答】

在配套网站中的代码基础上进行如下修改:(a)要支持基于同一句子而不是同一行计算单词,只需将本文按句子而不是按文本行存储到vector容器。在TextQuery.cpp中将TextQuery类的构造函数修改如下:

//读输入文件,将每个句子存储为lines_of_text的一个元素
TextQuery::TextQuery(ifstream &is)
:file(new vector<string>)
{
    char ws[] = {'\t', '\r', '\v', '\f', '\n'};
    char eos[] = {'?', '.', ',', '!'};
    set<char> whiteSpace(ws, ws + 5);//空白符
    set<char> endOfSentence(eos, eos + 3);//句子结束符
    string sentence;
    char ch;
    while(is.get(ch))
    {
        //未遇到文件结束符
        if(!whiteSpace.count(ch)) //非空白符
        {
            sentence += ch;
        }
        //读完一个句子
        if(endOfSentence.count(ch))
        {
            file->push_back(sentence);
            int n = file->size() - 1;
            istringstream is(sentence);
            string word;
            while(is >> word)
            {
                auto &lines = wm[word];
                if(!lines)
                {
                    lines.reset(new set<line_no>);
                }
                lines->insert(n);
            }
            //将sentence清空,准备读下一个句子
            sentence.assign("");
        }
        
    }
          
}

此外,将print函数中输出提示的"line"改为"sentence"。

(b)为了记录查询历史,可声明一个vector,每个元素是保存3个string的定长array,保存一个查询的3个查询词。还需修改get_word(s)和主程序的逻辑,允许用户选择历史查询。当用户输入一个合法的查询编号时,从vector对应元素中提取出3个查询词,重构Query,完成查询并输出结果。查询添加内容及多查询组合的功能可类似添加。

bool get_word(string &str)
{
    cout << "enter a word to search for, or q to quit, or h to history: ";

    if(!(std::cin >> str) || str  == "q")
    {
        std::cout << str;
        return false;
    }
    else
    {
        std::cout << str;
        return true;
    }
}


int main(int argc, char **argv)
{
    //读取文件建立映射表
    //TextQuery file = get_file(argc, argv);
    std::ifstream infile;
    infile.open("storyDataFile.txt");
    TextQuery file = infile;
    
    vector<array<string, 3>> h;
    //程序主循环:提示用户输入一个单词,在文件中查找它并打印结果
    while(true)
    {
        string sought1, sought2, sought3;
        if(!get_word(sought1))
        {
            break;
        }
        if(sought1 != "h")
        {
            cout << "\nenter second and third words:";
            cin >> sought2 >> sought3;
            //对给定字符串查找所有出现位置
            Query q = Query(sought1) & Query(sought2) | Query(sought3);
            h.push_back({sought1, sought2, sought3});
            //h.push_back({sought1, sought2, sought3});
            cout << "\nExecuting Query for: " << q << endl;
            const auto results = q.eval(file);
            //打印匹配结果
            print(cout, results, 0, 10);
        }
        else //用户输入了"h“,表示要提取历史查询
        {
            cout << "\nenter Query no.:";
            int i;
            cin >> i;
            if(i < 1 || i > h.size()) //历史编号合法性检查
            {
                cout << "\nBad Query no." << endl;
            }
            else
            {
                //提取3个查询词,重构查询
                Query q = Query(h[i - 1][0]) & Query(h[i - 1][1]) | Query(h[i - 1][2]);
                cout << "\nExecuting Query for: " << q << endl;
                const auto results = q.eval(file);
                //打印匹配结果
                print(cout, results, 0, 10);
            }
        }
    }
    return 0;
}

(c)定义另一个版本的print,接受两个参数beg和end,指出要输出的行号即可。

ostream &print(ostream &os, const QueryResult &qr, int beg, int end)
{
    //如果找到了单词,打印出出次数及所有出现的行号
    os << qr.sought << "  occures  " << qr.lines->size() << "  " << make_plural(qr.lines->size(), "time", "s") << endl;
    //打印单词出现的每一行
    for(auto num: *qr.lines)//对set中每个元素
    {
        //不让用户对从0开始的文本行号困惑
        if(num + 1 >= beg && num + 1 <= end)
        {
            os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << endl;
        }
    }
    return os;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值