Reputation: 58901
I have a list of obj
objects which each have an eval()
method, returning a double
. I'd like to get the maximum value of all of these. So far, I've always done
double maxval = std::numeric_limits<double>::lowest();
for (const auto &obj: objs) {
maxval = std::max(maxval, obj->eval());
}
I was wondering if there's a more elegant solution where you don't have to set the initial value -inf, something like Python's
max_value = max(obj.eval() for obj in objs)
perhaps. Note than eval()
may be expensive, so I only want to eval()
once per obj
.
Any hints? Bonus points for readability.
Upvotes: 3
Views: 83
Reputation: 38325
Some <ranges>
nice things (C++20 only):
#include <ranges>
const auto maxval = std::ranges::max_element(objs, std::less<>{}, &ObjType::eval);
if (maxval != objs.cend())
doStuffWith(*maxval);
where ObjType
is the type of the sequence elements. The last check could also be on the size of the container as maxval
would certainly be a dereferencable iterator when the sequence isn't empty, e.g.
if (!objs.empty()) ; // ...
Note however that as @NathanOliver has pointed out, this invokes eval()
2N-2 times. Here is a custom template that would call eval()
exactly N times:
#include <optional>
#include <functional>
#include <type_traits>
template <class Range, class Cmp = std::less<>, class Proj = std::identity>
auto maxValue(const Range& rng, Cmp pred = Cmp{}, Proj p = Proj{})
{
using std::begin;
using std::end;
using ValueType = std::remove_cvref_t<std::invoke_result_t<Proj,
decltype(*begin(rng))>>;
auto first = begin(rng);
const auto last = end(rng);
if (first == last)
return std::optional<ValueType>{};
auto result = std::invoke(p, *first);
for (++first; first != last; ++first)
result = std::max(std::invoke(p, *first), result, pred);
return std::optional{result};
}
It doesn't return an iterator, but the resulting value - wrapped into a std::optional
in case the range is empty (then the result is std::nullopt
). Usage for a type Test
with member function Test::eval()
would be like this:
const auto max = maxValue(myContainer, std::less<>{}, &Test::eval);
Second and third argument have sensible defaults, so for primitive types and the like they could be left out.
Upvotes: 4
Reputation: 13599
The C++ equivalent to Python's max
is std::max_element
.
auto itr = std::max_element(
objs.begin(), objs.end(),
[](const auto& lhs, const auto& rhs){ return lhs.eval() < rhs.eval(); }
);
if (itr != objs.end()) {
double maxval = *itr;
}
Upvotes: 2