优化的argmin:查找最小化函数的项目的有效方法

时间:2018-01-18 13:46:15

标签: c++ algorithm search standard-library

让我们说我有一系列项目和分数函数:

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

我想找到该系列中得分最低的项目。写这个的简单方法是:

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

但如果score是一个重要的计算函数,那么std::min_element实际上在某些项目上多次调用它的事实可能会令人担忧。这是预料之中的,因为编译器无法猜测scorepure function

我怎样才能找到argmin但每个项目只调用score一次?记忆是一种可能性,还有其他什么?

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

3 个答案:

答案 0 :(得分:3)

这是一个能够做你想要的功能 - 甚至超越直观的“每个元素一次呼叫分数”,意识到没有任何小于负无穷大的东西!

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

答案 1 :(得分:3)

如上所述,如果矢量不是太大,您可以先使用std::transform存储所有分数,然后应用std::min_element

但是,如果你想利用&#34;延迟评估&#34;,并且仍然想要使用C ++的STL,那么有一些技巧可以解决它。

std::accumulate可视为一般reducefold操作(如haskell中的foldl)。使用C ++ 17的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);
            }
    });

答案 2 :(得分:1)

根据建议用户@liliscent,可以:

  1. 生成一系列预先计算的分数,
  2. 从中找到最低分,
  3. 并从最小分数的位置推断出最小化项目的位置。
  4. 这是我对他们的建议的解读:

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

    使用live demo