【问题标题】:Very generic argmax function in C++ wanted需要 C++ 中非常通用的 argmax 函数
【发布时间】:2012-12-21 09:49:38
【问题描述】:

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

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 关键字和range-based iteration,但我无法将它们拼凑在一起。

【问题讨论】:

  • lambda x: -1 * abs(42 - x) 是做什么的?
  • @Nawaz 它创建了一个函数,将参数x 映射到42 和x 之间的绝对差值。乘以 -1,因为我们想要最小化差异。
  • @Nawaz 我确定它相当于[](int x){ return -1 * abs(42 - x); }
  • 请注意,您的 python 代码与您引用的 argmax 的定义不完全匹配,因为它返回单个值,而不是集合。
  • 您的问题是找到最大值,而不是 argmax。 (argmax:最大值的索引)。对于 c++ std::vector argmax,请参阅fundamentalthinking.blogspot.com/2016/04/…

标签: c++ templates generics functional-programming


【解决方案1】:

这是一个两步过程。定义一个函数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);
});

在这里,我们必须捕获在第二个 lambda 函数之外定义的 key。 (我们也可以在里面定义它)。你也可以把它放在一个单独的 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++ 标准算法的设计(&lt;algorithm&gt;)。让您自己的编码风格适应您正在使用的工具总是一个好主意。

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

【讨论】:

  • 不错的答案,但“查找最近”问题只是使用max(collection, key=function) 的示例,而不是要解决的实际问题。
  • @cls 我将添加一个通用的高阶函数来解决 argmax 问题。
  • @cls 给你。 .begin() 和 .end() 是 C++ 算法的典型,所以我采用了 C++ 风格。
  • @cls: 另请注意,此解决方案将迭代器返回到元素而不是元素本身。
  • 你应该使用typedef std::iterator_traits&lt;Iter&gt;::value_type T;正确的是,它专门用于没有内部类型value_type 的指针迭代器。否则这个答案的解决方案是正确的方法。
【解决方案2】:

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

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

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

See the live demo.

【讨论】:

  • 这里的-&gt; 运算符和decltype 是什么?
  • @cls: 称为 trailing-return-type。它是 C++11 特性。 :-)
  • @cls:见Alternative function syntax,它使用trailing-return-type
  • @cls: 而decltype 推导出表达式的type。所以你可以写decltype(0) age = 40;int age = 40; 相同。在这种情况下,它没有多大意义,但有时像我的解决方案一样需要它。
  • 如果您要使用std::begin,请使用 ADL!因此人们可以编写自定义容器。 :) 其次,请注意您的代码在空容器上执行未定义的行为,我怀疑 python 版本会这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-09
  • 2014-09-24
  • 1970-01-01
相关资源
最近更新 更多