Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optimized argmin: an effective way to find an item minimizing a function

Let us say I've got a collection of items and a score function on them:

struct Item { /* some data */ };
std::vector<Item> items;
double score(Item);

I'd like to find the item from that collection whose score is the lowest. An easy way to write this is:

const auto argmin = std::min_element(begin(items), end(items), [](Item a, Item b) {
    return score(a) < score(b);
});

But if score is a heavy-to-compute function, the fact that std::min_element actually calls it multiple times on some items may be worrying. And this is expected because the compiler cannot guess score is a pure function.

How could I find argmin but with score being called only once per item? Memoization is one possibility, anything else?

My objective is to write a code snippet which is easy to read, in a dream world as obvious as calling std::min_element on the collection is.

like image 577
YSC Avatar asked Jan 18 '18 13:01

YSC


People also ask

How do you find the minimum of a function?

Optimizers find the location of a minimum of a nonlinear objective function. You can find a minimum of a function of one variable on a bounded interval using fminbnd, or a minimum of a function of several variables on an unbounded domain using fminsearch. Maximize a function by minimizing its negative.

How do you maximize a non-linear function?

Maximize a function by minimizing its negative. Find a nonnegative solution to a linear least-squares problem using lsqnonneg. The equation solver fzero finds a real root of a nonlinear scalar function. Control the output or other aspects of your optimization by setting options using optimset.

What is the optimize live editor task?

Solve problems and set options using a visual interface with the Optimize Live Editor task. Minimizing and maximizing in one or more dimensions. This example shows how to fit a nonlinear function to data by minimizing the sum of squared errors.


2 Answers

As I commented above, if the vector is not too big, you can use std::transform to store all scores first, then apply std::min_element.

However, if you want to take benefit of "lazy evaluation", and still want to use C++'s STL, there are some tricks to work it out.

The point is std::accumulate can be regarded as a general reduce or fold operation (like foldl in haskell). With C++17's syntax sugar for std::tuple, we can write something like:

    auto [min_ind, _, min_value] = std::accumulate(items.begin(), items.end(),
        std::make_tuple(-1LU, 0LU, std::numeric_limits<double>::max()),
        [] (std::tuple<std::size_t, std::size_t, double> accu, const Item &s) {
            // up to this point, the index of min, the current index, and the last minimal value
            auto [min_ind, cur_ind, prev_min] = accu;
            double r = score(s);
            if ( r < prev_min ) {
                return std::make_tuple(cur_ind, cur_ind + 1, r);
            } else {
                return std::make_tuple(min_ind, cur_ind + 1, prev_min);
            }
    });
like image 52
llllllllll Avatar answered Oct 09 '22 18:10

llllllllll


Here's a function that does what you want--even going beyond the intuitive "call score exactly once per element" by realizing that there's nothing smaller than negative infinity!

const Item* smallest(const std::vector<Item>& items)
{
    double min_score = items.empty() ? NAN : INFINITY;
    const Item* min_item = items.empty() ? nullptr : &*begin(items);
    for (const auto& item : items) {
        double item_score = score(item);
        if (item_score < min_score) {
            min_score = item_score;
            min_item = &item;
            if (item_score == -INFINITY) {
                break;
            }
        }
    }
    return min_item;
}
like image 3
John Zwinck Avatar answered Oct 09 '22 18:10

John Zwinck