6

我是一个被宠坏的 Python 程序员,他习惯于计算a相对于一些的argmaxcollectionfunction

max(collection, key=function)

例如:

l = [1,43,10,17]
a = max(l, key=lambda x: -1 * abs(42 - x))

a然后包含 43,最接近 42 的数字。

是否可以编写一个 C++ 函数,它采用任何“可迭代”和任何函数并返回 argmax,如上?我想这将涉及模板参数、auto关键字和基于范围的迭代,但我无法将它们拼凑在一起。

4

2 回答 2

8

这是一个两步过程。定义一个key应该映射到元素的函数,即在找到最大值的操作之前应用。将事物包装在 lambda 表达式中,定义用于查找最大值的比较。

auto key = [](int x){
    return -abs(42 - x);
};

std::max_element(l.begin(), l.end(), [key](int a, int b){
    return key(a) < key(b);
});

在这里,我们必须捕获key在第二个 lambda 函数之外定义的内容。(我们也可以在里面定义它)。你也可以把它放在一个单独的 lambda 函数中。当 42 应该从 lambda 外部参数化时,将其捕获为变量:

int x = 42;
std::max_element(l.begin(), l.end(), [x](int a, int b){
    return -abs(x - a) < -abs(x - b);
});

请注意,它std::max_element返回一个迭代器。要访问值/对它的引用,请在其前面加上*

int x = 42;
auto nearest = std::min_element(l.begin(), l.end(), [x](int a, int b){
    return abs(x - a) < abs(x - b);
});
std::cout << "Nearest to " << x << ": " << *nearest << std::endl;

您可以很好地将其包装在一个通用find_nearest函数中:

template<typename Iter>
Iter find_nearest(Iter begin, Iter end,
                  const typename std::iterator_traits<Iter>::value_type & value)
{
    typedef typename std::iterator_traits<Iter>::value_type T;
    return std::min_element(begin, end, [&value](const T& a, const T& b){
        return abs(value - a) < abs(value - b);
    });
}

auto a = find_nearest(l.begin(), l.end(), 42);
std::cout << *a << std::endl;

现场演示 find_nearest:http: //ideone.com/g7dMYI


与您问题中的函数类似的高阶函数argmax可能如下所示:

template<typename Iter, typename Function>
Iter argmax(Iter begin, Iter end, Function f)
{
    typedef typename std::iterator_traits<Iter>::value_type T;
    return std::min_element(begin, end, [&f](const T& a, const T& b){
        return f(a) < f(b);
    });
}

您可以使用以下代码调用它,完全符合您问题中的 lambda 函数:

auto a = argmax(l.begin(), l.end(), [](int x) { return -1 * abs(42 - x); });
std::cout << *a << std::endl;

现场演示 argmax:http: //ideone.com/HxLMap


现在唯一剩下的区别是这个argmax函数使用了一个基于迭代器的接口,它对应于 C++ 标准算法 ( <algorithm>) 的设计。使您自己的编码风格适应您正在使用的工具总是一个好主意。

如果你想要一个直接返回值的基于容器的接口,Nawaz 提供了一个很好的解决方案,它需要 decltype-feature 来正确指定返回类型。我决定以这种方式保留我的版本,以便人们可以看到两种替代界面设计。

于 2013-01-07T16:27:18.630 回答
5

由于@leemes 解决方案太多。一切都是正确的,除了在您的示例中没有尝试模仿Python 版本,这是我模仿的尝试:

方便的通用 argmax 函数,就像 Python 版本一样:

template<typename Container, typename Fn>
auto max(Container const & c, Fn && key) -> decltype(*std::begin(c))
{  
    if ( std::begin(c) == std::end(c) ) 
       throw std::invalid_argument("empty container is not allowed.");

    typedef decltype(*std::begin(c)) V;
    auto cmp = [&](V a, V b){ return key(a) < key(b); };
    return *std::max_element(std::begin(c), std::end(c), cmp);
}

并将其用作:

std::vector<int> l = {1,43,10,17};
auto a = max(l, [](int x) { return -1 * std::abs(42-x); };

int l[] = {1,43,10,17}; //works with array also!
auto a = max(l, [](int x) { return -1 * std::abs(42-x); };

注意:与其他解决方案不同,这max()将返回元素本身,而不是元素的迭代器!

另请注意,此解决方案也适用于用户定义的容器:

namespace test
{
     template<size_t N>
     struct intcollection
     {
         int _data[N];
         int const * begin() const { return _data; }
         int const * end() const { return _data + N; }
     };
}

test::intcollection<4> c{{1,43,10,17}};
auto r = max(c, [](int x) { return -1 * std::abs(42-x); });

查看现场演示

于 2013-01-07T16:48:27.817 回答