!!!Chapter 11 Generic Algorithms

来源:互联网 发布:意式 咖啡机推荐 知乎 编辑:程序博客网 时间:2024/06/15 10:31

11.1 Overview

The find function takes two iterators and a value. If it finds the value, it will return an iterator pointing to that value; otherwise, it will return the second iterator (the second argument).

The first iterator should be the first element we want to check; the second iterator should be one pass the last element we want to check:

//only search elements ia[1] and ia[2]int *result = find(ia +1, ia +3, search_value);

11.2 A first Look at the Algorithms

To use a generic algorithm, we must include the algorithm header:

#include <algorithm>

The library also defines a set of generalized numeric algorithms, which are defined under numeric header:

#include <numeric>

Besides few exceptions, all the algorithm operate over a range of elements, which are referred to as "input range". The algorithm that take an input range always use their first two parameters to denote that range.

These two parameters are iterators denote the first and one past the last element that we want to process.

Algorithms normally do three things: read elements, write elements, rearrange the order of elements.

11.2.1 Read-Only Algorithms

find is one algorithm that only read elements.

accumulate is also a read-only algorithm that defined in numeric header:

//vec is a vector of int values//sum the elements in vec starting the summation with the value 42 (42+vec[0]+...)int sum = accumulate(vec.begin(), vec.end(), 42);

The return type of accumulate is the type of its third argument. The third argument is necessary, because accumulate knows nothing about the element types that it is accumulating. Also the element type should be convertible to the third argument.

Using find_first_of

find_first_of takes two pairs of iterators that denote two ranges of elements. It looks in the first range for a match to any element from the second range and returns an iterator denoting the first element that matches. If no match is found, it returns the end iterator of the first range.

//The below function will go through roster1 only once, but may go through roster2 many timessize_t cnt=0;list<string>::iterator it = roster1.begin();while((it = find_first_of(it, roster1.end(), roster2.begin, roster2.end())!=roster1.end()){  ++cnt;  //increment it to look in the rest of roster1, till we reach roster1.end()  ++it;}

Generic algorithms operate on iterator pairs, so the two iterators must refer to elements in the same container. And we should be able to reach the second iterator from the first one.
For find_first_of, the first pair and the second pair don't have to pointing to same container type. The only requirement is the elements in each container are comparable.

11.2.2 Algorithms that Write Container Elements

When using algorithms that write elements, we must take care to ensure that the sequence into which the algorithm writes is at least at large as the number of elements being written.

Writing to the Elements of the Input Sequence

Algorithms write to their input sequence are inherently safe: they write only as many elements as are in the specified input range.

fill is an algorithm that writes to its input sequence:

//reset each element to 0fill(vec.begin(), vec.end(), 0); // set subsequence of the range to 10fill(vec.begin(), vec.begin()+ vec.size()/2, 10); 

fill takes a pair of iterators that denote a range in which to write copies of its third parameter.

Algorithms Do Not Check Write Operations

The fill_n function takes an iterator, a count, and a value. It assumes that it is safe to write the specified number of elements.

vector<int> vec;   //empty vector//disaster: attempts to write to 10 (nonexistent) elementsfill_n(vec.begin(), 10, 0);

Algorithm that write a specified number of elements or that write to a destination iterator do not check whether the destination is large enough to hold the number of elements being written.
Introducing back_inserter

An insert iterator is an iterator that adds elements to the underlying container. When we assign through an insert iterator, a new element is added to the container.

The back_inserter function is an iterator adaptor. Programs that use back_inserter must include theiterator header.

vector<int>  vec;//ok: back_inserter creates an insert iterator that adds elements to vecfill_n(back_inserter(vec), 10, 0);   //appends 10 elements to vec

The above program is equal to call push_back(0) 10 times.

we can also use vec.insert(vec.end(), 10, 0)

Algorithms that Write to a Destination Iterator

A third kind of algorithm writes an unknown number of elements to a destination iterator. As with fill_n, the destination iterator refers to the first element of a sequence that will hold the output.

copy takes three iterators, the first two is an input range, the third refers to an element in the destination sequence. destination passed to copy should be >= input range.

