#Essential C + +, Chapter 3 generic programming style

Posted by prinzcy on Mon, 03 Feb 2020 14:53:05 +0100

Basic knowledge

array and vector are continuous storage space, which can be accessed by arithmetic operation of pointer. List is also a container. The difference is that the elements of list are linked by a set of pointers: the forward pointer points to the next element, and the backward pointer points to the previous element. Therefore, the arithmetic operation of the pointer is not applicable to the list. The solution to this problem is to provide a layer of abstraction above the behavior of the underlying pointer, replacing the original "pointer direct operation" mode of the program. We put the underlying pointer processing in this abstract layer, so that users do not have to face pointer operation directly, so that the iterator (generic pointer) came into being.

Generic algorithms provide many operations on container classes and types. These algorithms are called generic because they are independent of the type of element they want to operate on. The generic operators achieve the goal of "being independent of the type of the operands" through the function template technology.

Using istream iterator to read a string from a standard input device, we need a pair of iterators: first and last to indicate the range of elements. The following definitions:

istream_iterator<string> is(cin);

It provides us with a first iterator, which defines is as an istream [iterator] bound to a standard input device. We also need a last iterator to represent "the next location of the last element to read.". For non-standard input devices, end of file stands for last. As long as the istream object is not specified when defining istream [iterator], it represents end of file, for example:

istream_iterator<string> eof;

Common operation of all containers

equality (= =) and inequality (! =) operators, return true or false.

assignment (=) operator to copy one container to another.

empty() returns true when the container has no elements, otherwise false.

size() returns the number of elements currently held in the container.

clear() delete all elements

Each container provides two functions, begin() and end(), which return the iterator pointing to the next location of the first and last elements of the container respectively. In general, the iterations we do on the container start with begin () and end ().

All containers provide insert () to insert elements, and erase() to delete elements.

insert() inserts a single or scoped element into the container.

erase() removes a single element or a range of elements from the container.

The behavior of insert() and erase() differs depending on whether the container itself is a sequential or associative container.

Sequential container

Sequential containers are used to maintain a set of elements that are arranged in an orderly manner and of the same type. Here are three main sequential containers: vector, list, and deque.

Vector stores elements in a contiguous block of memory. Random access to a vector, for example, taking its fifth element first, then its 17th element, and then its ninth element, is quite efficient; each element in the vector is stored at a fixed offset from the starting point. If an element is inserted into any position, and this position is not the end of the vector, the efficiency will be very low, because each element at the right end of the insertion position must be copied and moved to the right in turn. In the same way, deleting any element other than the last one in a vector is also inefficient.

List stores content in double linked rather than continuous memory, so you can perform forward or backward operations. Each element in the list contains three fields: value, back pointer (to the previous element), and front pointer (to the next element). It is quite efficient to insert or delete elements anywhere in the list, because the list itself only needs to set the back pointer and the front pointer properly. But if we want to access the list randomly, the efficiency is not good.

The third kind of sequential container is called deque. Deque behaves much like vector - it stores elements in contiguous memory. Different from vector, deque is more efficient for the insertion and deletion of the front-end elements; the same is true for the end elements. (the queue of the standard library is implemented with deque, that is, with deque as the bottom storage element.)

There are five ways to define sequential container objects:

1. Generate empty container:

list<string> slist;
vector<int> ivec;

2. Generate a container of a specific size. Each element starts with its default value:

list<int> ilist(1024);
vector<string> svec(32);

3. Generate a container of a specific size and specify an initial value for each element:

vector<int> ivec(10, -1);
list<string> slist(16, "unassigned");

4. A container is generated by a pair of iterator s, which are used to indicate the range of a whole set of elements as the initial value:

int ia[8] = { 1,1,2,3,5,8,13,21 };
vector<int> fib(ia, ia + 8);

5. Generate a new container according to a container, copy the elements in the original container as the initial value of the new container:

list<string> slist;
list<string> slist2(slist);


There are two special operation functions that allow us to insert and delete at the end of the container: push back() and pop back(). Push back() inserts an element at the end, and pop back() removes the last element. In addition, list and deque (but not vector) also provide push ﹣ front() and pop ﹣ front(), allowing insertion and deletion at the front of the container.

Associative container

Associating containers allows us to quickly find element values in the container.

Map is a pair of key/value combinations. Key is used for searching and value is used to indicate the data we want to store or retrieve. For example, a phone number can be easily represented by a map. A phone user name is a key, and value is associated with a phone number.

