C++ Primer 学习笔记_38_STL实践与分析(12)--容器的综合应用:文本查询程序

STL 实践与分析

-- 容器的综合应用:文本查询程序

引言:

本章中最重点的实例,因为不需要用到 multiset 与 multimap 的内容,于是将这一小节提到了前面,通过这个实例程序,大师分析问题的智慧,大师的编程风格,大师对程序的控制能力,由此可见一斑。因此,我对这一小节的内容几乎不做修改,或只做很小的更改(因为有些东西不同人有不同的理解),搬出来,以供大家仔细品读。

要求:

我们的程序将读取用户指定的 任意文本文件 , 然后允许用户从该文件中查找单词。 查询的结果是该单词出现的次数 , 并列出每次出现所在的行。 如果某单词在同一行中多次出现 , 程序将只显示该行一次 。行号按升序显示。

样例:

以本章的内容作为文件输入 , 然后查找单词“ element” 。输出的前几行应为 :

element occurs 125 times
  (line 62) element with a given key.
  (line 64) second element with the same key.
  (line 153) element |==| operator.
  (line 250) the element type.
  (line 398) corresponding element.
  …

一、查询程序的设计

设计程序的一个良好习惯是首先将程序所涉及的操作先列出来,明确需要提供的操作有助于建立需要的数据结构和实现这些行为 。从需求出发,我们的程序需要支持以下任务:

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

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

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

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

1 、数据结构

设计一个简单的 TextQuery 类来实现这个程序:

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

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

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

2 、操作

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

事实上 , 查询的过程相当简单 : 使用下标访问 map 对象获取关联的 set 对象即可。唯一的问题是如何返回所找到的 set 对象。安全的设计方案是返回该 set 对象的副本。但如此一来 , 就意味着要复制 set 中的每个元素。如果处理的是一个相当庞大的文件 ,则复制 set 对象的代价会非常昂贵。其他可行的方法包括 : 返回一个 pair 对象 , 存储一对指向 set 中元素的迭代器 ; 或者返回 set 对象的 const 引用。为简单起见 , 我们在这里采用返回副本的方法 , 但注意 : 如果在实际应用中复制代价太大 , 需要新考虑其实现方法。

第一、第三和第四个任务是使用这个类的程序员将执行的动作。第二个任务则是类的内部任务。将这四任务映射为类的成员函数 , 则类的接口需提供下列三个 public函数 :

1 ) read_file 成员函数 , 其形参为一个 ifstream& 类型对象。该函数每次从文件中读入一行 , 并将它保存在 vector 容器中。输入完毕后 ,read_file 将创建关联每个单词及其所在行号的 map 容器。

2 ) run_query 成员函数 , 其形参为一个 string 类型对象 , 返回一个 set 对 象 , 该set 对象包含出现该 string 对象的所有行的行号。

3 ) text_line 成员函数 , 其形参为一个行号 , 返回输入文本中该行号对应的文本行。

无论 run_query 还是 text_line 都不会修改调用此函数的对象 , 因此 , 可将这两个操作定义为 const 成员函数。

为实现 read_file 功能 , 还需定义两个 private 函数来读取输入文本和创建 map 容器:

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

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

二、 TextQuery 类

//in TextQuery.h
class TextQuery
{
public:
  typedef std::vector<std::string>::size_type line_no;
  void read_file(std::ifstream &is)
  {
    store_file(is);
    build_map();
  }
  std::set<line_no> run_query(const std::string &) const;
  std::string text_line(line_no) const;

private:
  void store_file(std::ifstream &);
  void build_map();

  std::vector<std::string> line_of_text;
  std::map< std::string,std::set<line_no> > word_map;
};

三、 TextQuery 类的使用

1 、主程序

//in main.cpp
/**记得添加
*#include "TextQuery.h" 类的定义
*#include "other.h"	 其他函数的声明
*/
int main(int argc,char **argv)
{
  ifstream inFile;
  if (argc != 2 || !open_file(inFile,argv[1]))
  {
    cerr << "No input file!" << endl;
    return EXIT_FAILURE;
  }

  TextQuery tq;
  tq.read_file(inFile);
  while (true)
  {
    cout << "enter word to look for,or q/Q to quit: ";
    string s;
    cin >> s;
    if (!cin || s == "q" || s == "Q")
      break;

    set<TextQuery::line_no> locs = tq.run_query(s);
    print_results(locs,s,tq);
  }
  return 0;
}

