zoukankan      html  css  js  c++  java
  • 第十四篇:一个文本查询程序的实现

    前言

           本文将讲解一个经典的文本查询程序,对前面所学的容器相关知识进行一个从理论到实际的升华,同时也对即将学习的面向对象知识来一次初体验。

    程序描述

           要求实现这样一个程序:读取用户指定的文件,然后允许用户从中查找某个单词所在的位置。

    一个面向过程的落后的设计思想

      将待检索文件以行为单位存放到Vector容器中,然后遍历容器,将容器内元素依次转存到字符串流对象中,然后在内层遍历这个字符串流对象,检索是否存在与给定单词匹配的单词。如果有则输出该行内容以及该行序号。

    落后的原因及先进的设计思想

           这是我以前尝试解决这个问题的思路。其本质是一个典型的面向过程思想,只不过用容器简化了些操作罢了。

           这种思路使得每次执行检索都要重新遍历一次文件。如果当文件比较大的时候,这样的检索效率是不能为用户所接受的。

           最好的方法应该是采用面向对象的方法,设定一个类,该类封装一个数据结构专门记载关于单词与行号的信息,该类还同时封装初始化函数,查询函数等功能函数。

           另外,如果我这里不设定一个类,而是直接全局定义一个数据结构来记载关于单词与行号的信息,那么当还要对文本实现一些其他功能的时候,程序将会变得杂乱无章,代码里到处都是乱七八糟的数据结构和全局变量。这就是类封装性的好处,也是面向对象的美妙之处之一。

    下面,将用面向对象的思想“美妙”地设计出这个文本查询程序... ...

    第一步:设计类

           第1步:确定类所包含的方法

           1. read_file 函数:将指定的待检索文件存入容器并初始化”单词-行号“数据结构

           2. run_query 函数:获取待查询单词并返回单词在文本中的行号

           3. text_line 函数:获取某个行号,返回文件中该行的内容。

           第2步:确定类所包含的数据

           1. 一个string对象存放要查询的单词

           2. 一个vector容器存放待检索的文本

           3. 一个map容器存放单词和它对应的行号

           类定义如下():

     1 class TextQuery {
     2 public:
     3     // 为行号类型取个别名( 行号的类型实在是太长了 )
     4     typedef std::vector<std::string>::size_type line_no;
     5     // 将数据存入vector容器并初始化单词 - 行号数据结构
     6     void read_file(std::ifstream &is) {
     7         // 将待检索文件存入容器
     8         store_file(is);
     9         // 建立单词 - 行号数据结构
    10         build_map();
    11     }
    12     // 根据用户指定的单词执行查询并返回结果行号( 结果是放在一个set容器中的 )。
    13     std::set<line_no> run_query(const std::string&) const;
    14     // 根据行号返回该行内容
    15     std::string text_line(line_no) const;
    16 private:
    17     // 下面这两个函数是上面read_file 函数的实现函数,是内部函数因此设为私有。
    18     void store_file(std::ifstream&);    // 将数据存入vector容器
    19     void build_map();    // 初始化单词 - 行号数据结构
    20     // 一个vector容器
    21     std::vector<std::string> lines_of_text;
    22     // 一个单词 - 行号数据结构
    23     std::map< std::string, std::set<line_no> > word_map;    // 注意这个是容器的容器 因此尖括号后面要留空格
    24 };

           如此一来,整个程序的框架就显得豁然开朗。可见设计类这一环节的重要性。事实上,工程中常用UML之类的技术专门处理这个环节。

    第二步:实现类

           1. 实现store_file 函数:

    1 void TextQuery::store_file(ifstream & is) {
    2     string textline;
    3     while (getline(is, textline))
    4         lines_of_text.push_back(textline);
    5 }

           2. 实现bulid_map 函数:

    1 void TextQuery::build_map()
    2 {
    3     for (line_no line_num = 0; line_num != lines_of_text.size()) {
    4         istringstream line(lines_of_text[line_num]);
    5         string word;
    6         while (line >> word)
    7             word_map[word].insert(line_num);
    8     }
    9 }

           3. 实现run_query 函数:

    1 set<TextQuery::line_no>
    2 TextQuery::run_query(const string &query_word) const {
    3     map< string, set<line_no> >::const_iterator loc = word_map.find(query_word);
    4     if (loc == word_map.end())
    5         return set<line_no>();
    6     else
    7         return loc->second;
    8 }

           4. 实现text_line 函数:

    1 string TextQuery::text_line(line_no line) const {
    2     if (line < lines_of_text.size()) {
    3         return lines_of_text(line);
    4     }
    5     throw std::out_of_range("line number out of range");
    6 }

    第三步:编写主函数( 其实可以和第二步同时进行 提高效率 )

     1 void print_results(const set<TextQuery::line_no> & locs, const string &sought, const TextQuery &file);
     2 ifstream & open_file(ifstream &in, const string &file);
     3 string make_plural(size_t ctr, const string &word, const string &ending);
     4 
     5 int main(int argc, char **argv)
     6 {
     7     ifstream infile;
     8     if (argc < 2 || !open_file(infile, argv[1])) {
     9         cerr << "No input file!" << endl;
    10         return EXIT_FAILURE;
    11     }
    12 
    13     TextQuery tq;
    14     tq.read_file(infile);
    15 
    16     while (true) {
    17         cout << "inter a word to query:" << endl;
    18         string s;
    19         cin >> s;
    20         if (!cin || s == "q") break;
    21         set<TextQuery::line_no> locs = tq.run_query(s);
    22         print_results(locs, s, tq);
    23     }
    24 
    25     return 0;
    26 }
    27 
    28 void print_results(const set<TextQuery::line_no> & locs, const string &sought, const TextQuery &file) {
    29     // 为了表示下面这个size_type类型,还真煞费苦心。
    30     typedef set<TextQuery::line_no> line_nums;
    31     line_nums::size_type size = locs.size();
    32     cout << endl << sought << " occurs "  << size << " " << make_plural(size, "time", "s") << endl;
    33 
    34     line_nums::const_iterator it = locs.begin();
    35     for (; it != locs.end(); ++it) {
    36         cout << "	(line " << (*it) + 1 << ")" << file.text_line(*it) << endl;
    37     }
    38 }
    39 
    40 ifstream & open_file(ifstream &in, const string &file)
    41 {
    42     in.close();
    43     in.clear();
    44 
    45     in.open(file.c_str());
    46 
    47     return in;
    48 }
    49 
    50 string make_plural(size_t ctr, const string &word, const string &ending)
    51 {
    52     return (ctr == 1) ? word : word+ending;
    53 
    54 }

    说明

           第一步,第二步,第三步的代码文件分别为主函数源代码文件( .cpp ),类定义头文件( .h )和类实现源代码文件( .cpp )。

           在构建这种工程时,头文件和命名空间的设定是有讲究的( 若要生成真正的可执行程序,上面的代码还要根据一些原则进行改动 ),本文不进行详述。

    小结

           1. 体验面向对象”工程“ ( 这一部分目前也有点还没弄清楚 待深入学习 )

           2. 灵活使用容器

           3. 感受面向对象思想

  • 相关阅读:
    (十)条件判断
    (九)字符处理命令
    (八)awk命令
    (六)环境变量配置文件
    (七)grep命令行提取符号
    Ⅶ 类模板与STL编程 ②
    Ⅵ 虚函数与多态性
    Ⅴ 运算符重载
    Ⅳ 继承与派生②
    Ⅳ 继承与派生①
  • 原文地址:https://www.cnblogs.com/muchen/p/6352174.html
Copyright © 2011-2022 走看看