Set, containing only key. We query it to determine whether a value exists in it. If we want to create a set of index tables to record the words appearing in news and stories, we may want to exclude some neutral words such as the, an, but. Before entering a word into the index table, we need to query a set such as "exclude" word. If the word is in it, we will ignore it and no longer care about it; otherwise, we will add it to the index table.

Exercise answer

Exercise 3.1 write a program to read a text file and store each word in the file into a map. The key of map is the word just mentioned, and the value of map is the number of times the word appears in the text file. Then define a set composed of "exclusion words", which contains words such as a, an, or, the, and and but. Before putting a word in the map, make sure that the word is not in the exclude word set. Once the text file has been read, please display a list of words and the number of occurrences of each word. You can even extend it to allow users to query whether a word appears in a text file before displaying it.

#include <map>
#include <set>
#include <string>
#include <iostream>
#include <fstream>

using namespace std;

void initialize_exclusion_set(set<string>&);
void process_file(map<string, int>&, const set<string>&, ifstream&);
void user_query(const map<string, int>&);
void display_word_count(const map<string, int>&, ofstream&);

int main()
{
    ifstream ifile("1.txt");
    ofstream ofile("2.txt");
    if (!ifile || !ofile)
    {
        cerr << "Unable to open file -- bailling out!\n";
        return -1;
    }
    set<string> exclude_set;
    initialize_exclusion_set(exclude_set);
    map<string, int> word_count;
    process_file(word_count, exclude_set, ifile);
    user_query(word_count);
    display_word_count(word_count, ofile);
    return 0;
}

void initialize_exclusion_set(set<string>& exs)
{
    static string _excluded_words[25] = {
        "the","and","but","that","then","are","been",
        "can","a","could","did","for","of",
        "had","have","him","his","her","its","is",
        "were","which","when","with","would"
    };
    exs.insert(_excluded_words, _excluded_words + 25);
}

void process_file(map<string, int>& word_count, const set<string>& exclude_set, ifstream& ifile)
{
    string word;
    while (ifile >> word)
    {
        if (exclude_set.count(word))
            continue;
        word_count[word]++;
    }
}

void user_query(const map<string, int>& word_map)
{
    string search_word;
    cout << "Please enter a word to search( q to quit ): ";
    cin >> search_word;
    while (search_word.size() && search_word != "q")
    {
        map<string, int>::const_iterator it;
        if ((it = word_map.find(search_word)) != word_map.end())
            cout << "Found! " << it->first
            << " occurs " << it->second
            << " times.\n";
        else cout << search_word
            << " was not fount in text.\n";
        cout << "\nAnother search?( q to quit) ";
        cin >> search_word;
    }
}

void display_word_count(const map<string, int>& word_map, ofstream& os)
{
    map<string, int>::const_iterator iter = word_map.begin(),
        end_it = word_map.end();
    while (iter != end_it)
    {
        os << iter->first << " ( "
            << iter->second << " ) " << endl;
        ++iter;
    }
    os << endl;
}

Exercise 3.2 read the contents of the text file - as in exercise 3.1 - and save the contents in the vector. Sort vectors based on the length of the string. Define a function object and pass it to sort(); this function object accepts two strings. When the length of the first string is less than the length of the second string, it returns true. Finally, print the sorted vector content.

#include <iostream>
#include <vector>
#include <fstream>
#include <algorithm>

using namespace std;

class LessThan
{
public:
    bool operator()(const string& s1, const string& s2)
    {
        return s1.size() < s2.size();
    }
};

template <typename elemType>
void display_vector(const vector<elemType>& vec, ostream& os = cout, int len = 8)
{
    vector<elemType>::const_iterator iter = vec.begin(),
        end_it = vec.end();
    int elem_cnt = 1;
    while (iter != end_it)
    {
        os << *iter++
            << (!(elem_cnt ++ % len) ? '\n' : ' ');
    }
    os << endl;
}

int main()
{
    ifstream ifile("1.txt");
    ofstream ofile("2.txt");
    if (!ifile || !ifile)
    {
        cerr << "Unable to open file -- bailing out!\n";
        return -1;
    }
    vector<string> text;
    string word;
    while (ifile >> word)
        text.push_back(word);
    sort(text.begin(), text.end(), LessThan());
    display_vector(text, ofile);
    return 0;
}

