Iterators and algorithms

The C++ algorithm library defines a series of functions for searching, sorting, technology and manipulating a certain range (range). This range is given in the form of left-closed and right-opened, which is the [first, last ). From the perspective of algorithm requirements, iterators can be divided into five categories, namely input, output, one-way, two-way, and random; from the results of the algorithm's operation on the container , it can be divided into four types: insert, flow, reverse, and move.

1. Iterator method and classification

Iterators are class templates, which define operations to traverse the container. So what are these operations?

1.1 Iterator method

The basic operations of iterators: self-increment, self-decrement, dereference, return members and judgment, etc. If a class can complete the above operations on the container, then it conforms to the concept of iterators.

In vector and string, we can not only access an element through subscripts, but also achieve the same effect through more general iterators. The reason why iterators are more versatile is that not all standard libraries support subscript operations, but all support iterator operations. So what is the difference between pointers and iterators? The acquisition method is different. The pointer is through the addressing method, and the iterator is through the member functions (begin and end, etc.). For different containers, the operations supported by the iterator may be different. Operations that all iterators have ( increment and decrement return members and judgment, etc. ):

operating meaning
* iter Returns the reference (dereference) of the element pointed to by the iterator
iter->mem Dereference and return mem member
++ process Iterator points to the next element
- iter Iterator points to the previous element
iter1==iter2 Determine whether two iterators are equal, true if the dereferences are the same
iter1!=iter2 Determine whether the two iterators are not equal, if the dereferences are different, then it is true

Some iterators may support additional operations:

Additional operations supported by string and vector meaning
iter+n The iterator plus an integer value is still an iterator, moving forward several elements compared to the original position. The result may be an in-container or tail iterator
iter-n The iterator minus an integer value is still an iterator, and it moves several elements backward compared to the original position. The result may be an in-container or tail iterator
iter1+=n Iterator compound addition statement, the result of iter1+n is assigned to iter1 itself
iter1-=n Iterator compound subtraction statement, the result of iter1-n is assigned to iter1 itself
iter1-iter2 The iterator on the right moves several elements forward to get the iterator on the left. It must be the same container, and the result is the distance between the iterators (positive or negative)
>、>=、<、<= The iterator relational operator indicates the context of the iterator. It must be the same container, and the result is a Boolean value.

1.2 Classification

STL algorithms use iterators as input and output. The requirements of different algorithms for iterators are different. Some algorithms (std::find, std::equal, std::count) only require iterators to access elements and increment The ability of iterators and comparison iterators; and some algorithms (sort) require reading and writing, random access capabilities; for reverse, bidirectional reading and writing are required. We know that not all containers have all the functions of an "ideal" iterator. For example, our forward_list does not have the ability to "backward" and random access. If the iterator is added with a const attribute, it does not have the ability to write. Each algorithm will specify the iterator parameters, indicating what capabilities it needs the iterator to help complete my algorithm requirements. Simply put, it can be divided into five iterator categories:

Iterator category Features
Input iterator Read only, no write; single pass scan, only increment (++)
Output iterator Only write, no read; single pass scan, only increment (++)
Forward iterator Read and write; multiple scans, only increment (–)
Bidirectional iterator Read and write; multi-pass scanning, can increase and decrease (++ --)
Random access iterator Read and write; multi-pass scanning, support all operations of iterator

1
The five types of iterator hierarchy (hierarchy) relationships are shown in the figure above. As can be seen from the figure above, forward iterators, bidirectional iterators and random access iterators are also valid input iterators. (In simple terms, if the iterator is forward, bidirectional, and random iterator, then it must have the input iterator requirements and is an input iterator)
Insert picture description here

Second, the iterator of function division

Like an algorithm passing an iterator of the wrong category, many compilers will not give any warnings. According to the function of the iterator, it is divided into five types of iterators:

2.1 Input iterator (input iterator)

  • Judgment (==, !=)
  • Advance (++)
  • Dereference (*)
  • Arrow (->)

The input iterator is only used for sequential access, probably because an operation similar to *it++[1] is used in the algorithm, and the increment operator is used, which means that the iterator becomes invalid after using it. Therefore the input iterator is suitable for single pass scanning. Algorithm find accumulate; istream_iteratoris the input iterator.

2.2 output iterator (output iterator)

  • Advance (++)
  • Dereference (*) as lvalue

The output iterator can only be assigned once and only applies to one scan. For example, the third parameter of copy is the output iterator. ostream_iteratorIs the output iterator.

template< class InputIt, class OutputIt >
OutputIt copy( InputIt first, InputIt last, OutputIt d_first );

std::copy copies elements in one container to another container. The input iterator can well complete the task of access (the first two parameters), and the third parameter needs to be written, so she cannot use the input iterator, but an iterator that allows writing-input iterator . It should be noted that the algorithm for writing containers assumes that there is enough space, and novices are likely to make the mistake of writing elements to empty containers:

vector<int> vec;
fill_n(vec.begin(),10,0);//算法不会检查这样的错误,运行时才会发现

We often use the back_iterater iterator to complete the write operation. The iterator will create space before assignment to prevent the error of no space.

vector<int>  vec;
fill_n(back_inserter(vec),10,0);

2.3 Forward iterator

The forward iterator is readable and writable, and can only scan multiple times in one direction "" "" "" but not "" "" "" "". The most typical examples are replace and replace_if:

template< class ForwardIt, class T >
void replace( ForwardIt first, ForwardIt last,
              const T& old_value, const T& new_value );
template< class ForwardIt, class UnaryPredicate, class T >
void replace_if( ForwardIt first, ForwardIt last,
                 UnaryPredicate p, const T& new_value );

