C++17 STL Стандартная библиотека шаблонов — страница 44 из 119

в дальнейшем станет некорректным, вследствие чего может мгновенно выйти из области видимости:


  {

    const auto new_end (remove(begin(v), end(v), 2));

    v.erase(new_end, end(v));

  }

  print(v);


5. Теперь удалим все нечетные числа. Для этого реализуем предикат, который сообщит, является ли число нечетным, и передадим его в функцию

std::remove_if
, принимающую подобные предикаты:


  {

    auto odd_number ([](int i) { return i % 2 != 0; });

    const auto new_end ( 

      remove_if(begin(v), end(v), odd_number));

    v.erase(new_end, end(v)); 

  } 

  print(v); 


6. Далее поработаем с алгоритмом

std::replace
. Воспользуемся им, чтобы переписать все значения
4
значениями
123
. Функция
std::replace
существует и в форме
std::replace_if
, которая также принимает функции-предикаты.


  replace(begin(v), end(v), 4, 123);

  print(v);


7. Заполним вектор совершенно новыми значениями и создадим два новых пустых вектора, чтобы провести еще один эксперимент:


  v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

  vector v2; vector v3;


8. Далее снова реализуем предикат для нечетных чисел и еще одну функцию-предикат, которая решает прямо противоположную задачу: сообщает, является ли заданное число четным:


  auto odd_number  ([](int i) { return i % 2 != 0; });

  auto even_number ([](int i) { return i % 2 == 0; });


9. Следующие две строки делают одно и то же: копируют четные значения в векторы

v2
и
v3
. В первой строке это делается с помощью алгоритма
std::remove_copy_if
, копирующего все из исходного контейнера в другой контейнер, который не соответствует ограничению, налагаемому предикатом. В другой строке используется алгоритм
std::copy_if
, который копирует все значения, удовлетворяющие ограничению, налагаемому предикатом:


  remove_copy_if(begin(v), end(v),

                 back_inserter(v2), odd_number);

  copy_if(begin(v), end(v),

  back_inserter(v3), even_number);


10. Вывод на экран двух векторов должен дать одинаковый результат:


  print(v2);

  print(v3);

}


11. Скомпилируем и запустим программу. В первой строке показан вектор после инициализации. Во второй — вектор, из которого мы удалили все значения

2
. В следующей строке представлен результат удаления всех нечетных чисел. Перед четвертой строкой мы заменили все значения
4
на значения
123
.

В последних двух строках показываются векторы

v2
и
v3
:


$ ./removing_items_from_containers

1, 2, 3, 4, 5, 6,

1, 3, 4, 5, 6,

4, 6,

123, 6,

2, 4, 6, 8, 10,

2, 4, 6, 8, 10,



Как это работает

Мы использовали различные алгоритмы, связанные с фильтрацией данных (табл. 5.2).

Для каждого из перечисленных алгоритмов существует версия

*_if
, принимающая вместо значения функцию-предикат, которая затем выводит, какие значения будут удалены или заменены. 

Преобразуем содержимое контейнеров

Если

std::copy
является самым простым алгоритмом STL для работы с диапазонами данных, то
std::transform
— второй по сложности алгоритм STL. Как и copy, он копирует элементы из одного диапазона данных в другой, но дополнительно принимает функцию преобразования. Она может изменить значение выходного типа до того, как это значение окажется в выходном диапазоне. Более того, данная функция может создать значение совершенно другого типа, что может быть полезно, если входной и выходной диапазоны данных имеют разные типы. Этот алгоритм прост в использовании, но в то же время очень полезен, вследствие чего он становится стандартным компонентом, который можно применять во многих повседневных программах.


Как это делается

В этом примере мы воспользуемся алгоритмом

std::transform
, чтобы изменить элементы вектора путем копирования.


1. Как обычно, сначала включим все необходимые заголовочные файлы и объявим об использовании пространства имен

std
, что сэкономит немного времени:


#include 

#include 

#include 

#include 

#include 

#include 


using namespace std;


2. В качестве примера исходной структуры данных возьмем вектор, содержащий простые целые числа:


int main()

{

  vector v {1, 2, 3, 4, 5};


3. Теперь скопируем все элементы в итератор вывода

ostream_iterator
, чтобы вывести их на экран. Функция
transform
принимает объект функции, который принимает элементы, чей тип совпадает с типом элементов контейнера, и преобразует их во время каждой операции копирования. В данном случае мы вычисляем квадрат каждого числа, поэтому наш код выведет значения элементов вектора, возведенные в квадрат, избавив от необходимости сохранять их куда-либо.


  transform(begin(v), end(v),

    ostream_iterator{cout, ", "},

    [] (int i) { return i * i; });

  cout << '\n';


4. Выполним еще одно преобразование. Например, из числа

3
можно сгенерировать точную и читабельную строку
3^2 = 9
. Следующая функция
int_to_string
делает именно это с помощью объекта
std::stringstream
:


  auto int_to_string ([](int i) {

    stringstream ss;

    ss << i << "^2 = " << i * i;

    return ss.str();

  });


5. Функция, которую мы только что реализовали, возвращает строковые значения на основе числовых. Мы также могли бы сказать, что она соотносит строки и числа. С помощью функции transform можно скопировать эти соотношения из вектора чисел в вектор строк:


  vector vs;

  transform(begin(v), end(v), back_inserter(vs),

            int_to_string);


6. Выведем полученный результат на экран, и пример закончится:


  copy(begin(vs), end(vs),

    ostream_iterator{cout, "\n"});

}


7. Скомпилируем и запустим программу:


$ ./transforming_items_in_containers

1, 4, 9, 16, 25,

1^2 = 1

2^2 = 4

3^2 = 9

4^2 = 16

5^2 = 25


Как это работает

Функция

std::transform
работает точно так же, как и
std::copy
, но вместе с копированием элементов в новый диапазон применяет к значению пользовательскую функцию преобразования до того, как итоговый результат будет присвоен итератору по месту назначения.

Выполняем поиск элементов в упорядоченных и неупорядоченных векторах

Иногда требуется определить, лежит ли заданное значение в рамках некоторого диапазона. Если да, то часто нужно изменить его или получить доступ к данным, связанным с ним.

Существует несколько стратегий для поиска элементов. В случае отсортированных элементов можно выполнить бинарный поиск — это быстрее, чем проверка всех элементов один за другим. Если элементы не отсортированы, то придется рассмотреть их по порядку.