zoukankan      html  css  js  c++  java
  • 容器的综合应用:文本查询程序(摘自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>中。

  • 相关阅读:
    我劝你不要记账
    富人不存钱
    1w+的心路历程
    简历这么写才对!
    Android 开发凉了吗!
    三个思维转变
    说一说赚钱
    【原】Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(Matt Galloway著)读书笔记(二)
    【原】Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法(Matt Galloway著)读书笔记(一)
    【原】iOS开发进阶(唐巧)读书笔记(二)
  • 原文地址:https://www.cnblogs.com/guotao/p/2796739.html
Copyright © 2011-2022 走看看