vector<int> ivec;   //empty vectorcopy(ilist.begin(), ilist.end(), back_inserter(ivec));

More efficient way: Use input range as initializer for new container

vector<int> ivec(ilist.begin(), ilist.end());

Algorithm _copy Versions

replace

//replace any element with value of 0 by 42replace(vec.begin(), vec.end(), 0, 42);

replace_copy

//The original container is unchanged, it will modify the container pointed by the third iteratorreplace_copy(ilist.begin(), ilist.end(), back_inserter(ivec), 0, 42);

11.2.3 Algorithms that Reorder Container Elements

Task: Find unique words and order them,then get words longer than 6.

1. Sort elements

sort takes two iterators that denote the range of elements to sort. It uses the < operator to compare the elements.

//sort words alphabeticallysort(words.begin(), words.end());

2. Using unique

unique takes two iterators that denote a range of elements. It rearranges the elements so that adjacent duplicated entries are eliminated and returns an iterator that denotes the end of the range of the unique values(one pass last unique element).

//get unique wordsvector<string>::iterator end_unique = unique(words.begin(), words,end());

3. Using Container Operations to Remove Elements

We should use container operation: erase, to eliminate the duplicate items.

//this program is safe, even there is no duplicate//In that case, end_unique = words.end()words.erase(end_unique, words.end());

Algorithms never directly change the size of a container. If we want to add or remove elements, we must use a container operation.

Defining Needed Utility Functions

To sort words by length and find those longer than six, we need to use stable_sort and count_if

To use these two algorithms, we need a companion, known as predicate.

Predicate is a function that performs some test and returns a type that can be used in a condition to indicate success or failure. EG:

//comparison function to be used to sort by word lengthbool isShorter(const string &s1, const string &s2){    return s1.size() < s2.size();}//determine whether a given string is of length six or greater:bool GT6(const string &s1){  return s.size()>=6;}

Sorting Algorithms
stable_sort maintains the original order among equal elements.(here equal means equal length, so two equal words can be different words, therefore, we need stable sort)

For sort and stable_sort, one version is use default < operator to compare elements. The other version is take a third parameter, which is a predicate, to comparing elements. So the predicate function should take the same type as the element type.

// sort words by size, but maintain alphabetic order for words of the same sizestable_sort(words.begin(), words.end(), isShorter);

Counting Words of Length Six or More

count_if executes by reading the input range. It pass each element to the predicate function, and add one if the condition is true for that element.

vector<string>::size_type wc =           count_if(words.begin(), words.end(), GT6);

11.3 Revisiting Iterators

There are three special iterators, they are defined in the iterator header:

  • insert iterator: They are bound to a container and can be used to insert elements to the container.
  • iostream iterator: They can be bound to input or output streams and used to iterate through the associated IO stream
  • reverse iterator: They move backward. Each container defines its ownreverse_iterator type, which are retuned by therbegin andrend functions.

11.3.1 Insert Iterators

An inserter is an iterator adaptor that takes a container and yields an iterator that inserts elements into the specified container.

When we assign through an insert iterator, the iterator inserts a new element.

There are three kinds of inserters:

  • back_inserter, it creates an iterator that uses push_back
  • front_inserter, it creates an iterator that uses push_front
  • inserter, it uses insert. In addition to a container, inserter takes a second argument: an iterator indicating the position ahead of which insertion should begin.

front_inserter Requires push_front

We can use front_inserter only if the container has a push_front operation. We cannot use front_inserter on vector.

inserter Yields an iterator that Inserts at a Given PlaceP 406

Elements are always inserted in front of the position denoted by the iterator argument to inserter.

inserter and front_inserter behaves differently:

  • inserter: a1, a2, a3, *iterator
  • front_inserter: a3, a2, a1, *iterator

front_inserter results in the elements appearing in the destination in reverse order.

11.3.2 iostream Iterators left

11.3.3 Reverse Iterators

Reverse iterator traverses from the last element to the first. It inverts the meaning of ++ and --.

rbegin will return the last element, rend will return one before the first element.

