找到产生最大函数输出的输入的快速STL方法? (连续的整数输入)

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

为了提高可读性,我正在努力改掉重新发明轮子的习惯。

问题:

考虑一个黑盒函数,Foo,它有一个整数作为输入和输出。我们想要找到最大化输出的输入。考虑所有可能的输入都属于一个连续的整数范围;而且范围足够小,我们可以尝试每一个。

速度很重要,所以我们不使用容器。 即使用户已经为所有可能的输入创建了一个容器,计算下一个输入(++input) 而不是从内存中获取它(缓存未命中)。

示例:

范围:[5, 8)

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

我们想做一个函数应该 return 6:

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

自定义解决方案:

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;  
}

问题:

我经常使用这样的函数,所以我认为应该有一种 STL 方法来实现它。有吗?

您可以使用 <algorithm> 中定义的 std::max_element

这将return迭代到指定范围内的最大元素。您可以使用 std::distance.

获取索引

示例复制自 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';

通常,STL 中的算法处理由迭代器遍历的值序列。他们也倾向于 return 迭代器。这就是它使用的模式。

如果你正在做很多这样的事情,其中​​你的输入“序列”是一个连续的数字列表,那么你将需要一个迭代器来“迭代”一个序列(w/o 后面的任何存储)。

进行了一些搜索 Boost.CountingIterator,看起来它可以满足您的需求。我相信还有其他类似的人。

警告 - 完全未经测试的代码

    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'


正如其他人所观察到的,std::max_element 被定义为获取范围内的最大元素。

在你的例子中,“迭代器”是一个整数,取消引用该迭代器的结果是......一些与输入明显无关的结果(但显然你有一些方法可以得到尽管如此,它仍然有效。

在这种情况下,我可能会定义一个专门的迭代器 class,然后将其与 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);
}

当我运行这个时,我得到这样的输出:

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

所以,它似乎工作正常。

如果您需要将其与各种不同的关联函数一起使用,您可能希望将其作为模板参数传递,以保持迭代部分与关联函数分离。

// 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);  }

然后你必须将你的关联函数重新转换为适合用作模板参数的形式,例如:

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

然后在 main 中,您可能希望为迭代器定义一个类型并结合关联函数:

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

C++20 范围可以做到这一点:

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 不会将整个范围存储在任何地方。范围内的迭代器包含一个 T 值,该值在迭代器递增时递增。