简体   繁体   English

C ++ 11中的高效Argmin

[英]Efficient Argmin in C++11

I have a vector of elements, and I can calculate a single number from each of the elements using a very expensive function. 我有一个元素向量,我可以使用非常昂贵的函数从每个元素计算一个数字。 I would like the element which maps to the lowest of these numbers. 我想要映射到这些数字中最低的元素。 I know how to do this in C++03: * 我知道如何在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;
}

I could also do this using std::min_element , except the naive way of doing things (calling Foo::bar and returning a boolean from < ) calls Foo::bar more times than the code I posted above. 我也可以使用std::min_element做到这std::min_element ,除了天真的做事方式(调用Foo::bar并从<返回一个布尔值)调用Foo::bar比我上面发布的代码多了几次。 I could pre-calculate each of these values and then use std::min_element , except that this code is more complicated than the above code. 我可以预先计算每个值,然后使用std::min_element ,除了这个代码比上面的代码更复杂。

In Going Native, someone (Sean Parent, thanks SChepurin!) said that a good style guide for modern C++ is to avoid "raw loops". 在Going Native中,有人(Sean Parent,谢谢SChepurin!)说现代C ++的一个好的风格指南是避免“原始循环”。 Is there a more C++11 idiomatic way of doing what I want? 是否有更多的C ++ 11惯用方式来做我想要的事情?

* I just typed this into the window, I didn't even try to compile it. *我只是在窗口中键入它,我甚至没有尝试编译它。

This is an interesting one: determining a property based on an expensive operation at a position is not immediately supported. 这是一个有趣的问题:不立即支持根据某个位置的昂贵操作确定属性。 Using a version of std::min_element() which would do the computations in each call to the binary predicate isn't quite the way to go: you don't want to recompute the value of the current known minimum. 使用std::min_element()版本可以在每次调用二进制谓词时进行计算,这是不太可能的:你不想重新计算当前已知最小值的值。 It may be warranted to write a custom loop. 编写自定义循环可能是有保证的。

In general, the STL algorithms assume that getting the value at a position is fairly cheap. 通常,STL算法假设在某个位置获取值相当便宜。 Likewise, the iterator operations (advance, test, dereference) should be fast. 同样,迭代器操作(提前,测试,取消引用)应该很快。 The somewhat costly operation is assumed to be the comparison in this example. 在该示例中,假设稍微昂贵的操作是比较。 When uses match these use caes, STL algorithms are probably indeed a better option, eg, because they can do all kinds of crazy things (loop unrolling, memory operations, etc.). 当使用匹配这些使用caes时,STL算法可能确实是更好的选择,例如,因为它们可以做各种疯狂的事情(循环展开,内存操作等)。 I certainly agree with Herb's statement to use what to do rather than how to do it but for your case I don't think the STL algorithms can do it efficiently. 我当然有香草的声明用做什么 ,而不是如何去做同意,但对你的情况我不认为STL算法可以有效地做到这一点。

If calling Foo::bar is really such a big deal in terms of performance (see juancho's note to profiling), I'd first calculate a vector of the bar values and then search for the min_index there: 如果调用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;
}

Update: another approach would be using std::accumulate with a lambda to do exactly what you handcoded, but that would involve housekeeping and rely on side effecets of the lambda, making the code less comprehensible. 更新:另一种方法是使用带有lambda的std::accumulate来完成您手动编码的操作,但这将涉及内务处理并依赖于lambda的副作用,使代码不易理解。

If you do not want an iterator on the best Foo , you could go with a for_each : 如果你不想在最好的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
}

If I recall correctly, Sean Parent also suggested to write your own algorithm if you do not find one suitable in the STL. 如果我没记错的话,如果你没有在STL中找到合适的算法,Sean Parent也建议编写自己的算法。 You call bar only once per element and you do not have to store its value. 每个元素只调用一次bar,而不必存储其值。 I guess the main idea is the separation concerns between the algorithm and your application code. 我想主要的想法是算法和你的应用程序代码之间的分离问题。

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));
}

Given the return type of the cost function returns a type that supports less than, the algorithm is generic and supports an empty range. 如果cost函数的返回类型返回支持小于的类型,则算法是通用的并且支持空范围。

To be thorough, I investigated first the possibility to use the standard min_element: 为了彻底,我首先调查了使用标准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()));
}

But I find this solution clumsy: 但我觉得这个解决方案很笨拙:

  1. it relies too much on an interpretation of the definition from the standard "Returns the first iterator i in the range [first, last) such that for every iterator j in the range [first, last) the conditions hold: comp(*j, *i) == false", ie the "candidate" minimum is always on the right hand side 它过分依赖于标准定义的解释“返回范围[first,last]中的第一个迭代器i,这样对于[first,last]范围内的每个迭代器j,条件成立:comp(* j, * i)== false“,即”候选人“最小值始终在右侧
  2. because of the previous point, the predicate has to be defined localy: it does not work outside of this context. 因为前一点,谓词必须在localy中定义:它不能在此上下文之外工作。
  3. It does not work in Debug mode with VS2013 because of the checks on the predicate to ensure Compare defines strick weak ordering (eventhough I am not sure it is required here) but it works fine in release. 它在VS2013的调试模式下不起作用,因为检查谓词以确保Compare定义strick弱排序(虽然我不确定这里是否需要)但它在发布时工作正常。

Both code samples compile under VS2013. 两个代码示例都在VS2013下编译。 Both return the same value as the function in the question (once the typo were fixed). 两者都返回与问题中的函数相同的值(一旦拼写错误被修复)。

not really an answer, but solution to your problem: why dont you cache result of bar inside the object? 不是一个真正的答案,而是你问题的解决方案:为什么不在对象中缓存bar的结果? aka 又名

double bar()
{
  if (bar_calculated)
      return bar_val;
   //...
}

BTW regarding avoiding raw loops: BTW关于避免原始循环:
you need to avoid them when you need equivalent code as you would get by using STL algs. 当你需要使用STL algs所获得的等效代码时,你需要避免使用它们。 If you have special req and you cant customize alg to suit your needs use raw loop. 如果你有特殊的需求,你不能自定义alg以满足你的需要使用原始循环。 :) :)
For example here I think you could have stateful comparator that remembers the current arg_min address so it can cache its value... but that is bending the design just to use alg. 例如, 我认为你可以使用有状态的比较器来记住当前的arg_min地址,这样它就可以缓存它的值......但是这样做只是为了使用alg而弯曲设计。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM