This article summarizes and excerpts a text query program, a big example in C ++ primer. The main purpose is to learn the programming specifications of masters.
Program Requirements: read any text file specified by the user, and then allow the user to find words from the file. The query result shows the number of times a word appears and lists the rows that appear each time. If a word appears multiple times in the same row, the program displays the row only once and the row number is displayed in ascending order.
Analysis:
- Enter the file name;
- A row must be taken as a whole and output by the row number;
- Each row must be broken down into individual words, and the row where the word is located is recorded. All rows are returned for the word query;
- This section mainly describes the use of containers;
Solution:
- Use Vector to store the entire row. One element in the vector is a row;
- MAP is used to take words as keywords and the set of rows as satellite data;
The following is the included header file:
#include<utility>#include<set>#include<map>#include<vector>#include<string>#include<iostream>#include<fstream>#include<sstream>using namespace std;
Below are the main types of textquery:
class TextQuery { public: //typedef to make declarations easier typedef std::vector<std::string>::size_type line_no; typedef std::set<line_no>::const_iterator set_it; /* interface: * read_file() builds internal data structures for the given file * run_query() finds the given word and returns "set" of lines on which it appears * text_line() return a requested line from the input file */ void read_file(std::ifstream& is) { store_file(is); build_map();} std::pair<set_it,set_it> run_query(const std::string& ) const; std::string text_line(line_no) const; private: //utility functions used by read_file() void store_file(std::ifstream& ); //store input file void build_map(); //associated each word with a set of line numbers; std::vector<std::string> lines_of_text; //remember the whole input file std::map<std::string, std::set<line_no> > word_map; };
The following are the auxiliary functions make_plural and open_file:
/* This function provides a counter, a word and a word Terminator ending. When the counter value is greater than 1, return the plural version of the word */STD: String make_plural (size_t CTR, const STD: string & Word, const STD: string & ending) {return (CTR = 1 )? Word: Word + ending;} // open the given file STD: ifstream & open_file (STD: ifstream & in, const STD: string & file) {In. close (); // close in case it was already open in. clear (); // clear any existing errors; In. open (file. c_str (); Return in ;}
The following is the implementation of various functions in the class. Here, the implementation of run_query adopts the author's second suggestion and returns a pair of iterators:
// Step 1: store the input file void textquery: store_file (ifstream & IS) {string textline; while (Getline (is, textline) lines_of_text.push_back (textline );} // Step 2: Create the word map container void textquery: build_map () {for (line_no line_num = 0; line_num! = Lines_of_text.size (); ++ line_num) {istringstream line (lines_of_text [line_num]); string word; while (line> word) {// Add this line number to the Set; // word_map [word] is a set, and then insert word_map [word]. insert (line_num) ;}}// Step 3: Query/return a pair of set iterators pair <textquery: set_it, textquery: set_it> textquery :: run_query (const string & query_word) const {// You must note that you cannot directly query using subscript operations to prevent the insertion of nonexistent map elements <string, set <line_no>: c Onst_iterator loc = word_map.find (query_word); Return make_pair (loc-> second ). begin (), (loc-> second ). end () ;}// Step 4: output result void print_results (pair <textquery: set_it, textquery: set_it> & Its, const string & sought, const textquery & file) {typedef set <textquery: line_no> line_nums; line_nums: size_type size = 0; For (textquery: set_it beg = its. first; beg! = Its. second; ++ beg) ++ size; cout <"\ n" <sought <"occurs" <size <"" <make_plural (size, "time", "S") <Endl; line_nums: const_iterator it = its. first; For (; it! = Its. second; ++ it) {cout <"\ t (line" <(* It) + 1 <")" <file. text_line (* It) <Endl ;}} string textquery: text_line (line_no line) const {If (line <lines_of_text.size () return lines_of_text [Line]; throw STD :: out_of_range ("line number out of range ");}
The test procedure is as follows:
int main() { ifstream infile; string filename; if(!(cin>>filename) || !open_file(infile,filename)){ cerr<<"NO input file!"<<endl; return EXIT_FAILURE; } TextQuery tq; tq.read_file(infile); while(true){ cout<<"enter word to look for, or q to quit: "; string s;cin>>s; if(!cin || s=="q") break; pair<TextQuery::set_it,TextQuery::set_it> its=tq.run_query(s); print_results(its,s,tq); }return 0; }
Expand the preceding text query program. Requirements:
- Search for a single word and display all rows containing the word in ascending order;
- Non-query, use ~ Operator to display all unmatched rows;
- "Or" query, use the | Operator to display all rows that match any of the two query conditions;
- For "and" queries, use the & operator to display all rows that match both query conditions;
- Can combine these operators;
The design category is as follows:
When we use:
Query q = query ("") & query ("") | query ()
This usage tells us that user-level code cannot directly use our inheritance hierarchy. We can only define a handle class named query to hide the inheritance hierarchy. The user code is executed according to the handle. The user code can only indirectly manipulate the query_base object.
Take query q = query ("fiery") & query ("bird") | query ("wind") as an example:
Generate 10 objects: 5 query_base objects and their associated handles.
The code in the book is directly intercepted here:
// private, abstract class acts as a base class for concrete query types class Query_base { friend class Query; protected: typedef TextQuery::line_no line_no; virtual ~Query_base() { } private: // eval returns the |set| of lines that this Query matches virtual std::set<line_no> eval(const TextQuery&) const = 0; // display prints the query virtual std::ostream& display(std::ostream& = std::cout) const = 0; };
Query handle class:
// handle class to manage the Query_base inheritance hierarchy class Query { // these operators need access to the Query_base* constructor friend Query operator~(const Query &); friend Query operator|(const Query&, const Query&); friend Query operator&(const Query&, const Query&); public: Query(const std::string&); // builds a new WordQuery // copy control to manage pointers and use counting Query(const Query &c): q(c.q), use(c.use) { ++*use; } ~Query() { decr_use(); } Query& operator=(const Query&); // interface functions: will call corresponding Query_base operations std::set<TextQuery::line_no> eval(const TextQuery &t) const { return q->eval(t); } std::ostream &display(std::ostream &os) const { return q->display(os); } private: Query(Query_base *query): q(query), use(new std::size_t(1)) { } Query_base *q; std::size_t *use; void decr_use() { if (--*use == 0) { delete q; delete use; } } };
Overload OPERATOR:
inline Query operator&(const Query &lhs, const Query &rhs) { return new AndQuery(lhs, rhs); } inline Query operator|(const Query &lhs, const Query &rhs) { return new OrQuery(lhs, rhs); } inline Query operator~(const Query &oper) { return new NotQuery(oper); }
Output OPERATOR:
inline std::ostream& operator<<(std::ostream &os, const Query &q) { return q.display(os); }
Wordquery class:
class WordQuery: public Query_base { friend class Query; // Query uses the WordQuery constructor WordQuery(const std::string &s): query_word(s) { } // concrete class: WordQuery defines all inherited pure virtual functions std::set<line_no> eval(const TextQuery &t) const { return t.run_query(query_word); } std::ostream& display (std::ostream &os) const { return os << query_word; } std::string query_word; // word for which to search };
Notquery class:
class NotQuery: public Query_base { friend Query operator~(const Query &); NotQuery(Query q): query(q) { } // concrete class: NotQuery defines all inherited pure virtual functions std::set<line_no> eval(const TextQuery&) const; std::ostream& display(std::ostream &os) const { return os << "~(" << query << ")"; } const Query query; }; set<TextQuery::line_no> NotQuery::eval(const TextQuery& file) const { // virtual call through the Query handle to eval set<TextQuery::line_no> has_val = query.eval(file); set<line_no> ret_lines; // for each line in the input file, check whether that line is in has_val // if not, add that line number to ret_lines for (TextQuery::line_no n = 0; n != file.size(); ++n) if (has_val.find(n) == has_val.end()) ret_lines.insert(n); return ret_lines; }
Binary pure virtual class:
class BinaryQuery: public Query_base { protected: BinaryQuery(Query left, Query right, std::string op): lhs(left), rhs(right), oper(op) { } // abstract class: BinaryQuery doesn't define eval std::ostream& display(std::ostream &os) const { return os << "(" << lhs << " " << oper << " " << rhs << ")"; } const Query lhs, rhs; // right- and left-hand operands const std::string oper; // name of the operator };
Orquery class:
class OrQuery: public BinaryQuery { friend Query operator|(const Query&, const Query&); OrQuery(Query left, Query right): BinaryQuery(left, right, "|") { } // concrete class: OrQuery inherits display and defines remaining pure virtual std::set<line_no> eval(const TextQuery&) const; }; set<TextQuery::line_no> OrQuery::eval(const TextQuery& file) const { // virtual calls through the Query handle to get result sets for the operands set<line_no> right = rhs.eval(file), ret_lines = lhs.eval(file); // destination to hold results // inserts the lines from right that aren't already in ret_lines ret_lines.insert(right.begin(), right.end()); return ret_lines; }
Andquery class:
class AndQuery: public BinaryQuery { friend Query operator&(const Query&, const Query&); AndQuery (Query left, Query right): BinaryQuery(left, right, "&") { } // concrete class: And Query inherits display and defines remaining pure virtual std::set<line_no> eval(const TextQuery&) const; }; set<TextQuery::line_no> AndQuery::eval(const TextQuery& file) const { // virtual calls through the Query handle to get result sets for the operands set<line_no> left = lhs.eval(file), right = rhs.eval(file); set<line_no> ret_lines; // destination to hold results // writes intersection of two ranges to a destination iterator // destination iterator in this call adds elements to ret set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(ret_lines, ret_lines.begin())); return ret_lines; }