习题10.23 编写程序将被排除的单词存储在vector对象中,而不是存储在set对象中。请指出使用set的好处。
使用set的好处是:可以简单地使用count函数来检查单词是否出现在排除集中。(而使用vector则需要循环比较来完成)
*********************************/
#include <iostream>
#include <set>
#include <map>
#include <stdexcept>
#include <fstream>
#include <sstream>
#include <vector>
using namespace std;
void restricted_wc(ifstream &remove_file, map<string, int>&word_count);
int main()
{
map<string, int> wordCount;
string fileName;
//读入包含单词排除集的文件的名字并打开相应文件
cout << "Enter filename:" << endl;
cin >> fileName;
ifstream exFile(fileName.c_str());
if(!exFile)//打开文件失败
{
cout << "error: can not open file:" << fileName << endl;
return -1;
}
restricted_wc(exFile, wordCount);
//输出map中的单词排除集中单词个数
cout << "word\t\t" << "times" << endl;
map<string, int>::iterator iter = wordCount.begin();
while(iter != wordCount.end())
{
cout << iter->first << "\t\t" << iter->second << endl;
iter++;
}
return 0;
}
void restricted_wc(ifstream &remove_file, map<string, int>&word_count)
{
//set<string> excluded; //set to hold words we'll ignore
vector<string> excluded; //保存被排除的单词
string remove_word;
while(remove_file >> remove_word)
//excluded.insert(remove_word); //error?
excluded.push_back(remove_word);
cout << "the words in vector:" << endl;
vector<string>::iterator sit = excluded.begin();
for(; sit != excluded.end(); ++sit)
cout << *sit << endl;
//读入文本并对不在排除集中的单词进行出现次数统计
string word;
cout << "enter a word that excluded:(ctrl+z to end)" << endl;
while(cin >> word)
{
bool find = false;
//确定该单词是否在排除集中
vector<string>::iterator sit = excluded.begin();
for(; sit != excluded.end(); ++sit) //处理的单个字符,而不是字符串
{
if(*sit == word)
{
find = true;
break;
}
}
//如果单词不在排除集中,则进行计数
if(!find)
++word_count[word];
}
}
/***************************************************************************************************
时间:2013-8-12
习题10.24:编写程序通过删除单词尾部的's',生成该单词的非复数版本。
同时,建立一个单词排除集,用以识别以's'结尾,但这个结尾的's'又不能删除的单词。
例如,放在该排除集中的单词可能有success和class。
使用这个排除集编写程序,删除输入单词的复数后缀,而如果输入的是排除集中的单词,则保持该电池不变。
注:怎么删除单词末尾的‘s’,word.resize(word.size() - 1); //去掉单词末尾的‘s’
int main()
{
set<string> excluded;
//建立单词排除集
excluded.insert("success");
excluded.insert("class");
//....//可以在此处继续插入其他以s结尾的单词
string word;
cout << "enter a word(ctrl+z to end)" << endl;
//读入单词并根据排除生成单词的非复数版本
while(cin >> word)
{
if(!excluded.count(word)) //该单词未在排除集合中出现
word.resize(word.size() - 1); //去掉单词末尾的‘s’
cout << "non-plural vesion:" << word << endl;
cout << "enter a word(ctrl+z to end)" << endl;
}
return 0;
}
习题10.25
自己写的,很稀烂。
//时间:2013-8-12
//定义一个vector容器,存储你在未来六个月里要阅读的书,再定义一个set,用于记录你已经看过的书。
//编写程序从vector中为你选择一本没有读过而现在要读的书。
//当它位你返回选中的书名后,应该将该书名放入记录已读书目的set中。
//如果实际上你把这本书放在一边没有看,则本程序应该支持从已读书目的set中删除该书的目录。
//在虚拟的六个月后,输出已读书目和没有读的书目。
/*
Q1:怎么查找vector的一个元素?find?
A1:
Q2:怎么在已读书目的set中删除目录,同时添加到vector容器中?
怎么在vector删除未读书目同时把该书目添加到set中?
A1:定义vector容器,里面是pair类型的对象,vector<pair<string,int>>,
其中string代表书目,int标记该书是否已读?
*/
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main()
{
vector<string> svec;
set<string> sset;
string book_name;
// bool is_vec = false;
cout << "enter book name to vector:(ctrl+z to end)" << endl;
while(cin >> book_name)
svec.push_back(book_name);
cin.clear();//added
cout << "enter a book you want to read:(ctrl+z to end)" << endl;
while(cin >> book_name)
{
//is_vec = false;
vector<string>::iterator sit = svec.begin();
while(sit != svec.end())
{
if(book_name == *sit) //如果输入的书目在vector,判断再判断该书
{
if(!sset.count(*sit)) //如果book_name未被记录为已读
{
sset.insert(*sit); //将此book_name标记为已读
//svec.erase();将此数删除
}
else
{
cout << "the book you choose has read,pls input again:" << endl;
}
break;
}
++sit;
}
if(sit == svec.end())
{
cout << "the book you choose not included in vector,pls input again:" << endl;
continue;
}
}
//输出set容器中已读书
set<string>::iterator set_str = sset.begin();
for(; set_str != sset.end(); ++set_str)
cout << *set_str << ' ';
cout << endl;
//输出vector容器中未读的书
vector<string>::iterator sit = svec.begin();
for(; sit != svec.end(); ++sit)
cout << *sit << ' ';
cout << endl;
return 0;
}
注:1)答案用到了随机数来从vector对象生成一个书目,比在vector查找书目方便多了。对随机数使用不太熟悉。
#include <iostream>
#include <vector>
#include <set>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
vector<string> books;
set<string> readedBooks;
string name;
//建立保存未来6个月要阅读的书名的vector对象
cout << "enter names for books you'd like to read:(ctrl+z to end)" << endl;
while(cin >> name)
books.push_back(name);
cin.clear();//使流对象有效
bool timeOver = false;
string answer, bookName;
//用当前系统时间设置随机数发生器种子
srand((unsigned)time(NULL));
//模拟随时间的流逝而改变读书记录
while(!timeOver && !books.empty())
{
//时间未到6个月还有书没有读过
cout << "would you like to read to book?(Yes/No)" << endl;
cin >> answer;
if(answer[0] == 'y' || answer[0] == 'Y')
{
//在vector中随机选择一本书
int i = rand() % books.size(); //产生一个伪随机数
bookName = books[i];
cout << "You can read this book:"
<< bookName << endl;
readedBooks.insert(bookName);//将该书放入已读集合
books.erase(books.begin() + i); //从vector对象中删除该书
cout << "did you read it?(Yes/No)" << endl;
cin >> answer;
if(answer[0] == 'n' || answer[0] == 'N')
{
//没有读这本书
readedBooks.erase(bookName);//从已读集合中删除该书
books.push_back(bookName);//将该书重新放入vector中
}
}
cout << "Time over?(Yes/No)" << endl;
cin >> answer;
if(answer[0] == 'y' || answer[0] == 'Y')
{
//虚拟的6个月结束了
timeOver = true;
}
}
if(timeOver)//虚拟的6个月结束了
{
//输出已读书目
cout << "books read:" << endl;
for(set<string>::iterator sit = readedBooks.begin();
sit != readedBooks.end();++sit)
cout << *sit << endl;
//输出还没有读的书目
cout << "books not read:" << endl;
for(vector<string>::iterator vit = books.begin();
vit != books.end(); ++vit)
cout << *vit << endl;
}
else
cout << "congraturations! You have read all these books."
<< endl;
return 0;
}
10.6 容器的综合应用:文本查询程序
/****************************************************
时间:2013-8-13
习题:将读取用户指定的任意文本文件,然后允许用户从该文件中查找单词。查询的结构是该单词出现的次数,并列出每次所在的行。如果某单词在同一行中多次出现,程序将只显示该行一次。行号按升序显示。
如:查找单词“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
*****************************************************************/
这个程序搞了一两天,很多问题:对set和map的理解和使用不熟悉;对类的接口,返回值等不熟悉。程序用一个TextQuery类来实现,使用了几种容器。
1.数据结构
1)使用vector<string>来存储输入文本文件的副本。注意,vector的每个元素都是每一行的文本,这里可以用getline来每行每行的读入;还是就是,在输出某一行时,只需以行号为下标来输出该行所在的元素。(在后面的string TextQuery::text_line(line_no line) const)这个函数中,我用的循环来查找行号与line_no相等的下标,再输出,没有必要,可以直接根据line_no(即下标)访问来输出对应的文本行。)
2)将每个单词所在的行号存储在一个set容器对象中。使用set就可确保每行只有一个条目,而且行号将自动按升序排列。(关于这里,有点迷糊,为什么要用set对象来保存行号?)
3)还使用一个map容器将每个单词和set容器对象关联起来。在find在map容器中查找单词时,可以知道该单词对应的行号。
2.接口
书上一段话,有点不理解:“查询的过程非常简单:使用下标访问map对象获取关联的set对象即可。唯一的问题是如何返回找到的set对象。安全的设计方案是返回该set对象的副本。但如此一来,意味着要复制set中的每一个元素。如果处理的是一个相当大的文件,则复制set对象的代价非常昂贵。其中可行的方法:返回一个pair对象,存储一对指向set中元素的迭代器;或者返回set对象的const引用。”
“TextQuery.h"部分
//TextQuery.h部分
#include <iostream>
#include <map>
#include <set>
#include <cstring>
#include <vector>
#include <fstream>
using namespace std;
class TextQuery
{
public:
typedef vector<string>::size_type line_no;//修改1:行号为vector<>::size_type 类型,这里可以用typedef
//void read_file(ifstream &is); //
void read_file(ifstream &is)
{
store_file(is); //修改2:添加了两个private函数(不知道为什么?)
build_map();
}
//const set<line>& run_query(string s);
set<line_no> run_query(string &s) const; //修改3:返回值为set对象的const引用?
//const int text_line(int line);
//line_no text_line(line_no); //返回该行对应的文本行
string text_line(line_no) const; //修改4:定义常成员函数const放在后面
//void store_file(ifstream&);
private:
//修改
//实现read_file
void store_file(ifstream&); //store input file
void build_map(); //
//remeber the whole input file
vector<string> lines_of_text;
//set<line>;
//map word to set of lines on which it occurs
map<string, set<line_no> > word_map;
//map<string, set<line> > my_map;
//string str; //每行文本
//int line; //行号
};
TextQuery.cpp部分
void TextQuery::store_file(ifstream &is)
{
string line;
//①类的私有函数怎么访问私有变量?
// while(is >> word)
// lines_of_text.push(word);
while(getline(is, line))
line_of_text.push_back(line);
}
//②输入完毕后,read_file将创建关联单词与行号的map容器?
//前面将每行的文本读到vector中,vector中的每一个元素是一行文本,
//怎么提取这一行文本的所有单词并且都记录为在当前行号?
//
void TextQuery::build_map()
{
//向map<string,set<line_no> >插入元素?
//vector<string>::size_type ix = 0; //此处元素string为一行文本?
line_no ix = 0; //类里已用typedef定义为line_no
//使用istringstream将每一行的单词提取出来
for(; ix != line_of_text.size(); ++ix)
{
//istringstream stream(*iter);//?可以改为用下标来访问vector元素
istringstream stream(lines_of_text[ix]);
string word;
while(stream >> word)
{
// map<string, set<line_no> >::const_iterator map_it =
// word_map.find(word);
//
// if(map_it != word_map.end())
// map_it->second = ix;
//word_map.insert(make_pair(word, ix));//自己写的
word_map[word].insert(ix);//答案?这句没想明白
}
}
}
//返回该行的文本
string TextQuery::text_line(line_no line) const
{
//访问vector,根据line_no输出对应的行string?注意:行号与该行的文本行对应
// vector<string>::size_type ix = 0;
//
// for(; ix != lines_of_text.size(); ++ix)
// if(ix == line)
// return lines_of_text[ix];
//不需要遍历vector,形参已经给出下标(即行号)来访问
if(line < lines_of_text.size())
return lines_of_text[line];
throw out_of_range("line number of range");
}
//set对象包含出现该string对象的所有行的行号
set<line_no> TextQuery::run_query(string &s) const
{
//map对象的遍历
map<string, set<line_no> >::const_iterator =
map_it = word_map.begin();
while(map_it != word_map.end())
{
if(map_it->first == s)
return map_it->second;
++map_it;
}
return 0;
}
run_query()函数答案用到find函数来查找(),使用find好处,如果指定键的元素不存在,就不会在容器中创建新的元素。(可是没有明白下面为什么不用循环,每次find只能返回一个值,怎么返回得到查找单词的所有行好的?还有这个map容器,一个键不是只能对应一个值吗?word[word].insert(ix),不理解?)
//答案:
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;
}