C ++ 11中的高效Argmin

时间:2013-11-04 16:23:48

标签: c++ c++11

我有一个元素向量,我可以使用非常昂贵的函数从每个元素计算一个数字。我想要映射到这些数字中最低的元素。我知道如何在C ++ 03中执行此操作:*

Foo get_lowest(const std::vector<Foo> &foos) {
  double lowest_so_far = std::numeric_limits<double>::max();
  std::vector<Foo>::iterator best;
  for(std::vector<Foo>::iterator i = foos.begin(); i != foos.end(); i++) {
    const double curr_val = i->bar();
     if( curr_val < lowest_so_far ) { 
       best = i;
       lowest_so_far = curr_val
     }
  }

  return *i;
}

我也可以使用std::min_element执行此操作,除了天真的做事方式(调用Foo::bar并从<返回布尔值)调用Foo::bar的次数超过std::min_element我上面发布的代码。我可以预先计算每个值,然后使用{{1}},除了这个代码比上面的代码更复杂。

在Going Native中,有人(Sean Parent,感谢SChepurin!)说现代C ++的一个好的风格指南是避免“原始循环”。是否有更多的C ++ 11惯用方式来做我想要的事情?

*我刚把它输入窗口,我甚至没有尝试编译它。

5 个答案:

答案 0 :(得分:4)

这是一个有趣的方法:不立即支持根据某个位置的昂贵操作确定属性。使用std::min_element()版本可以在每次调用二进制谓词时进行计算是不可能的:你不想重新计算当前已知最小值的值。可能有必要编写自定义循环。

通常,STL算法假设在某个位置获取值相当便宜。同样,迭代器操作(提前,测试,取消引用)应该很快。在该示例中,假设稍微昂贵的操作是比较。当使用匹配这些使用caes时,STL算法可能确实是更好的选择,例如,因为它们可以做各种疯狂的事情(循环展开,存储器操作等)。我当然同意Herb声明使用 做什么而不是如何这样做但是对于你的情况我不认为STL算法可以有效地做到这一点。

答案 1 :(得分:2)

如果在性能方面调用Foo::bar 真的这么大(请参阅juancho的分析说明),我首先会计算bar值的向量然后在那里搜索min_index

Foo const& get_lowest(const std::vector<Foo> &foos) {
  typedef decltype(foos[0].bar()) BarVal;

  std::vector<BarVal> barValues;
  barValues.reserve(foos.size());

  std::transform(begin(foos), end(foos), std::back_inserter(barValues), [](Foo const& f) {
    return f.bar(); 
  });

  auto barPos = std::min_element(begin(barValues), end(barValues));
  auto fooPos = begin(foos) + std::distance(begin(barValues), barPos);
  return *fooPos;
}

更新:另一种方法是使用带有lambda的std::accumulate来完成您手动编码的操作,但这将涉及内务处理并依赖于lambda的副作用,使代码不易理解。

答案 2 :(得分:1)

如果你不想在最好的Foo上使用迭代器,你可以选择for_each

Foo *get_lowest(const std::vector<Foo> &foos) {

    Foo *best = nullptr;
    double lowest_so_far = std::numeric_limits<double>::max();
    std::for_each(begin(foos), end(foos), [&](Foo &i){
        const double curr_val = i.bar();
        if (curr_val < lowest_so_far) {
            lowest_so_far = curr_val;
            best = &i;
        }
    });

    return best; // Return a "Foo *" to handle the empty vector case
}

答案 3 :(得分:1)

如果我没记错的话,如果你没有在STL中找到合适的算法,Sean Parent也建议编写自己的算法。每个元素只调用一次bar,而不必存储其值。我想主要的想法是算法和应用程序代码之间的分离问题。

template<class ForwardIterator, class Cost>
ForwardIterator min_cost_element(ForwardIterator first, ForwardIterator last, Cost cost)
{
    typedef decltype(cost(iterator_traits<ForwardIterator>::value_type())) value_t;

    if(first == last)
        return last;
    value_t lowest = cost(*first);
    ForwardIterator found = first;
    while(++first != last) {
        value_t val = cost(*first);
        if(val < lowest) {
            lowest = val;
            found = first;
        }
    }
    return found;
}

const Foo& get_lowest(const vector<Foo>& foos) {
    assert(!foos.empty());
    return *min_cost_element(foos.begin(), foos.end(), mem_fn(&Foo::bar));
}

如果cost函数的返回类型返回支持小于的类型,则算法是通用的并且支持空范围。

为了彻底,我首先调查了使用标准min_element的可能性:

const Foo& get_lowest_wierd(const vector<Foo>& foos) {
    struct predicate {
        double lowest;
        predicate(const Foo& first) : lowest(first.bar()) {}
        bool operator()(const Foo& x, const Foo&) {
            auto val = x.bar();
            if(val < lowest) {
                lowest = val;
                return true;
            }
            return false;
        }
    };

    assert(!foos.empty());
    return *min_element(foos.cbegin(), foos.cend(), predicate(foos.front()));
}

但我觉得这个解决方案很笨拙:

  1. 它过分依赖于标准“退货”对定义的解释 第一个迭代器i在[first,last]范围内,对于每个 在[first,last]范围内的迭代器j条件成立:comp(* j, * i)== false“,即”候选人“最小值始终在右侧
  2. 由于前一点,谓词必须在localy中定义:它不能在此上下文之外工作。
  3. 它在VS2013的调试模式下不起作用,因为检查谓词以确保Compare定义strick弱排序(虽然我不确定这里是否需要)但是它在发布时工作正常。
  4. 两个代码示例都在VS2013下编译。两者都返回与问题中的函数相同的值(一旦拼写错误被修复)。

答案 4 :(得分:0)

不是一个真正的答案,而是你问题的解决方案:为什么不在对象中缓存bar的结果? 又名

double bar()
{
  if (bar_calculated)
      return bar_val;
   //...
}
关于避免原始循环的BTW:
当你需要使用STL algs所获得的等效代码时,你需要避免使用它们。如果你有特殊的需求,你不能自定义alg以满足你的需要使用原始循环。 :)
例如,我认为你可以使用有状态的比较器来记住当前的arg_min地址,这样它就可以缓存它的值......但是这只是为了使用alg而弯曲设计。