Nico Schlömer
Nico Schlömer

Reputation: 58901

max over c++ array with function call

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

Answers (2)

lubgr
lubgr

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

0x5453
0x5453

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

Related Questions