This is a follow-up question for A population_variance Function For Various Type Arbitrary Nested Iterable Implementation in C++. Thanks to G. Sliepen's answer, I am trying to implement the mentioned recursive_transform_reduce
function here.
The usage description
Similar to std::transform_reduce
, the purpose of recursive_transform_reduce
function is to apply a function (unary operation) to each element in the given range then reduce the results with a binary operation. There are four parameters in recursive_transform_reduce
function (the version without execution policy). The first one is a input range, the second one is the initial value of reduction result, the third one is a function (unary operation) which is to apply to each element and the final one is a binary operation for reduction operation.
The experimental implementation
The experimental implementation of recursive_transform_reduce
template function is as below.
// recursive_transform_reduce implementation
template<class Container, class ValueType, class UnaryOp, class BinaryOp = std::plus<ValueType>>
requires (std::ranges::range<Container>) && (!is_elements_iterable<Container>)
constexpr auto recursive_transform_reduce(const Container& input, ValueType init, const UnaryOp& unary_op, const BinaryOp& binop = std::plus<ValueType>())
{
for (const auto& element : input) {
auto result = unary_op(element);
init = binop(init, result);
}
return init;
}
template<class Container, class ValueType, class UnaryOp, class BinaryOp = std::plus<ValueType>>
requires (std::ranges::range<Container>) && (is_elements_iterable<Container>)
constexpr auto recursive_transform_reduce(const Container& input, ValueType init, const UnaryOp& unary_op, const BinaryOp& binop = std::plus<ValueType>())
{
return std::transform_reduce(std::begin(input), std::end(input), init, binop, [&](auto& element) {
return recursive_transform_reduce(element, init, unary_op, binop);
});
}
// With execution policy
template<class ExPo, class Container, class ValueType, class UnaryOp, class BinaryOp = std::plus<ValueType>>
requires ((std::is_execution_policy_v<std::remove_cvref_t<ExPo>>) && (std::ranges::range<Container>) && (!is_elements_iterable<Container>))
constexpr auto recursive_transform_reduce(ExPo execution_policy, const Container& input, ValueType init, const UnaryOp& unary_op, const BinaryOp& binop = std::plus<ValueType>())
{
for (const auto& element : input) {
auto result = unary_op(element);
init = binop(init, result);
}
return init;
}
template<class ExPo, class Container, class ValueType, class UnaryOp, class BinaryOp = std::plus<ValueType>>
requires ((std::is_execution_policy_v<std::remove_cvref_t<ExPo>>) && (std::ranges::range<Container>) && (is_elements_iterable<Container>))
constexpr auto recursive_transform_reduce(ExPo execution_policy, const Container& input, ValueType init, const UnaryOp& unary_op, const BinaryOp& binop = std::plus<ValueType>())
{
return std::transform_reduce(execution_policy, std::begin(input), std::end(input), init, binop, [&](auto& element) {
return recursive_transform_reduce(execution_policy, element, init, unary_op, binop);
});
}
Test cases
With recursive_transform_reduce
function here, population_variance
function in the previous question can be improved as below.
template<typename T>
concept can_calculate_variance_of = requires(const T & value)
{
(std::pow(value, 2) - value) / std::size_t{ 1 };
};
template<typename T>
struct recursive_iter_value_t_detail
{
using type = T;
};
template <std::ranges::range T>
struct recursive_iter_value_t_detail<T>
: recursive_iter_value_t_detail<std::iter_value_t<T>>
{ };
template<typename T>
using recursive_iter_value_t = typename recursive_iter_value_t_detail<T>::type;
// population_variance function implementation (with recursive_transform_reduce template function)
template<class T = double, class Container>
requires (is_recursive_sizeable<Container>&& can_calculate_variance_of<recursive_iter_value_t<Container>>)
auto population_variance(const Container& input)
{
auto mean = arithmetic_mean<T>(input);
return recursive_transform_reduce(std::execution::par,
input, T{}, [mean](auto& element) {
return std::pow(element - mean, 2);
}, std::plus<T>()) / recursive_size(input);
}
Note: recursive_iter_value_t
is referred from How to solve requires clause is incompatible.
The improved version of arithmetic_mean
function implementation:
template<typename T>
concept is_recursive_reduceable = requires(T x)
{
recursive_reduce(x, T{});
};
template<typename T>
concept is_recursive_sizeable = requires(T x)
{
recursive_size(x);
};
template<class T = double, class Container> requires (is_recursive_sizeable<Container>)
auto arithmetic_mean(const Container& input)
{
return (recursive_reduce(input, T{})) / (recursive_size(input));
}
The implementation of recursive_reduce
function:
template<class T, class ValueType, class Function = std::plus<ValueType>>
auto recursive_reduce(const T& input, ValueType init, const Function& f)
{
return f(init, input);
}
template<class Container, class ValueType, class Function = std::plus<ValueType>>
requires is_iterable<Container>
auto recursive_reduce(const Container& input, ValueType init, const Function& f = std::plus<ValueType>())
{
for (const auto& element : input) {
auto result = recursive_reduce(element, ValueType{}, f);
init = f(init, result);
}
return init;
}
The recursive_size
function implementation:
// recursive_size implementation
template<class T> requires (!std::ranges::range<T>)
auto recursive_size(const T& input)
{
return 1;
}
template<class T> requires (!is_elements_iterable<T> && std::ranges::range<T>)
auto recursive_size(const T& input)
{
return input.size();
}
template<class T> requires (is_elements_iterable<T>)
auto recursive_size(const T& input)
{
return std::transform_reduce(std::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [](auto& element) {
return recursive_size(element);
});
}
The test of population_variance
function is like:
std::vector<double> test_vector{ 1, 2, 3, 4, 5 };
std::cout << "recursive_size of test_vector: " << recursive_size(test_vector) << std::endl;
std::cout << "population_variance of test_vector: " << population_variance(test_vector) << std::endl;
// std::vector<std::vector<double>> case
std::vector<decltype(test_vector)> test_vector2;
test_vector2.push_back(test_vector);
test_vector2.push_back(test_vector);
test_vector2.push_back(test_vector);
std::cout << "recursive_size of test_vector2: " << recursive_size(test_vector2) << std::endl;
std::cout << "population_variance of test_vector2: " << population_variance(test_vector2) << std::endl;
auto test_vector3 = n_dim_container_generator<10, std::vector, decltype(test_vector)>(test_vector, 3);
std::cout << "recursive_size of test_vector3: " << recursive_size(test_vector3) << std::endl;
std::cout << "population_variance of test_vector3: " << population_variance(test_vector3) << std::endl;
Then, the execution output:
recursive_size of test_vector: 5
population_variance of test_vector: 2
recursive_size of test_vector2: 15
population_variance of test_vector2: 2
recursive_size of test_vector3: 295245
population_variance of test_vector3: 2
Another test of population_variance
function:
std::vector<double> test_vector{ 1, 2, 3 };
std::cout << "recursive_size of test_vector: " << recursive_size(test_vector) << std::endl;
std::cout << "population_variance of test_vector: " << population_variance(test_vector) << std::endl;
// std::vector<std::vector<double>> case
std::vector<decltype(test_vector)> test_vector2;
test_vector2.push_back(test_vector);
test_vector2.push_back(test_vector);
test_vector2.push_back(test_vector);
std::cout << "recursive_size of test_vector2: " << recursive_size(test_vector2) << std::endl;
std::cout << "population_variance of test_vector2: " << population_variance(test_vector2) << std::endl;
auto test_vector3 = n_dim_container_generator<10, std::vector, decltype(test_vector)>(test_vector, 3);
std::cout << "recursive_size of test_vector3: " << recursive_size(test_vector3) << std::endl;
std::cout << "population_variance of test_vector3: " << population_variance(test_vector3) << std::endl;
The execution output of the above test code:
recursive_size of test_vector: 3
population_variance of test_vector: 0.666667
recursive_size of test_vector2: 9
population_variance of test_vector2: 0.666667
recursive_size of test_vector3: 177147
population_variance of test_vector3: 0.666667
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
A population_variance Function For Various Type Arbitrary Nested Iterable Implementation in C++
What changes has been made in the code since last question?
I am trying to implement a recursive_transform_reduce function here and use it in
population_variance
function.Why a new review is being asked for?
Please check the implementation of
recursive_transform_reduce
function and if there is any possible improvement, please let me know.