Exercise 3.3 define a map with the family name as key, and value is the first name of all children in the family. In addition, this map can hold at least six data. It allows users to query by last name and print every data in the map.

#include <iostream>
#include <vector>
#include <map>
#include <fstream>
#include <string>

using namespace std;

typedef vector<string> vstring;

void populate_map(ifstream& nameFile, map<string, vstring>& families)
{
    string textline;
    while (getline(nameFile, textline))
    {
        string fam_name;
        vector<string> child;
        string::size_type pos = 0, prev_pos = 0,
            text_size = textline.size();
        //Find all words separated by spaces
        while ((pos = textline.find_first_of(' ', pos)) != string::npos)    //string::npos Represents until the end of a string
        {
            //Calculate all endpoints from string
            string::size_type end_pos = pos - prev_pos;
            //If prev_pos If it is not set (or the value is 0), then the word read is the family name, otherwise we will read the children's first names one by one
            if (!prev_pos)
                fam_name = textline.substr(prev_pos, end_pos);
            else child.push_back(textline.substr(prev_pos, end_pos));
            prev_pos = ++pos;
        }
        //Now deal with the last child's name
        if (prev_pos < text_size)
            child.push_back(textline.substr(prev_pos, pos - prev_pos));
        if (!families.count(fam_name))
            families[fam_name] = child;
        else
            cerr << "Oops! We already hava a "
            << fam_name << " family in our map!\n";
    }
}

void display_map(const map<string, vstring>& families, ostream& os)
{
    map<string, vstring>::const_iterator it = families.begin(),
        end_it = families.end();
    while (it != end_it)
    {
        os << "The " << it->first << " family ";
        if (it->second.empty())
            os << "has no children\n";
        else
        {
            os << "has " << it->second.size() << " children: ";
            vector<string>::const_iterator iter = it->second.begin(),
                end_iter = it->second.end();
            while (iter != end_iter)
            {
                os << *iter << " ";
                ++iter;
            }
            os << endl;
        }
        ++it;
    }
}

void query_map(const string& family, const map<string, vstring>& families)
{
    map<string, vstring>::const_iterator it = families.find(family);
    if (it == families.end())
    {
        cout << "Sorry. The " << family
            << " is not currently entered.\n";
        return;
    }
    cout << "The " << family;
    if (!it->second.size())
        cout << " has no children.\n";
    else
    {
        cout << " has " << it->second.size() << " children: ";
        vector<string>::const_iterator iter = it->second.begin(),
            end_iter = it->second.end();
        while (iter != end_iter)
        {
            cout << *iter << " ";
            ++iter;
        }
        cout << endl;
    }
}

int main()
{
    map<string, vstring> families;
    ifstream nameFile("1.txt");
    if (!nameFile)
    {
        cerr << "Unable to find the file. bailling out!\n";
        return -1;
    }
    populate_map(nameFile, families);
    string family_name;
    while (1)
    {
        cout << "Please enter a family name or q to quit ";
        cin >> family_name;
        if (family_name == "q")
            break;
        query_map(family_name, families);
    }
    display_map(families, cout);
    return 0;
}

Exercise 3.4 write a program that uses istream iterator to read a series of integers from a standard input device. Use ostream iterator to write the odd numbers to a file, and each value is separated by a space. The ostream iterator is used to write even numbers to another file, with each value on a separate line.

#include <iterator>
#include <vector>
#include <iostream>
#include <algorithm>
#include <fstream>

using namespace std;

class even_elem
{
public:
    bool operator()(int elem)
    {
        return elem % 2 ? false : true;
    }
};

int main()
{
    vector<int> input;
    istream_iterator<int> in(cin), eos;
    ofstream even_file("even.file.txt"), odd_file("odd_file.txt");
    if (!even_file || !odd_file)
    {
        cerr << "arghh! unable to open the output files. bailling out!";
        return -1;
    }
    //take ostream_iterator Bind to the corresponding ofstream Object, the second parameter represents the separator when each element is output.
    ostream_iterator<int> even_iter(even_file, "\n"), odd_iter(odd_file, " ");
    copy(in, eos, back_inserter(input));
    vector<int>::iterator division = partition(input.begin(), input.end(), even_elem());
    copy(input.begin(), division, even_iter);
    copy(division, input.end(), odd_iter);
    return 0;
}
end.

"Where there is a will, there is a way, and the one hundred and two Qinguan pass will belong to Chu."

Topics: C++ less