首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >一种适用于各种类型任意嵌套Iterable实现的population_variance函数

一种适用于各种类型任意嵌套Iterable实现的population_variance函数
EN

Code Review用户
提问于 2020-12-06 15:25:10
回答 1查看 90关注 0票数 1

这是C++中各种类型任意嵌套Iterable实现的求和函数一种算法_C++中各种类型任意嵌套迭代实现的均值函数非嵌套测试_向量_算术生成函数_C++中的均值函数测试Generator函数用于算术_C++中的均值函数测试::非嵌套和std::list和的后续问题.除了计算任意嵌套迭代的求和和算术平均值外,我还试图实现一个arithmetic_variance函数,它可以用以下公式计算总体方差值。

是x的总体方差值,

是第一元素的值,

是由arithmetic_mean模板函数计算的总体均值,N是由recursive_size函数计算的总体大小(参见前面的问题递推_C++中各种类型任意嵌套Iterable实现的计数函数)。

用法说明

population_variance模板函数的输入是任意嵌套的。例如,给定一个test_vector:std::vector<double> test_vector{ 1, 2, 3, 4, 5 };population_variance模板函数可以像这个std::cout << "population_variance: " << population_variance(test_vector) << std::endl;那样调用,输出是

代码语言:javascript
复制
population_variance: 2

实验实现

本文给出了population_variance模板函数的实验实现。

代码语言:javascript
复制
//  population_variance function implementation
template<class T1, class T2>
requires (is_iterable<T1> && is_recursive_reduceable<T1> && is_recursive_sizeable<T1> && is_minusable2<std::iter_value_t<T1>, T2>)
// non-recursive version
auto _population_variance(const T1& input, const T2 arithmetic_mean_result)
{
    return std::transform_reduce(std::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [arithmetic_mean_result](auto& element) {
        return std::pow(element - arithmetic_mean_result, 2);
        });
}

template<class T1, class T2>
requires (is_iterable<T1> && is_elements_iterable<T1> && is_recursive_reduceable<T1> && is_recursive_sizeable<T1>)
auto _population_variance(const T1& input, const T2 arithmetic_mean_result)
{
    return std::transform_reduce(std::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [arithmetic_mean_result](auto& element) {
        return _population_variance(element, arithmetic_mean_result);
        });
}

template<class T>
requires (is_recursive_reduceable<T> && is_recursive_sizeable<T>)
auto population_variance(const T& input)
{
    return _population_variance(input, arithmetic_mean(input)) / (recursive_size(input));
}

使用的is_iterableis_elements_iterableis_recursive_reduceableis_recursive_sizeableis_minusable2概念如下。

代码语言:javascript
复制
template<typename T>
concept is_iterable = requires(T x)
{
    *std::begin(x);
    std::end(x);
};

template<typename T>
concept is_elements_iterable = requires(T x)
{
    std::begin(x)->begin();
    std::end(x)->end();
};

template<typename T>
concept is_recursive_reduceable = requires(T x)
{
    recursive_reduce(x, 0.0);
};

template<typename T>
concept is_recursive_sizeable = requires(T x)
{
    recursive_size(x);
};

template<typename T>
concept is_minusable = requires(T x) { x - x; };

template<typename T1, typename T2>
concept is_minusable2 = requires(T1 x1, T2 x2) { x1 - x2; };

使用的arithmetic_mean函数的实现:

代码语言:javascript
复制
template<class T> requires (is_recursive_reduceable<T> && is_recursive_sizeable<T>)
auto arithmetic_mean(const T& input)
{
    return (recursive_reduce(input, 0.0)) / (recursive_size(input));
}

使用的recursive_size函数的实现:

代码语言:javascript
复制
//  recursive_size implementation
template<class T> requires (!is_iterable<T>)
auto recursive_size(const T& input)
{
    return 1;
}

template<class T> requires (!is_elements_iterable<T> && is_iterable<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);
        });
}

测试用例

一个更复杂的例子是:

代码语言:javascript
复制
//  std::vector<std::vector<int>> case
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<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;

使用的n_dim_container_generator模板函数如下所示。多亏了格·斯利平的答复

代码语言:javascript
复制
template<std::size_t dim, template<class...> class Container = std::vector, class T>
constexpr auto n_dim_container_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        return Container(times, n_dim_container_generator<dim - 1, Container, T>(input, times));
    }
}

上帝的链接在这里。

欢迎所有建议。

摘要资料:

EN

回答 1

Code Review用户

回答已采纳

发布于 2020-12-07 15:21:07

考虑使用namespace detail使函数成为私有

在全局命名空间中以下划线开头的名称是由C++语言保留。一般来说,除非你真的想记住围绕它们的所有规则,否则不要用下划线来开头任何名字。典型的解决方案C++库用于使仅标头库中的东西“私有”,就是引入一个namespace detail,在其中放置私有变量、函数和类。所以:

代码语言:javascript
复制
namespace detail {

template<class T1, class T2> requires ...
// non-recursive version
auto population_variance(const T1& input, const T2 arithmetic_mean_result) {...}

template<class T1, class T2> requires ...
auto population_variance(const T1& input, const T2 arithmetic_mean_result) {...}

}

