License : Creative Commons Attribution 4.0 International (CC BY-NC-SA 4.0)
Copyright :
Frédéric Pennerath,
CentraleSupelec
Last modified : April 19, 2024 10:22
Link to the source : algorithms.md
The standard library has identified different concepts of iterators according to their capacities |
Iterators of containers populate these concepts. |
Every algorithm takes as inputs iterators with various requirements |
algorithm
iterator
)std::find
working on InputIterator
template<typename InputIt, typename T>
InputIt find(InputIt it, InputIt end, const T& elt) {
for(; it != end; ++it) if(*it == elt) break;
return it;
}
std::list<int> L = { 5, 3, 9, 1 };
auto it = std::find(L.begin(), L.end(), 9);
if(it != L.end()) L.erase(it); // L now contains { 5, 3, 1 }
std::copy
working on OutputIterator
(and InputIterator
)template<typename InputIt, typename OutputIt>
OutputIt copy(InputIt it, InputIt end, OutputIt outIt) {
for (; it != end; ++it) *out++ = *it;
return outIt;
}
std::list<int> L = { 5, 3, 9, 1 };
std::vector<int> V{ L.size() }; // std::copy does not create new cells.
// Ensure there is enough room before calling it.
std::copy(L.begin(), L.end(), V.begin()); // L now contains { 5, 3, 9, 1 }
std::unique
working on ForwardIterator
template<typename FwdIt>
FwdIt unique(FwdIt it, FwdIt end) {
FwdIt lastWrite = it;
for(; it != end; ++it)
if(*lastWrite != *it) {
++lastWrite;
*lastWrite = std::move(*it);
}
return ++lastWrite;
}
std::vector<int> V = { 5, 3, 3, 9, 1, 1, 1 };
auto new_end = std::unique(V.begin(), V.end());
V.resize(new_end - V.begin()); // V now contains { 5, 3, 9, 1 }
std::reverse
working on BidirectionalIterator
template<typename BidIt>
void reverse(BidIt begin, BidIt end) {
for(; begin != end; ++begin) {
--end;
if(begin == end)
break;
else
std::swap(*begin, *end);
}
}
std::list<int> L = { 5, 3, 9, 1 };
std::reverse(L.begin(), L.end()); // L now contains { 1, 9, 3, 5 }
std::shuffle
working on RandomIterator
template<typename RandIt, typename RandomGen>
void shuffle(RandIt it, RandIt end, RandomGen gen) {
std::uniform_real_distribution<double> dist(0.0, 1.0);
int index = 0;
for(; it != end; ++it) {
int i = static_cast<int>(dist(gen) * ++index); // Integer between 0 and index
std::swap(*it, *(begin + i));
}
}
#include <random>
std::mt19937 gen(std::random_device{}());
std::vector<int> V = { 5, 3, 9, 1 };
std::shuffle(v.begin(), v.end(), gen); // V is shuffled, e.g. { 3, 1, 9, 5 }
The iterator library defined in header <iterator>
provides different tools related to iterators.
#include <iterator>
std::iterator_traits<int *>::value_type value; // value is of type int
std::iterator_traits<int *>::reference ref = value; // value is of type int&
std::iterator_traits<int *>::iterator_category cat1; // cat1 is of type std::contiguous_iterator_tag
std::iterator_traits<std::list<int>>::iterator_category cat2; // cat2 is of type std::bidirectional_iterator_tag
std::advance
template<typename Iter>
void advance(Iter& it, ptrdiff_t dist) {
using category = typename std::iterator_traits<Iter>::iterator_category;
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, category>)
it += dist;
else
if(dist >= 0)
for(; dist > 0; --dist) ++it;
else
if constexpr (std::is_base_of_v<std::bidirectional_iterator_tag, category>)
for(; dist < 0; ++dist) --it;
else
throw std::domain_error("negative count not supported");
}
The iterator library provides various iterator adaptors. Here are some examples.
An input iterator is an example of input iterator that is not a forward iterator:
#include <iostream>
#include <sstream>
#include <iterator>
void grab_doubles(std::istream_iterator<double>& it) {
std::istream_iterator<double> end;
for(; it != end; ++it) std::cout << ' ' << *it;
std::cout << std::endl;
}
std::istringstream is("1 2 3.14 4 five 6");
std::istream_iterator<double> it1(is);
std::istream_iterator<double> it2(is);
std::string myString("1 2 3.14 4 five 6 ");
std::istringstream is(myString);
std::istream_iterator<double> it1(is);
std::istream_iterator<double> it2(it1);
std::cout << "it1:"; grab_doubles(it1);
std::cout << "it2:"; grab_doubles(it2);
return 0;
}
The console output illustrates a case where it1 == it2
does not imply *(++it1) == *(++it2)
!
it1: 1 3.14 4
it2: 1
The output stream iterator is the dual of input stream iterators.
std::istringstream is("1 2 3.14 4");
std::ostringstream os;
std::istream_iterator<double> in(is);
std::ostream_iterator<double> out(os, " "); // Second argument is the separator in the output stream
std::istream_iterator<double> end;
for(; in != end; ++in) *out++ = 2 * *in;
std::cout << " input: " << is.str() << std::endl;
std::cout << "output: " << os.str() << std::endl;
input: 1 2 3.14 4
output: 2 4 6.28 8
std::vector<double> read_coefs(std::istream& in) {
std::vector<double> result;
std::istream_iterator<double> it(in), end;
std::back_insert_iterator insert(result);
std::copy(it, end, insert); // Here push_back() is called
return result;
}
std::istringstream is("1 2 3.14 4");
std::vector<double> V = read_coefs(is); // V contain the coefficients of the string
Many other iterator adaptors exist like:
std::reverse_iterator
to reverse the direction of a bidirectional iterator (operator++
calls operator--
and conversely)std::move_iterator
is an input iterator that moves the read value to the reader (useful to transfer the content from one container to the other).
The goal of the range library is to enable loops similar to modern functional languages (Python, Scala, etc). |
A |
std::begin()
and std::end()
)#include <algorithm>
std::array<int> numbers {4, 5, 8, 3, 4, 1};
std::ranges::sort(numbers);
// Instead of
std::sort(numbers.begin(), numbers.end());
A |
std::views::iota
, etcoperator|
#include <ranges>
double temperatures[] { 5, -3, 2, -4, -5, -3, 4, 6 };
for(double temp : temperatures // Static C arrays are ranges
| std::views::filter([](auto& t) { return t < 0; }) // Only keep negative temperatures
| std::views::transform([](auto& t) { return t + 273.15; })) // Convert from Celsius to Kelvin
std::cout << ' ' << temp << 'K';