2 、辅助函数

print_results 函数、 make_plural 函数、 open_file 函数的定义 :

//in other.cpp
/**记得添加
*#include "TextQuery.h" 类的定义
*#include "other.h"	 其他函数的声明
*/
string make_plural(set<TextQuery::line_no size,const string &begin,const string &ends);
ifstream &open_file(ifstream &in,const string &file)
{
  in.close();
  in.clear();
  in.open(file.c_str());

  return in;
}

void print_results(set<TextQuery::line_no> &locs,
           const string &sought,
           const TextQuery &file)
{
  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;

  line_nums::iterator iter = locs.begin();
  while (iter != locs.end())
  {
    cout << "\t(line "
       << (*iter) + 1 << ")"
       << file.text_line(*iter) << endl;
  }
}

string make_plural(set<TextQuery::line_no size,
           const string &begin,
           const string &ends)
{
  return (size <= 1 ? begin : begin + ends);
}

四、编写成员函数 [inTextQuery.h]

1 、存储输入文件

void TextQuery::store_file(ifstream &is)
{
    string textline;
    while (getline(is,textline))
    {
        line_of_text.push_back(textline);
    }
}

2 、建立单词 map 容器

void TextQuery::build_map()
{
    for (line_no line_num = 0;
      line_num != line_of_text.size();
      ++line_num)
    {
  istringstream line(line_of_text[line_num]);
  string word;

  while (line >> word)
  {
      word_map[word].insert(line_num);
  }
    }
}

【分析:】

word_map[word].insert(line_num);

将 word 用做 map 容器的下标。如果 word 在 word_map 容器对象中不存在 , 那么下标操作符将该 word 添加到此容器中 , 并将其关联的值初始化为空的 set 。不管是否添加了 word, 下标运算都返回一个 set 对象 , 然后调用 insert 函数在该 set 对象中添加当前行号。如果某个单词在同一行中重复出现 , 那么 insert 函数的调用将不做任何操作。

3 、支持查询

set<TextQuery::line_no>
TextQuery::run_query(const std::string &query_word) const
{
  map<string,set<line_no> >::const_iterator
  loc = word_map.find(query_word);

  if (loc == word_map.end())
  {
    return set<TextQuery::line_no>();
  }
  else
  {
    return loc -> second;
  }
}

run_query 函数带有指向 conststring 类型对象的引用参数 , 并以这个参数作为下标来访问 word_map 对象。假设成功找到这个 string, 那么该函数返回关联此 string 的 set对象 , 否则返回一个空的 set 对象。

4 、 run_query 返回值的使用

string TextQuery::text_line(line_no line) const
{
    if (line < line_of_text.size())
    {
        return line_of_text[line];
    }
    throw out_of_range("line number out of range");
}
时间: 2024-11-05 14:40:34

C++ Primer 学习笔记_38_STL实践与分析(12)--容器的综合应用:文本查询程序的相关文章

C++ Primer 学习笔记_45_STL实践与分析(19)--泛型算法的结构

STL实践与分析 --泛型算法的结构 引言: 正如全部的容器都建立在一致的设计模式上一样,算法也具有共同的设计基础. 算法最主要的性质是须要使用的迭代器种类.全部算法都指定了它的每一个迭代器形參可使用的迭代器类型.比方,假设形參必须为随机訪问迭代器则可提供vector或 deque类型的迭代器,或者提供指向数组的指针.而其它容器的迭代器不能用在这类算法上. C++还提供了另外两种算法模式:一种模式由算法所带的形參定义;还有一种模式则通过两种函数命名和重载的规范定义. 一.算法的形參模式 大多数的

C++ Primer 学习笔记_46_STL实践与分析(20)--容器特有的算法

STL实践与分析 --容器特有的算法 与其它顺序容器所支持的操作相比,标准库为list容器定义了更精细的操作集合,使它不必仅仅依赖于泛型操作.当中非常大的一个原因就是list容器不是依照内存中的顺序进行布局的,不支持随即訪问,这样,在list容器上就不能使用随即訪问迭代器的算法,如sort等:还有其它的一些算法如:merge.remove.reverse和unique,尽管能够用在list上,但却付出了高昂的性能代价.因此标准库结合list的内部结构,编写出了更快算法: list容器特有的操作

