使用std :: min_element()时保存函数评估

时间:2014-09-16 15:42:15

标签: c++ c++11 stl

假设您有一个2D点矢量,并且预计会找到Euclidean norm最少的点。

这些点以std::vector<point_t> points提供以下typedef std::pair<double, double> point_t。规范可以使用

计算
double norm(point_t p)
{
    return pow(p.first, 2) + pow(p.second, 2);
}

自己编写循环我将执行以下操作:

auto leastPoint = points.cend();
auto leastNorm = std::numeric_limits<double>::max();
for (auto iter = points.cbegin(), end = points.cend(); iter != end; ++iter)
{
    double const currentNorm = norm(*iter);
    if (currentNorm < leastNorm)
    {
        leastNorm = currentNorm;
        leastPoint = iter;
    }
}

但是应该使用STL算法而不是使用自己的循环,所以我很想接受以下内容:

auto const leastPoint = std::min_element(points.cbegin(), points.cend(),
    [](point_t const lhs, point_t const rhs){ return norm(lhs) < norm(rhs); });

但有一点需要注意:如果n = points.size(),那么第一个实现需要n的{​​{1}}评估,但第二个实现需要norm()评估。 (至少在使用this possible implementation时)

所以我的问题是,是否存在任何STL算法,我可以通过该算法找到该点,但仅2n-2评估n

备注:

  • 我知道大喔的复杂性是一样的,但后者仍会导致两倍的评价
  • 创建一个单独的向量并用距离填充它似乎有点过分,只是为了启用STL算法的使用 - 对此有不同的看法?
  • 编辑:我实际上需要一个向量元素的迭代器来擦除那个点。

3 个答案:

答案 0 :(得分:3)

您可以使用std::accumulate(在algorithm标题中):

累积收到:

  • range
  • initial value
  • binary operator(可选,如果未通过,则会调用operator+

initial value range以及operator的每个元素都会被输入initial value,运算符会返回operator类型的结果使用范围的下一个元素进行下一次#include <algorithm> #include <iostream> #include <vector> #include <cmath> typedef std::pair<double, double> point_t; struct norm_t { point_t p; double norm; }; double norm(const point_t& p) { return std::pow(p.first, 2) + std::pow(p.second, 2); } norm_t min_norm(const norm_t& x, const point_t& y) { double ny = norm(y); if (ny < x.norm) return {y, ny}; return x; } int main() { std::vector<point_t> v{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}; norm_t first_norm{v[0], norm(v[0])}; auto min_norm_point = std::accumulate(v.begin(), v.end(), first_norm, min_norm); std::cout << "(" << min_norm_point.p.first << "," << min_norm_point.p.second << "): " << min_norm_point.norm << '\n'; } 调用,依此类推。

示例代码(使用C ++ 11测试GCC 4.9.0):

functor

您可以在struct minimum_norm { minimum_norm() : cached_norm(-1) {} bool operator()(const point_t& first, const point_t& second) { if (cached_norm == -1) cached_norm = norm(second); double norm_first = norm(first); if (norm_first < cached_norm) { cached_norm = norm_first; return true; } return false; } private: double cached_norm; }; int main() { std::vector<point_t> v{{3, 4}, {5, 6}, {1, 2}, {7, 8}, {9, 10}}; auto result = std::min_element(std::begin(v), std::end(v), minimum_norm()); std::cout << "min element at: " << std::distance(std::begin(v), result) << std::endl; } 中缓存最小规范,以避免额外的计算(请注意:我正在使用有关std::min_element实施的信息)。第二个元素是最小找到,第一个是迭代元素。

{{1}}

答案 1 :(得分:1)

这是boost::transform_iterator boost iterator library旨在解决的问题。然而,装饰迭代器方法存在局限性,C ++标准委员会Ranges working group正在考虑向标准添加范围,这可能允许更具功能性的管道方法,例如转换为min_element而无需临时存储。

Eric Niebler在blog上有一些关于范围的有趣帖子。

不幸的是,在通常实现min_element的情况下,transform_iterator并没有完全解决你的问题 - 每个比较都会取消引用迭代器,所以你的函数最终会被调用,而不是必要的。您可以使用boost iterator_adaptor来实现类似于&#39; caching_transform_iterator&#39;这避免了重新计算每个dereference,但它可能是像norm()这样的东西。如果您有更昂贵的计算,这可能是一种有用的技术。

答案 2 :(得分:0)

编辑:没关系,我误解了这个问题。

我认为你错误地认为min_element会进行2N-2次比较

Per the c++ reference of min_element你可以看到算法基本上执行N比较,这是未排序数组的最小值。

以下是www.cplusplus.com失败的(非常)不太可能的案例的副本。

template <class ForwardIterator>
  ForwardIterator min_element ( ForwardIterator first, ForwardIterator last )
{
  if (first==last) return last;
  ForwardIterator smallest = first;

  while (++first!=last)
    if (*first<*smallest)    // or: if (comp(*first,*smallest)) for version (2)
      smallest=first;
  return smallest;
}