程序读取用户指定的任意文本文件,然后允许用户从该文件中查找单词。查询的结果是该程序出现的次数,并且列出程序所在的行数。如果单词在行中出现多次,则程序将只显示该行一次。行号按升序显示。
二 查询程序的设计
1. 它必须允许用户指明要处理的文件名字。程序将存储该文件的内容,以便输出每个单词所在的原始行。
2. 它必须将每一行分解为各个单词,并且记录每个单词所在的所有行。在输出行号时,保证升序输出,并且不重复。
3. 对特定单词的查询将返回出现单词的所有行的行号。
4. 输出某单词所在的行文本时,程序必须能根据给定的行号从输入文件中获取相应地行。
数据结构
设计TextQuery类实现
1. 使用一个vector<string>类型的对象存储整个输入文件的副本。输入文件的每一行是该vector对象的一个元素。因而在输出某一行时,只需以下标获取该行所在的元素即可。
2. 将每个单词所在的行号存储在一个set容器对象中。使用set就可确保每行只有一个条目,而且条目将自动升序排列。(set里面每个值都不重复)
3. 使用一个map容器将每个单词与一个set容器对象关联起来,该set容器对象记录该单词的所在的行号。
故,我们定义的TextQuery类将有两个数据成员:存储输入文件的vector对象以及一个map容器对象,改容器关联每个输入的单词和记录单词所在行的set容器对象
操作
查询函数需返回存储一组行号的set对象
查询的过程很简单:使用下标访问map对象获取关联的set对象即可。唯一的问题是如何返回找到set对象。安全的设计方案是返回该set的副本。但是意味着要复制set中的每个元素。如果处理的是一个相当大的文件,则代码十分严重。可行的方法是:返回一个pair对象,存储一对指向set中元素的迭代器;返回set对象的const引用。 简单起见,我们先返回副本的方法
类应该包括以下三个public函数:
read_file:形参是一个ifstream&类型对象。每次从文件中读入一行,并将它保存在vector容器中,输入完毕后,read_file将关联每个单词及其所在行号的map容器。
run_query成员函数,其形参为一个string类型对象,返回一个set容器,该set容器包含出现该string对象的所有行的行号
text_line成员函数,其形参为一个行号,返回输入文本中该行号文本对应的文本行
无论是run_query还是text_line都不会修改调用此函数的对象,故可定义为const函数。
为实现read_file功能,定义两个private函数来读取输入文本和创建map容器
store_file函数读入文件,并将文件内容存储在vector容器
build_map函数将每一行分解为各个单词,创建map容器对象,同时记录每个单词出现行号。
TextQuery类的使用
其中,open_file()和make_plural()很简单,这里不细讲,代码中有。
类的成员函数的实现
首先是读入需要查询的文件。使用string和vector容器提供的操作,可以很简单地实现
每次存储文件的一行内容将其添加到lines_of_text的vector容器中
建立单词map容器
for 循环以每次一行遍历lines_of_text,然后将line(istringstream对象)与当前行绑定起来,然后使用istringstream的输入操作符读入行中的每个单词,至此能够将行中单词读取出来
程序最后如果单词不存在则插入该单词,初始化set并且将该行号插入到set,由于set容器不重复故当一行同一单词多个时set只记录一个行号,如果单词已经存在,就指更新set内的行号
支持查询
run_query函数带有指向const string类型对象的引用参数,并以这个参数作为下标来访问word_map对象。假如成功找到string则返回这个string的set对象,否则返回一个空的set对象。
run_query()返回值的使用:
运行run_query函数后,获得一组所查找的单词出现的行号。除了输出该单词的出现次数,还需要输出该单词出现的每一行
PS:源代码中还有个别和算法代码关系不大的函数,没做分析,这部分代码很简单,很容易看懂。
1 #include "TextQuery.h" 2 #include <string> 3 #include <vector> 4 #include <map> 5 #include <set> 6 #include <iostream> 7 #include <fstream> 8 #include <cctype> 9 #include <cstring> 10 #include <cstdlib> 11 12 using std::set; 13 using std::string; 14 using std::map; 15 using std::vector; 16 using std::cerr; 17 using std::cout; 18 using std::cin; 19 using std::ifstream; 20 using std::endl; 21 22 string make_plural(size_t, const string&, const string&); 23 ifstream& open_file(ifstream&, const string&); 24 void print_results(const set<TextQuery::line_no>& locs, 25 const string& sought, const TextQuery &file) 26 { 27 // if the word was found, then print count and all occurrences 28 typedef set<TextQuery::line_no> line_nums; 29 line_nums::size_type size = locs.size(); 30 cout << " " << sought << " occurs " 31 << size << " " 32 << make_plural(size, "time", "s") << endl; 33 34 // print each line in which the word appeared 35 line_nums::const_iterator it = locs.begin(); 36 for ( ; it != locs.end(); ++it) { 37 cout << " (line " 38 // don't confound user with text lines starting at 0 39 << (*it) + 1 << ") " 40 << file.text_line(*it) << endl; 41 } 42 } 43 44 // program takes single argument specifying the file to query 45 int main(int argc, char **argv) 46 { 47 // open the file from which user will query words 48 ifstream infile; 49 if (argc < 2 || !open_file(infile, argv[1])) { 50 cerr << "No input file!" << endl; 51 return EXIT_FAILURE; 52 } 53 54 TextQuery tq; 55 tq.read_file(infile); // builds query map 56 57 // iterate with the user: prompt for a word to find and print results 58 // loop indefinitely; the loop exit is inside the while 59 while (true) { 60 cout << "enter word to look for, or q to quit: "; 61 string s; 62 cin >> s; 63 64 // stop if hit eof on input or a 'q' is entered 65 if (!cin || s == "q") break; 66 67 // get the set of line numbers on which this word appears 68 set<TextQuery::line_no> locs = tq.run_query(s); 69 70 // print count and all occurrences, if any 71 print_results(locs, s, tq); 72 } 73 return 0; 74 }
1 #ifndef TEXTQUERY_H 2 #define TEXTQUERY_H 3 #include <string> 4 #include <vector> 5 #include <map> 6 #include <set> 7 #include <iostream> 8 #include <fstream> 9 #include <cctype> 10 #include <cstring> 11 12 class TextQuery { 13 // as before 14 public: 15 // typedef to make declarations easier 16 typedef std::string::size_type str_size; 17 typedef std::vector<std::string>::size_type line_no; 18 19 /* interface: 20 * read_file builds internal data structures for the given file 21 * run_query finds the given word and returns set of lines on which it appears 22 * text_line returns a requested line from the input file 23 */ 24 void read_file(std::ifstream &is) 25 { store_file(is); build_map(); } 26 std::set<line_no> run_query(const std::string&) const; 27 std::string text_line(line_no) const; 28 str_size size() const { return lines_of_text.size(); } 29 void display_map(); // debugging aid: print the map 30 31 private: 32 // utility functions used by read_file 33 void store_file(std::ifstream&); // store input file 34 void build_map(); // associated each word with a set of line numbers 35 36 // remember the whole input file 37 std::vector<std::string> lines_of_text; 38 39 // map word to set of the lines on which it occurs 40 std::map< std::string, std::set<line_no> > word_map; 41 // characters that constitute whitespace 42 static std::string whitespace_chars; 43 // canonicalizes text: removes punctuation and makes everything lower case 44 static std::string cleanup_str(const std::string&); 45 }; 46 #endif
1 #include "TextQuery.h" 2 #include <sstream> 3 #include <string> 4 #include <vector> 5 #include <map> 6 #include <set> 7 #include <iostream> 8 #include <fstream> 9 #include <cctype> 10 #include <cstring> 11 #include <stdexcept> 12 13 using std::istringstream; 14 using std::set; 15 using std::string; 16 using std::getline; 17 using std::map; 18 using std::vector; 19 using std::cerr; 20 using std::cout; 21 using std::cin; 22 using std::ifstream; 23 using std::endl; 24 using std::ispunct; 25 using std::tolower; 26 using std::strlen; 27 using std::out_of_range; 28 29 string TextQuery::text_line(line_no line) const 30 { 31 if (line < lines_of_text.size()) 32 return lines_of_text[line]; 33 throw std::out_of_range("line number out of range"); 34 } 35 36 // read input file: store each line as element in lines_of_text 37 void TextQuery::store_file(ifstream &is) 38 { 39 string textline; 40 while (getline(is, textline)) 41 lines_of_text.push_back(textline); 42 } 43 44 // v: vertical tab; f: formfeed; : carriage return are 45 // treated as whitespace characters along with space, tab and newline 46 string TextQuery::whitespace_chars(" v f"); 47 48 // finds whitespace-separated words in the input vector 49 // and puts the word in word_map along with the line number 50 void TextQuery::build_map() 51 { 52 // process each line from the input vector 53 for (line_no line_num = 0; 54 line_num != lines_of_text.size(); 55 ++line_num) 56 { 57 // we'll use line to read the text a word at a time 58 istringstream line(lines_of_text[line_num]); 59 string word; 60 while (line >> word) 61 // add this line number to the set; 62 // subscript will add word to the map if it's not already there 63 word_map[cleanup_str(word)].insert(line_num); 64 } 65 } 66 67 set<TextQuery::line_no> 68 TextQuery::run_query(const string &query_word) const 69 { 70 // Note: must use find and not subscript the map directly 71 // to avoid adding words to word_map! 72 map<string, set<line_no> >::const_iterator 73 loc = word_map.find(cleanup_str(query_word)); 74 if (loc == word_map.end()) 75 return set<line_no>(); // not found, return empty set 76 else 77 // fetch and return set of line numbers for this word 78 return loc->second; 79 } 80 81 void TextQuery::display_map() 82 { 83 map< string, set<line_no> >::iterator iter = word_map.begin(), 84 iter_end = word_map.end(); 85 86 // for each word in the map 87 for ( ; iter != iter_end; ++iter) { 88 cout << "word: " << iter->first << " {"; 89 90 // fetch location vector as a const reference to avoid copying it 91 const set<line_no> &text_locs = iter->second; 92 set<line_no>::const_iterator loc_iter = text_locs.begin(), 93 loc_iter_end = text_locs.end(); 94 95 // print all line numbers for this word 96 while (loc_iter != loc_iter_end) 97 { 98 cout << *loc_iter; 99 100 if (++loc_iter != loc_iter_end) 101 cout << ", "; 102 103 } 104 105 cout << "} "; // end list of output this word 106 } 107 cout << endl; // finished printing entire map 108 } 109 110 string TextQuery::cleanup_str(const string &word) 111 { 112 string ret; 113 for (string::const_iterator it = word.begin(); it != word.end(); ++it) { 114 if (!ispunct(*it)) 115 ret += tolower(*it); 116 } 117 return ret; 118 }