C++ 优化argmin:寻找最小化函数项的有效方法

C++ 优化argmin:寻找最小化函数项的有效方法,c++,algorithm,search,standard-library,C++,Algorithm,Search,Standard Library,假设我有一个项目集合和一个评分函数: struct Item { /* some data */ }; std::vector<Item> items; double score(Item); struct Item{/*一些数据*/}; 向量项; 双倍得分(项目); 我想从那个收藏中找出得分最低的物品。写这篇文章的简单方法是: const auto argmin = std::min_element(begin(items), end(items), [](Item a, It

假设我有一个项目集合和一个评分函数:

struct Item { /* some data */ };
std::vector<Item> items;
double score(Item);
struct Item{/*一些数据*/};
向量项;
双倍得分(项目);
我想从那个收藏中找出得分最低的物品。写这篇文章的简单方法是:

const auto argmin = std::min_element(begin(items), end(items), [](Item a, Item b) {
    return score(a) < score(b);
});
const auto argmin=std::min_元素(开始(项目)、结束(项目),[](项目a、项目b){
返回分数(a)<分数(b);
});
但是如果
score
是一个很难计算的函数,那么在某些项目上多次调用它的事实可能令人担忧。这是意料之中的,因为编译器无法猜测
分数
是一个错误

我怎样才能找到
argmin
,但是
score
每项只调用一次?记忆是一种可能性,还有别的吗


我的目标是编写一个易于阅读的代码片段,就像在集合中调用
std::min_element
一样显而易见。

这里有一个函数,它可以实现您想要的功能,甚至超越了直观的“每个元素只调用一次调用分数”,因为它意识到没有比负无穷大更小的东西

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;
}
const Item*最小(const std::vector&items)
{
double min_score=items.empty()?NAN:无穷大;
const Item*min_Item=items.empty()?nullptr:&*begin(items);
用于(常量自动和项目:项目){
双项目得分=得分(项目);
如果(项目分数<最低分数){
最小得分=项目得分;
最小项目=&项目;
如果(项目分数==-无穷大){
打破
}
}
}
返回最小项;
}

正如我在上面所评论的,如果向量不是太大,可以使用
std::transform
首先存储所有分数,然后应用
std::min_元素

然而,如果您想利用“惰性评估”,并且仍然想使用C++的STL,那么有一些技巧可以解决这个问题

关键是
std::acculate
可以被视为一种通用的
reduce
fold
操作(如haskell中的
foldl
)。使用C++17的语法sugar for
std::tuple
,我们可以编写如下内容:

    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);
            }
    });
auto[min_ind,u,min_value]=std::累计(items.begin(),items.end(),
std::make_tuple(-1LU,0LU,std::numeric_limits::max()),
[](标准::元组累计、常量项和){
//到目前为止,最小值的索引、当前索引和最后一个最小值
自动[最小值,当前值,上一个最小值]=累计;
双r=分数;
如果(r
根据bu user@liliscent的建议,您可以:

  • 生成预先计算的分数集合
  • 从中找出最低分数
  • 并从最小得分的位置推断最小化项的位置
  • 以下是我对他们建议的解读:

    template<class InputIt, class Scoring>
    auto argmin(InputIt first, InputIt last, Scoring scoring)
    {
        using score_type = typename std::result_of_t<Scoring(typename std::iterator_traits<InputIt>::value_type)>;
        std::vector<score_type> scores(std::distance(first, last));
        std::transform(first, last, begin(scores), scoring);
        const auto scoremin = std::min_element(begin(scores), end(scores));
        return first + std::distance(begin(scores), scoremin);
    }
    
    模板
    自动argmin(先输入,后输入,评分)
    {
    使用score\u type=typename std::result\u of\u t;
    标准:向量分数(标准:距离(第一,最后));
    转换(第一、最后、开始(分数)、评分);
    const auto scoremin=std::min_元素(开始(分数),结束(分数));
    返回第一个+标准::距离(开始(分数),最小分数);
    }
    

    有了。

    您只需要存储我所看到的最新最低分数。然后您只需要调用
    score
    一次。坦率地说,虽然它有点粗糙。如果向量不是太大,您可以使用
    std::transform
    首先存储所有分数,然后应用
    std::min_元素
    @lilisten,它将找到最小分数,而不是最小化
    分数的项(
    )但是,从新向量的
    std::distance
    std::min_元素的
    begin
    将为您提供一个索引,该索引包含原始
    向量中得分最低的项。这是值得回答的。我更新了我的问题,但不够清楚。很抱歉,我更喜欢一个代码片段,它能让代码像
    std::min_元素
    一样容易理解。很抱歉,这不是我想要的。我很重视你的意见,真的。但如果这是最好的,我的问题就一文不值了。关于懒惰,我在这里是比喻:我只是说我喜欢我的代码尽可能简洁。@MohammadKanan对我来说毫无意义*那么^^^我认为在离散的、有限的集合中找到一个最小值在技术上是不明智的。另一方面,它的卓越之处在于代码的简单性和可读性。@YSC,这里的感觉是不同的。您在问题中提出的条件是真正的问题,多次调用score()对某些项目可能令人担忧。。所以这不仅仅是迭代一个离散的有限集。。我是说这个问题不是毫无意义,答案也不是。。。知道来自的函数可以做它只是一个附加组件:)只是为了提供信息,我对这个答案投了赞成票。我认为这是一个完全正确的答案。这不是我所期望的,在约翰回答之后,我更新了我的问题。如果我误导了其他人,我很抱歉。我同时将你的建议转化为答案。我很高兴我们有不同的阅读。给我一点时间来比较它们:)它确实非常接近我用函数式语言编写的Haskell或OCaml。