C++ Primer 学习笔记_29_STL实践与分析(3) --操作步骤集装箱(下一个)

STL实践与分析 --顺序容器的操作(下) 六.訪问元素 假设容器非空,那么容器类型的front和back成员将返回容器的第一个和最后一个元素的引用. [与begin和end的对照:] 1)begin和end返回容器类型的迭代器,而不是引用: 2)end返回容器最后一个元素的下一个位置的迭代器,而back返回容器的最后一个元素的引用! /* *必须保证该list容器非空! *假设容器为空,则if语句内的全部操作都是没有定义的! */ if (!iList.empty()) { list<int>

C++ Primer 学习笔记_35_STL实践与分析(9)--map种类(在)

STL实践与分析 --map类型(上) 引: map是键-值对的集合. map类型通常能够理解为关联数组:能够通过使用键作为下标来获取一个值,正如内置数组类型一样:而关联的本质在于元素的值与某个特定的键相关联,而并不是通过元素在容器中的位置来获取. 一.map对象的定义 1.定义map对象时,必须分别指明键和值的类型: map<string,int> wordCnt; map的构造函数 map<K,V>m; 创建一个名为m的空对象,其键和值的类型分别为K和V map<K,V&

C++ Primer 学习笔记_43_STL实践与分析(17)--再谈迭代器【中】

STL实践与分析 --再谈迭代器[中] 二.iostream迭代[续] 3.ostream_iterator对象和ostream_iterator对象的使用 能够使用ostream_iterator对象将一个值序列写入流中,其操作过程与使用迭代器将一组值逐个赋值给容器中的元素同样: ostream_iterator<string> out_iter(cout,"\n"); istream_iterator<string> in_iter(cin),eof; wh

C++ Primer 学习笔记_41_STL实践与分析(15)--先来看看算法【下一个】

STL实践与分析 --初窥算法[下] 一.写容器元素的算法 一些算法写入元素值.在使用这些算法写元素时一定要当心.必须确保算法所写的序列至少足以存储要写入的元素. 1.写入输入序列的元素 写入到输入序列的算法本质上是安全的--仅仅会写入与指定输入范围数量同样的元素. 写入到输入序列的一个简单算法是fill函数: fill(iVec.begin(),iVec.end(),10); fill(iVec.begin(),iVec.begin()+iVec.size()/2,0); fill带有一对迭代

C++ Primer 学习笔记_45_STL实践与分析(19)--建筑常规算法

STL实践与分析 --泛型算法的结构 引言: 正如全部的容器都建立在一致的设计模式上一样,算法也具有共同的设计基础. 算法最主要的性质是须要使用的迭代器种类.全部算法都指定了它的每一个迭代器形參可使用的迭代器类型. 比方,假设形參必须为随机訪问迭代器则可提供vector或 deque类型的迭代器,或者提供指向数组的指针. 而其它容器的迭代器不能用在这类算法上. C++还提供了另外两种算法模式:一种模式由算法所带的形參定义;还有一种模式则通过两种函数命名和重载的规范定义. 一.算法的形參模式 大多

C++ Primer 学习笔记_44_STL实践与分析(18)--再谈迭代器【下】

STL实践与分析 --再谈迭代器[下] 三.反向迭代器[续:习题] //P355 习题11.19 int main() { vector<int> iVec; for (vector<int>::size_type index = 0; index != 10; ++index) { iVec.push_back(index); } for (vector<int>::reverse_iterator r_iter = iVec.rbegin(); r_iter !=

C++ Primer 学习笔记_40_STL实践与分析(14)--概要、先来看看算法【上】

STL实践与分析 --概述.初窥算法[上] 标准库容器定义的操作很少.并没有给容器加入大量的功能函数.而是选择提供一组算法,这些算法大都不依赖特定的容器类型,是"泛型"的.可作用在不同类型的容器和不同类型的元素上! 所谓泛型算法:一是由于它们实现共同的操作,所以称之为"算法";而"泛型"指的是它们可以操作在多种容器类型上--不但可作用于vector或list这些标准库类型,还可用在内置数组类型.甚至其它类型的序列上,仅仅要自己定义的容器类型仅仅要