template<class T> requires (is_recursive_reduceable<T> && is_recursive_sizeable<T>)
auto population_variance(const T& input)
{
    return detail::population_variance(...) / ...;
}

使用正确的类型

编写模板的全部目的是使它们能够处理不同类型的模板。这意味着您必须小心硬编码类型,或者使用错误类型的文字常量。例如,在您的代码中,您有:

代码语言:javascript
复制
return std::transform_reduce(std::begin(input), std::end(input), std::size_t{}, std::plus<std::size_t>(), [arithmetic_mean_result](auto& element) {...});

在这里,您强制累加器为std::size_t。但是,如果我想计算0.01.0之间浮点值的平均值和方差怎么办?在您的代码中,所有内容都将被转换为std::size_t,无论发生什么,最终结果都将是0。您应该确保类型与存储在嵌套容器中的值的类型相匹配。

对于带有平均值的参数的detail::population_variance()函数,我只使用平均值的类型:

代码语言:javascript
复制
template<class T1, class T2> requires ...
auto population_variance(const T1& input, const T2 mean)
{
    return std::transform_reduce(std::begin(input), std::end(input), T2{}, std::plus<T2>(), [mean](auto& element) {
        return std::pow(element - mean, 2);
    });
}

因此,现在的问题是在arithmetic_mean()。你有:

代码语言:javascript
复制
return (recursive_reduce(input, 0.0)) / (recursive_size(input));

文字0.0强制结果为double。这可能是好的,即使输入是一个嵌套的整数容器,它也可能是您想要的,但是它也可能是错误的。考虑到容器中的值可能是std::complex,还是其他自定义类型?对于这些类型,差异的概念可能不再有意义了,但这意味着您必须做出决定:

  1. 只接受值类型的容器,其均值和方差可以由double精确地描述。
  2. 推导嵌套容器的值类型,并使用该类型作为初始值。
  3. 允许用户指定用于表示均值和方差的类型。

STL通常允许调用者以某种方式指定结果的类型,通常是为初始值取一个参数。当然,这对arithmetic_meanpopulation_variance没有意义,但是我会为此使用一个模板参数。不幸的是,不能为依赖于后续参数的模板参数设置默认值,因此我认为这是最好的折衷方案:

代码语言:javascript
复制
template<class T = double, class Container> requires ...
auto arithmetic_mean(const Container& input)
{
    return recursive_reduce(input, T{}) / recursive_size(input);
}

template<class T = double, class Container> requires ...
auto population_variance(const Container& input)
{
    return detail::population_variance(input, arithmetic_mean<T>(input)) / recursive_size(input);
}

概念的使用

尝试减少requires语句中使用的概念的数量。此外,如果概念非常具体,并使用派生类型,如:

代码语言:javascript
复制
requires (... && is_minusable2<std::iter_value_t<T1>, T2>)

然后,如果失败,错误消息可能会像不需要这个概念一样被压缩,并且错误将发生在实例化模板的正文中。

您应该首先使用概念来确保选择正确的模板变体,为此您只需要is_elements_iterable来区分递归变体和非递归变体。其他一切只是为了给出一个更好的错误信息。在这种情况下,我只需要编写一个概念来检查给定的值类型是否可以计算出以下变量的方差:

代码语言:javascript
复制
template<typename T>
concept can_calculate_variance_of(const T& value)
{
    (std::pow(value, 2) - value) / std::size_t{1};
}

另外,您不应该在detail::population_variance()中检查这些需求,而应该在公共population_variance()函数中检查这些需求。否则,错误仍然会发生在population_variance()的主体内,而且会比必要时更加神秘。所以理想情况下,你想要这样的东西:

代码语言:javascript
复制
template<class T>
requires (... && can_calculate_variance_of<recursive_iter_value_t<T>>)
auto population_variance(const T& input)
{
    return detail::population_variance(input, arithmetic_mean(input)) / recursive_size(input);
}

更简洁的递归

处理方法

在这种情况下,不需要两个detail::population_variance()函数都调用std::transform_reduce()。这看起来像是不必要的重复。相反,你可以写:

代码语言:javascript
复制
namespace detail
{

template<class T1, class T2>
requires (!is_iterable<T1>)
auto population_variance(const T1& input, const T2 mean)
{
    return std::pow(input - mean, 2);
}

template<class T1, class T2>
auto population_variance(const T1& input, const T2 mean)
{
    return std::transform_reduce(std::begin(input), std::end(input), T2{}, std::plus<T2>(), [mean](auto& element) {
        return population_variance(element, mean);
    });
}

}

但是,也许更好的方法是拥有一个recursive_transform_reduce(),这样您就可以摆脱细节函数,用以下方式替换公共函数:

代码语言:javascript
复制
template<class T, class Container>
requires (...)
auto population_variance(const Container& input)
{
    auto mean = arithmetic_mean<T>(input);
    return recursive_transform_reduce(input, T{}, std::plus<T>(), [mean](auto &element) {
        return std::pow(element - mean, 2);
    }) / recursive_size(input);
}
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/253131

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档