The following is to replace the 6 in the vector with 66, and replace the ones greater than 5 with 0.

std::vector<int> v = {
    
     1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // or std::vector<int>v(10, 2);
std::replace(v.begin(), v.end(), 6, 66);
std::replace_if(v.begin(), v.end(), [](int ele) {
    
    return ele > 5; }, 0);

Note: Although the name seems to be related to forward_list (both forward), this is the minimum requirement, in fact all standard libraries (string vector list forward_list deque) can meet it.

2.4 bidirectional iterator (bidirectional iterator)

Compared with the forward iterator, the bidirectional iterator has one more direction, which is the reverse scan ( --). In addition to forward_list, there are two-way iterators (begin end) that meet the requirements, and two-way scanning.

2.5 Random-access iterator (random-access iterator)

Random access iterator is the ability to access any element in the container in constant time. Except for linked list containers, they all have random access capabilities.

Third, the algorithm's requirements for iterators

The following operations may be performed on the iterator inside the algorithm:

  • Overwrite the value pointed to by the iterator
  • Scan the value in the container forward and backward
  • Dereference, access a data member

Corresponding to iterators:

  • Reading and writing requirements
  • Scan direction requirements
  • Access requirements

We only need to pass in the correct iterator when using the algorithm. What is incorrect! Pass an iterator with poor capabilities, such as the fill_n mentioned earlier. Obviously we need to rewrite the value of the iterator, so we can't just pass an input iterator. Generally speaking, if you use an algorithm instead of passing a const iterator such as cend() and cbegin(), you do not need to consider issues such as reading and writing; if you are not using a container such as a linked list, you always All algorithms can be used. After all, except for the linked list, the rest are containers with random access capabilities. The algorithm has the highest requirements.

Fourth, the algorithm parameter form

Most of the algorithm parameter forms belong to one of the following forms:

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

It can be seen that almost all algorithms accept an input range, and whether there are other parameters depends on the operation to be performed. The more common parameters are dest and beg2 end2, both of which are iterator parameters:

  • dest
    target iterator, the algorithm assumes that it needs to write parameters, no matter how many writes are safe
  • beg2 end2
    It can be the complete iterator range [beg2 end2 ); it can also be specified only [beg2, + ∞ +\ infin+ ) That is, the end position is uncertain, and it is assumed to be at least as large as the full iterator.

Five, algorithm naming convention

In addition to parameter specifications, algorithms also follow a set of naming and overloading specifications. Of course, predicates also have certain specifications, such as defining <== and copying the original sequence or overwriting the original sequence.

5.1 Overload, add predicate

unique(beg,end);
unique(beg,end,comp);//comp定义了什么是unique

Predicates enrich the scope of use of algorithms, and delegate the power of interpretation to programmers.

5.2 _if hypothetical version

Compared to the non-if version, the _if version replaces the value with a predicate. The exact value of the value becomes a fuzzy element that meets certain conditions.

find(beg,end,val);
find(beg,end,pred);//不再是切确的val,而是模糊的pred(如大于某个值)

5.3 _copy copy version

Compared with the non-copy version, _copy does not directly "pollute" the source sequence, but instead, given a new iterator (or range of iterators), it completes the output of the algorithm result without changing the original sequence.

reverse(beg,end);
reverse(beg,end,dest);//不再是直接输出到原序列,而是输出在dest

5.4 _copy_if copy hypothetical version

Such as:

remove_if(v1.begin(),v1.end(),[](int i){
    
    return i%2;});
remove_copy_if(v1.begin(),v1.end(),back_inserter(v2),[](int i){
    
    return i%2;});

5.5 Specific container algorithm

Because some containers have certain characteristics, general algorithms may require unique customization. For example, list and forward_list, they have customized more efficient and suitable algorithm versions according to the characteristics of their containers, but they are given in the form of member functions.

//都返回void
lst.merge(lst2);//有序链表lst2 lst合并,使用<运算进行合并
lst.merge(lst2,comp);//有序链表lst2 lst合并,使用comp运算进行合并
lst.remove(val);//调用erase ==val的值
lst.remove_if(pred);//调用erase  符合pred 的val的值
lst.reverse();//反转元素顺序
lst.sort();//<比较排序
lst.sort(comp);//comp比较排序
lst.unique();//调用erase删除重复元素
lst.unique(pred);//调用erase删除符合pred元素

The linked list type also defines the splice algorithm, which is unique to the linked list:

lst.splice(args) flst.splice(args)
(p,lst2)
(p,lst2,p2)
(p,lst2,b,e)

In addition, it should be noted that compared with the general algorithm, this member function version of the algorithm directly changes the underlying repetitive elements.

Six, function division algorithm

  • Non-modifying sequence operations
  • Modifying sequence operations
  • Partitioning operations
  • Sorting operations
  • Binary search operations (on sorted ranges)
  • Other operations on sorted ranges
  • Set operations (on sorted ranges)
  • Heap operations
  • Minimum/maximum operations
  • Comparison operations
  • Permutation operations
  • Numeric operations(Defined in header )
  • Operations on uninitialized memory(Defined in header )
  • C library(Defined in header )

The detailed function description can be found at: https://en.cppreference.com/w/cpp/algorithm

[1] The precedence of ++ operator is higher than *
[2] https://www.geeksforgeeks.org/input-iterators-in-cpp/
[3] StanleyB.Lippman, JoseeLajoie, BarbaraE.Moo, etc. C++ Primer Chinese version[J]. 2013.

Guess you like

Origin blog.csdn.net/weixin_39258979/article/details/113747371