std::transform()

Стандартная библиотека C++ STL содержит множество обобщённых и интересных инструментов для работы с контейнерами, что не удивительно, ведь одна из главных причин появления STL – это желание элегантно абстрагировать алгоритмы и отделить их от данных, т.е. от этих самых контейнеров.

Один из простых и довольно интуитивных (для функциональных программистов) алгоритмов – трансформация последовательной области контейнера. В двух словах состит он в следующем: применяем функцию (оператор) к одному элементу контейнера, пишем куда-нибудь результат (новое значение элемента), затем передвигаем итераторы обоих контейнеров вперёд и повторяем весь процесс снова. Процесс продолжается пока не закончится один из контейнеров, либо пока не будет достигнут некоторый заданный (конечный) итератор.

Алгоритм называется transform() и находится в стандартном заголовке <algorithm>.

template< class InputIt, class OutputIt, class UnaryOperation >
OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first,
                    UnaryOperation unary_op );

Если first1 и last1 суть итераторы начала и конца соответственно, то унарная (с одним параметром) функция unary_op “пройдётся” по всем элементам контейнера. Результат каждой такой операции будет записан назад через итератор d_first, который может принадлежать этому-же самому, либо совершенно другому контейнеру, лишь-бы в нём хватило места (после записи оба итератора инкрементятся).

Аналогичная манипуляция доступна и сразу над двумя контейнерами, составляющими при “проходе” соответственно первый и второй операнд уже для бинарной операции (с двумя параметрами):

template< class InputIt1, class InputIt2, class OutputIt, class BinaryOperation >
OutputIt transform( InputIt1 first1, InputIt1 last1, InputIt2 first2,
                    OutputIt d_first, BinaryOperation binary_op );

Принцип автоинкремента итераторов тот-же; главное - чтобы хватило места/элементов в контейнерах. Если один контейнер короче другого или в контейнере для записи закончилось место, std::transform() просто перестаёт менять последующие елементы и возращается.

Пример использования

Функцию std::tranform() особенно приятно использовать вместе с лямбда-нотацией (анонимные функции [](int arg, ...){ ... ; return x; }) C++.

Вот простой пример:

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

/*
 * Печаталка вектора
 */
template <class T, class Allocator = std::allocator<T>>
void printv(const std::vector<T, Allocator> &v) {
  for (T e : v)
    std::cout << e << " ";

  std::cout << std::endl;
}

int main() {
  std::vector<int> intv1 {10, 20, 30, 40, 50, 60};
  std::vector<int> intv2 {1, 2, 3, 4, 5, 6};
  std::vector<int> intv3 {1, 2, 3, 4, 5};

  printv(intv1);

  /*
   * Унарная операция над всем контейнером; результат пишем обратно в intv1
   */
  std::transform(intv1.begin(), intv1.end(), intv1.begin(),
                 [](int x){ return x * 10; });
  printv(intv1);

  /*
   * Бинарная операция над двумя векторами одинаковой длины; результат в intv1
   */
  std::transform(intv1.begin(), intv1.end(), intv2.begin(), intv1.begin(),
                 [](int a, int b) { return a + b; });
  printv(intv1);

  /*
   * Бинарная операция над векторами разной длины: трансформация останавливается
   *  перед последним элементом; результат пишем обратно в intv1
   */
  std::transform(intv1.begin(), intv1.end(), intv3.begin(), intv1.begin(),
                 [](int a, int b) { return a - b; });
  printv(intv1);


  return 0;
}

Скорее всего вы уже поняли, что напечатает эта программа:

10 20 30 40 50 60 
100 200 300 400 500 600 
101 202 303 404 505 606 
100 200 300 400 500 606