您的位置:首页 > 编程语言 > C语言/C++

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

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

本例有如下需求:

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>中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: