容器的综合应用:文本查询程序(摘自C++ Primer)

设计程序的一个良好习惯是首先将程序所涉及的操作列出来。明确需要提供的操作有助于建立需要的数据结构和实现这些行为。

 

本例有如下需求:

1.它必须允许用户指明处理的文件名字。程序将存储该文件的内容,以便输出每个单词所在的原始行。

2.它必须将每一行分解为各个单词,并记录每个单词所在的所有行。字输出行号时,应保证以升序输出,并且不重复。

3.对特定单词的查询将返回出现该单词的所有行的行号。

4.输出某单词所在的行文本时,程序必须能根据给定的行号从输入的文件中获取相应的行。

 

数据结构

TestQuery类

1.使用一个vector<string>类型的对象存储整个输入文件的副本。输入文件的每一行是该vector对象的一个元素。因而,在希望输出某一行时,只需以行号为下标获取该行所在的元素即可。

2.将每个单词所在的行号存储在一个set容器队形中。使用set就可确保每行只有一个条目,而且行号将自动按升序排列。

3.使用一个map容器将每个单词与一个set容器对象关联起来,该set容器对象记录此单词所在的行号。

综上,TextQuery类将有两个数据成员:存储输入文件的vector对象,以及一个map容器对象,该对象关联每个书u单词以及记录该单词所在行号的set容器对象。

 

操作

  对于类还要求有良好的接口。然而,一个重要的设计策略首先要确定:查询函数需返回存储一组行号的set对象。这个返回类型应该如何设计呢?

  事实上,查询的过程相当简单:使用下标访问map对象获取关联的set对象即可。唯一的问题是如何返回所找到的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_fie功能,还需定义两个private函数来读取输入文本和创建map容器:

  store_file 函数读入文件,并将文件内容存储在vector容器对象中

  build_map 函数将每一行分解为各个单词,创建map容器对象,同时记录每个单词出现行号。

 

源代码:

------------------------------------TextQuery.h-------------------------------------------

#include <vector>
#include <map>
#include <set>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <stdexcept>
using namespace std;

#ifndef TEXTQUERY_H
#define TEXTQUERY_H

//typedef to make declarations easier
 typedef vector<string>::size_type line_no;

class TextQuery{
public:
 
 /*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 returns a requested line from the input file
 */
 void read_file(ifstream &is) {store_file(is); build_map();}
 set<line_no> run_query(const string&)const;
 string text_line(line_no) const;

private:
 //utility functions used by read_file
 void store_file(ifstream&);   //store input file
 void build_map(); //associated each word with a set of line numbers


 //remember the whole input file
 vector<string> lines_of_text;


 //map word to set of the lines on which it occurs
 map< string,set<line_no> > word_map;
};

#endif

---------------------------------------------TextQuery.cpp--------------------------------

//read input file :store each line as element in lines_of_text
void TextQuery::store_file(ifstream& is)
{
   string textline;
   while(getline(is, textline))
      lines_of_text.push_back(textline);
}

//finds whitespace-separated words in the input vector
//and puts the word in word_map along with the line number
void TextQuery::build_map()
{
   //process each line from the input vector
   for(line_no line_num = 0;
        line_num != lines_of_text.size();
        ++line_num)
   {
    //we'll use line to read the text a word at a time
    istringstream line(lines_of_text[line_num]);
    string word;
    while(line >> word)
       //add this line nmber to the set;
       //subscript will add word to the map if it's not alread there
       word_map[word].insert(line_num);            //word_map[word]是一个set对象,该语句调用起insert函数,将行号插入。
   }
}

set<TextQuery::line_no>
TextQuery::run_query(const string &query_word) const
{
   //Note: must use find and not subscript the map directly
  //to avoid adding words to word_map!
   map<string, set<line_no> >::const_iterator
       loc = word_map.find(query_word);
   if(loc == word_map.end())
      return set<line_no>();// not found return empty set
   else
      //fetch and return set of line numbers for this word
      return loc->second;
}

string TextQuery::text_line(line_no line) const
{
   if(line < lines_of_text.size())
      return lines_of_text[line];
   throw out_of_range("line number out of range");
}

 

----------------------------------------tq.cpp---------------------------------------

#include "TextQuery.h"

using namespace std;

//opens in binding it to the given file
ifstream& open_file(ifstream &in, const string &file)
{
   in.close();     //close in case it was already open
   in.clear();  //clear any existing errors
   //if the open fails, the stream will be in an invalid state
   in.open(file.c_str()); //open the file we were given
   return in;   //condition state is godd if open succeeded
}

//return plural version of word if ctr isn't 1
string make_plural(size_t ctr, const string &word, const string &ending)
{
   return (ctr == 1)? word : word+ending;
}

void print_results(const set<TextQuery::line_no> &locs, const string& sought, const TextQuery &file)
{
   //if the word was found, then print count and all occurrences
   typedef set<TextQuery::line_no> line_nums;
   line_nums::size_type size = locs.size();
   cout<<"\n"<<sought<<" occurs"<<size<<" "
   <<make_plural(size,"time","s")<<endl;

   //print each line in which the word appeared
   line_nums::const_iterator it = locs.begin();
   for(;it !=locs.end(); ++it){
      cout<<"\t(line "
      <<(*it) +1 <<") "
      <<file.text_line(*it)<<endl;
   }
}

//program takes single argument specifying the file to query
int main(int argc, char **argv)
{
   //open the file from which user will query words
   ifstream infile;
   if(argc < 2 || !open_file(infile,argv[1])){
    cerr<<"No input file!"<<endl;
    return EXIT_FAILURE;
 }
   TextQuery tq;
   tq.read_file(infile); //build query map
   //iterate with the user: prompt for a word to find and printf results
   //loop indefinitely: the loop exit is inside the while
   while(true){
      cout<<"enter word to look for, or q to quit:";
      string s;
      cin >> s;
    //stop if hit eof on input or a 'q' is entered
    if(!cin || s == "q" )break;
    //get the set of line numbers on which this word appears
    set<TextQuery::line_no> locs = tq.run_query(s);
    //print count and all occurrences,if any
    print_results(locs, s, tq);
 }
 return 0;
}

 

PS:  1.string对象的c_str,将string对象转化为char *

    2.istringstream对象可以绑定一行字符串,然后以空格为分隔符把该行分隔开来。在头文件<sstream>中.

    3.out_of_range一个异常类。在头文件<stdexcept>中。

posted @ 2012-11-30 19:47  Tiu.G  阅读(320)  评论(1编辑  收藏  举报