我有一个元素向量,我可以使用非常昂贵的函数从每个元素计算一个数字。我想要映射到这些数字中最低的元素。我知道如何在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惯用方式来做我想要的事情?
*我刚把它输入窗口,我甚至没有尝试编译它。
答案 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()));
}
但我觉得这个解决方案很笨拙:
两个代码示例都在VS2013下编译。两者都返回与问题中的函数相同的值(一旦拼写错误被修复)。
答案 4 :(得分:0)
不是一个真正的答案,而是你问题的解决方案:为什么不在对象中缓存bar的结果? 又名
double bar()
{
if (bar_calculated)
return bar_val;
//...
}
关于避免原始循环的BTW: