Spec-Zone.ru › C++

std::transform_reduce

Определён в заголовочном файле <numeric>
template< class InputIt1, class InputIt2, class T >
T transform_reduce( InputIt1 first1, InputIt1 last1,
                    InputIt2 first2, T init );
(1) (начиная с C++17)
(constexpr начиная с C++20)
template< class ExecutionPolicy,
          class ForwardIt1, class ForwardIt2, class T >
T transform_reduce( ExecutionPolicy&& policy,
                    ForwardIt1 first1, ForwardIt1 last1,
                    ForwardIt2 first2, T init );
(2) (начиная с C++17)
template< class InputIt1, class InputIt2, class T,
          class BinaryOp1, class BinaryOp2 >
T transform_reduce( InputIt1 first1, InputIt1 last1,
                    InputIt2 first2, T init,
                    BinaryOp1 reduce, BinaryOp2 transform );
(3) (начиная с C++17)
(constexpr начиная с C++20)
template< class ExecutionPolicy,
          class ForwardIt1, class ForwardIt2, class T,
          class BinaryOp1, class BinaryOp2 >
T transform_reduce( ExecutionPolicy&& policy,
                    ForwardIt1 first1, ForwardIt1 last1,
                    ForwardIt2 first2, T init,
                    BinaryOp1 reduce, BinaryOp2 transform );
(4) (начиная с C++17)
template< class InputIt, class T,
          class BinaryOp, class UnaryOp >
T transform_reduce( InputIt first, InputIt last, T init,
                    BinaryOp reduce, UnaryOp transform );
(5) (начиная с C++17)
(constexpr начиная с C++20)
template< class ExecutionPolicy,
          class ForwardIt, class T,
          class BinaryOp, class UnaryOp >
T transform_reduce( ExecutionPolicy&& policy,
                    ForwardIt first, ForwardIt last, T init,
                    BinaryOp reduce, UnaryOp transform );
(6) (начиная с C++17)
1) Эквивалентен
transform_reduce(first1, last1, first2, init,
                 std::plus<>(), std::multiplies<>())
, фактически распараллеленной версии стандартного std::inner_product.
3) Применяет transform к каждой паре элементов диапазонов [first1, last1) и диапазона из std::distance(first1, last1) элементов, начиная с first2, а затем сводит результаты (возможно, переставленные и объединённые неопределённым образом) вместе с начальным значением init по reduce.
Результат недетерминирован, если reduce не является ассоциативным или коммутативным (например, сложение чисел с плавающей точкой).
Если какое-либо из следующих значений не преобразуется в T, программа некорректна:
  • reduce(init, init)
  • reduce(init, transform(*first1, *first2))
  • reduce(transform(*first1, *first2), init)
  • reduce(transform(*first1, *first2), transform(*first1, *first2))
Пусть last2 — это std::distance(first1, last1)-й следующий итератор для first2. Если выполняется хотя бы одно из следующих условий, поведение не определено:
  • T не удовлетворяет требованиям MoveConstructible.
  • transform или reduce изменяет какой-либо элемент [first1, last1) или [first2, last2).
  • transform или reduce делает недействительным какой-либо итератор или поддиапазон [first1, last1] или [first2, last2].
5) Применяет transform к каждому элементу диапазона [first, last), а затем сводит результаты (возможно, переставленные и объединённые неопределённым образом) вместе с начальным значением init по reduce.
Результат недетерминирован, если reduce не является ассоциативным или коммутативным (например, сложение чисел с плавающей точкой).
Если какое-либо из следующих значений не преобразуется в T, программа некорректна:
  • reduce(init, init)
  • reduce(init, transform(*first))
  • reduce(transform(*first), init)
  • reduce(transform(*first), transform(*first))
Если выполняется хотя бы одно из следующих условий, поведение не определено:
  • T не удовлетворяет требованиям MoveConstructible.
  • transform или reduce изменяет какой-либо элемент [first, last).
  • transform или reduce делает недействительным какой-либо итератор или поддиапазон [first, last].
2,4,6) То же, что (1,3,5), но выполняется согласно policy.
Эти перегрузки участвуют в разрешении перегрузки только в том случае, если значение следующего выражения равно true:

std::is_execution_policy_v<std::decay_t<ExecutionPolicy>>

(до C++20)

std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>

(начиная с C++20)

Параметры

first1, last1 - пара итераторов, задающих диапазон элементов, используемых в качестве левого операнда transform
first2 - начало диапазона элементов, используемых в качестве правого операнда transform
first, last - пара итераторов, задающих диапазон элементов, используемых в качестве операнда transform
init - начальное значение обобщённой суммы
policy - используемая политика выполнения
reduce - бинарный FunctionObject, который будет применяться в неопределённом порядке к результатам transform, результатам других reduce и init.
transform - унарный или бинарный FunctionObject, который будет применяться к каждому элементу входного диапазона (диапазонов). Возвращаемый тип должен быть допустим в качестве входных данных для reduce.
Требования к типам
-InputIt1, InputIt2, InputIt должен удовлетворять требованиям LegacyInputIterator.
-ForwardIt1, ForwardIt2, ForwardIt должен удовлетворять требованиям LegacyForwardIterator.

Возвращаемое значение

1,2) Обобщённая сумма init и values по std::plus<>(), где values — это значения, преобразованные с помощью std::multiplies<>(); каждое значение получается преобразованием пары элементов из двух входных диапазонов.
3,4) Обобщённая сумма init и values по reduce, где values — это значения, преобразованные с помощью transform; каждое значение получается преобразованием пары элементов из двух входных диапазонов.
5,6) Обобщённая сумма init и values по reduce, где values — это значения, преобразованные с помощью transform; каждое значение получается преобразованием элемента входного диапазона.

Обобщённая сумма группы элементов относительно бинарной операции binary_op определяется следующим образом:

  • Если группа содержит только один элемент, сумма равна значению этого элемента.
  • В противном случае выполняются следующие действия по порядку:
  1. Из группы выбираются любые два элемента elem1 и elem2.
  2. Вычисляется binary_op(elem1, elem2), и результат помещается обратно в группу.
  3. Шаги 1 и 2 повторяются, пока в группе не останется только один элемент.

Сложность

Пусть \(\scriptsize N\)N — это std::distance(first1, last1) (или std::distance(first, last) для перегрузок (5,6)):

1,2) \(\scriptsize O(N)\)O(N) применений std::plus<>() и std::multiplies<>() соответственно.
3-6) \(\scriptsize O(N)\)O(N) применений reduce и transform соответственно.

Исключения

Перегрузки с параметром шаблона с именем ExecutionPolicy сообщают об ошибках следующим образом:

  • Если выполнение функции, вызванной в рамках алгоритма, приводит к исключению, а ExecutionPolicy является одной из стандартных политик, вызывается std::terminate. Для любого другого ExecutionPolicy поведение определяется реализацией.
  • Если алгоритму не удаётся выделить память, генерируется std::bad_alloc.

Примечания

transform никогда не применяется к init.

Если first == last или first1 == last1, возвращается init без изменений.

Пример

transform_reduce можно использовать для распараллеливания std::inner_product. Для получения преимуществ параллельного выполнения некоторым системам может потребоваться дополнительная поддержка. Например, в GNU/Linux необходимо установить Intel TBB и передать компилятору gcc/clang параметр -ltbb.

#if PARALLEL
#include <execution>
#define PAR std::execution::par,
#else
#define PAR
#endif

#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <locale>
#include <numeric>
#include <vector>

// to parallelize non-associate accumulative operation, you'd better choose
// transform_reduce instead of reduce; e.g., a + b * b != b + a * a
void print_sum_squared(long const num)
{
    std::cout.imbue(std::locale{"en_US.UTF8"});
    std::cout << "num = " << num << '\n';
    
    // create an immutable vector filled with pattern: 1,2,3,4, 1,2,3,4 ...
    const std::vector<long> v{[n = num * 4] {
        std::vector<long> v;
        v.reserve(n);
        std::generate_n(std::back_inserter(v), n,
            [i = 0]() mutable { return 1 + i++ % 4; });
        return v;
    }()};

    auto squared_sum = [](auto sum, auto val) { return sum + val * val; };
    
    auto sum1 = std::accumulate(v.cbegin(), v.cend(), 0L, squared_sum);
    std::cout << "accumulate(): " << sum1 << '\n';
    
    auto sum2 = std::reduce(PAR v.cbegin(), v.cend(), 0L, squared_sum);
    std::cout << "reduce(): " << sum2 << '\n';
    
    auto sum3 = std::transform_reduce(PAR v.cbegin(), v.cend(), 0L, std::plus{},
                                      [](auto val) { return val * val; });
    std::cout << "transform_reduce(): " << sum3 << "\n\n";
}

int main()
{
    print_sum_squared(1);
    print_sum_squared(1'000);
    print_sum_squared(1'000'000);
}

Возможный вывод:

num = 1
accumulate(): 30
reduce(): 30
transform_reduce(): 30

num = 1,000
accumulate(): 30,000
reduce(): -7,025,681,278,312,630,348
transform_reduce(): 30,000

num = 1,000,000
accumulate(): 30,000,000
reduce(): -5,314,886,882,370,003,032
transform_reduce(): 30,000,000

// Compile-options for parallel execution on POSIX:
// g++ -O2 -std=c++17 -Wall -Wextra -pedantic -DPARALLEL ./example.cpp -ltbb -o tr; ./tr

См. также

accumulate
суммирует или сворачивает диапазон элементов
(шаблон функции)
transform
применяет функцию к диапазону элементов и сохраняет результаты в целевом диапазоне
(шаблон функции и функциональный объект алгоритма)
ranges::transform
(C++20)
reduce
(C++17)
аналогичен std::accumulate, за исключением того, что порядок выполнения не фиксирован
(шаблон функции)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/algorithm/transform_reduce

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API