简体   繁体   中英

Fast STL way to find input that produces maximum output of function? (contiguous integer inputs)

To improve the readability, I'm trying to get out of the habit of reinventing the wheel.

Problem:

Consider a black-box function, Foo , which has an integer as input and output. We want to find the input that maximises the output. Consider that all the possible inputs belong to a single, contiguous range of integers; and that the range is small enough that we can try each one.

Speed is important, so we don't use containers. Even if the user has already created a container for all the possible inputs , it's still about 100x faster to calculate the next input ( ++input ) than to get it from memory (cache misses).

Example:

Range: [5, 8)

Foo(5); // 19
Foo(6); // 72
Foo(7); // 31

We want to make a function that should return 6 :

InputOfMaxOutputOnRange(5, 8, Foo); // 6

Custom solution:

template <typename T, typename Func>
T InputOfMaxOutputOnRange (T begin_range, T end_range, Func && Scorer)
{    
    // initialise:
    auto max_o = Scorer(begin_range);
    T i_of_max_o = begin_range;

    // now consider the rest of the range:
    ++begin_range;

    for (T i = begin_range; i < end_range; ++i)
    {
        auto output = Scorer(i);

        if (max_o < output)
        {
            max_o = output;
            i_of_max_o = i;
        }
    }
    
    return i_of_max_o;  
}

Question:

I use functions like this so often that I think there should be an STL way to do it. Is there?

In general, the algorithms in the STL work on sequences of values, that are traversed by iterators. They tend to return iterators as well. That's the pattern that it uses.

If you're doing a lot of things like this, where your input "sequence" is a sequential list of numbers, then you're going to want an iterator that "iterates" over a sequence (w/o any storage behind it).

A little bit of searching turned up Boost.CountingIterator , which looks like it could do what you want. I'm confident that there are others like this as well.

Warning - completely untested code

    auto iter = std::max_element(boost::counting_iterator<int>(5),
                                 boost::counting_iterator<int>(8),
          // a comparator that compares two elements
                                );
    return *iter; // should be '6'


As others have observed, std::max_element is defined to get the largest element in aa range.

In your case, the "iterator" is an integer, and the result of dereferencing that iterator is...some result that isn't related to the input in an obvious (but apparently you have some way to getting it efficiently nonetheless).

This being the case, I'd probably define a specialized iterator class, and then use it with std::max_element :

#include <iostream>
#include <iterator>
#include <algorithm>

// your association function goes here. I've just done something
// where the relationship from input to output isn't necessarily
// immediately obvious
int association_function(int input) {
    int a = input * 65537 + 17;
    int b = a * a * a;
    return b % 127;
}

class yourIterator {
    int value;
public:
    // create an iterator from an int value
    explicit yourIterator(int value) : value(value) {}

    // "Deference" the iterator (get the associated value)
    int operator*() const { return association_function(value);  }

    // advance to the next value:
    yourIterator operator++(int) {
        yourIterator temp(value);
        ++value;
        return temp;
     }

     yourIterator &operator++() {
        ++value;
        return *this;
    }

    // compare to another iterator
    bool operator==(yourIterator const& other) const { return value == other.value; }
    bool operator!=(yourIterator const& other) const { return value != other.value; }

    // get the index of the current iterator:
    explicit operator int() const { return value; }
};

int main() {
    // For demo, print out all the values in a particular range:
    std::cout << "values in range: ";
    std::copy(yourIterator(5), yourIterator(10), std::ostream_iterator<int>(std::cout, "\t"));

    // Find the iterator that gives the largest value:
    yourIterator max = std::max_element(yourIterator(5), yourIterator(10));

    // print out the value and the index that gave it:
    std::cout << "\nLargest element: " << *max << "\n";
    std::cout << "index of largest element: " << static_cast<int>(max);
}

When I run this, I get output like this:

values in range: 64     90      105     60      33
Largest element: 105
index of largest element: 7

So, it seems to work correctly.

If you need to use this with a variety of different association functions, you'd probably want to pass that as a template parameter, to keep the iteration part decoupled from the association function.

// pass association as a template parameter
template <class Map>
class mappingIterator {
    int value;
    // create an instance of that type:
    Map map;
public:
    // use the instance to map from iterator to value:
    int operator*() const { return map(value);  }

Then you'd have to re-cast your association function into a form suitable for use as a template parameter, such as:

struct association_function {
    int operator()(int input) const {
        int a = input * 65537 + 17;
        int b = a * a * a;
        return b % 127;
    }
};

Then in main you'd probably want to define a type for the iterator combined with an association function:

    using It = mappingIterator<association_function>;
    It max = std::max_element(It(5), It(10));

C++20 ranges can do this:

template<typename T, typename F>
T argmax_iota(T begin, T end, F &&score) { // can't really think of a good name for this; maybe it doesn't even deserve its own function
    return std::ranges::max(std::views::iota(begin, end), std::less{}, std::ref(score));
    // over the values in the range [begin, end) produced by counting (iota)...
    // find the one that produces the greatest value (max)...
    // when passed to the projection function score...
    // with those values under the ordering induced by std::less
}

Godbolt

iota does not store the whole range anywhere. Iterators into the range hold a single T value that is incremented when the iterator is incremented.

You can use std::max_element defined in <algorithm> .

This will return the iterator to the maximum element in a specified range. You can get the index using std::distance .

Example copied from cppreference .

 std::vector<int> v{ 3, 1, -14, 1, 5, 9 }; 
 std::vector<int>::iterator result;
 
 result = std::max_element(v.begin(), v.end());
 std::cout << "max element at: " << std::distance(v.begin(), result) << '\n';

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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