Print elements in reverse order:

vector<int>::reverse_iterator r_iter;for(r_iter = vec.rbegin(); r_iter != vec.rend(); ++r_iter)    cout << *r_iter << endl;

We can use reverse iterator to sort in descending order:

//normally, sort ascendingsort(vec.begin(), vec.end());//use rbegin, rend to sort descendingsort(vec.rbegin(), vec.rend());

Similarly, we can use rbegin, rend in other algorithms to find the last element, unique sort from the last element...

Reverse Iterators Require Decrement Operators

We can only define reverse iterator from an iterator that support -- as well as ++. stream iterator does not support reverse iterator.

Relationship between Reverse Iterator and Other Iterators

//find the last element in a comma-separated liststring::reverse_iterator rcomma = find(line.rbegin(), line.rend(), ',');//rbegin and rend will return reverse_iterator, so rcomma must be reverse_iterator

To print the last element:

//wrong waycout << string(line.rbegin(), rcomma) << endl;//correct way: get a forward iterator and read till endcout << string(rcomma.base(), line.end()) << endl;

Each reverser iterator has a member called base, which will transfer the reverser iterator to a normal iterator.P 414

11.3.4 const Iterator

Algorithms require the iterators that denote a range to have exactly the same type.

find_first_of(sample, roster1.end(), roster2.begin(), roster2.end());

If roster1 is a const container, then roster1.end() will return a const_iterator. So sample must also be a const_iterator.

If roster1 is a plain container, then roster1.end() will return a normal iterator. So sample must also be a normal iterator. (In this case, you cannot define sample as const_iterator)

11.3.5 The Five Iterator Categories

The iterator operations required by the algorithms are grouped into five categories.

Iterator CategoriesInput iteratorread, but not write; increment onlyOutput iteratorwrite, but not read; increment onlyForward iteratorread and write; increment onlyBidirectional iteratorread and write; increment and decrementRandom access iteratorread and write; full iterator arithmeticThe map, set and list types provide bidirectional iterators.

Iterators on string, vector, and deque are random-access iterators, as are pointers bound to arrays.

11.4 Structure of Generic Algorithms

The most fundamental property of any algorithm is the kind of iterators it expects.

A second way to classify the algorithms is by what actions they take on the elements.

11.4.1 Algorithm Parameter Patterns

Most of algorithms take one of the following four forms:

alg(beg, end, other parms);alg(beg, end, dest, other parms);alg(beg, end, beg2, other parms);alg(beg, end, beg2, end2, other parms);

beg and end consists a range known as "input range" of the algorithm.

Algorithms with a single destination iterator

A dest parameter is an iterator that denotes a destination used to hold the output.

Algorithm assume that it is safe to write as many elements as needed.

To ensure the output container is large enough, we usually use insert iterators.

Algorithms with a second input sequence

Algorithms that take beg2 but not end2 treat beg2 as the first element in the second input range. These algorithms assume that the range starting at beg2 is at least as large as the one denoted by beg, end.

11.4.2 Algorithm Naming Conventions

Distinguishing Versions that Take a Value or a Predicate

Many algorithms operate by testing elements in their input range. They typically use the standard relational operator(== or <). Most of them also provide a second version that allows the programmer override the use of operator and supply a comparison or test function. EG.

sort(beg, end);         //use < to sort the elementssort(beg, end, comp);   //use function named comp to sort the elements

The find algorithm also support function, but it's not overloaded:

find(beg, end, val);       //find first instance of val in the input rangefind_if(beg, end, pred);   //find first instance for which pred is true

Sort can overload, because the number of parameters changed.finddid not overload, because both algorithm take 3 parameters.

Distinguishing Versions that Copy from Those that Do Not

Algorithms may rearrange elements within the input range. By default, such algorithms write the rearranged elements back into the input range.

These algorithms also provide version that writes to a specified output destination.These algorithms append _coy to their name:

reverse(beg, end);reverse_copy(beg, end, dest);

11.5 Container-Specific Algorithms

There are lots of list specific algorithms that can do the same task more efficiently.P 423