将C++中的vector of pairs->first转换为新的vector的方法std::transform。

23

非常抱歉问一个初学者的问题。有向量和成对向量。

typedef std::vector <int> TItems;
typedef std::vector < std::pair <int, int> > TPairs;

有没有一种方法可以通过一步操作将所有成对的第一个元素转换为另一个向量?

int main ()
{
TItems items;
TPairs pairs;

pairs.push_back (std::make_pair(1,3));
pairs.push_back (std::make_pair(5,7));

std::transform( items.begin(), items.end(), items.begin(), comp ( &pairs ) );

return 0;
}

如何设计一个函数对象(functor)?

class comp
{
private:
     TPairs *pairs;

public:
    comp ( TPairs  *pairs_ ) : pairs ( pairs_) { }

    unsigned int operator () ( const unsigned int index ) const
    {
        return  (*pairs)[index].second != pairs->end();  //Bad idea
    }
};
也许有一种更加用户友好的方法,不需要使用lambda表达式和循环。感谢您的帮助。
6个回答

23

首先,您应该将back_inserter用作传递给transform的第三个参数,这样变换后的值就会被推到向量的末尾。

其次,您需要一种接受一对整数并返回第一个整数的函数对象。可以使用以下代码实现:

int firstElement( const std::pair<int, int> &p ) {
    return p.first;
}
现在,为了把所有的部分组合起来:
TPairs pairs;
pairs.push_back( std::make_pair( 1, 3 ) );
pairs.push_back( std::make_pair( 5, 7 ) );

TItems items;
std::transform( pairs.begin(), pairs.end(), std::back_inserter( items ),
                firstElement );

执行完这段代码后,items 数组中包含了 1 和 5。


3
有没有什么聪明的方法可以使用 std::get<0> 代替您自定义的函数? - NHDaly

18

查看Frerich或Kotlinski的回答以获取关于C++03的解决方案。

C++11带有lambda表达式的解决方案:

std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               [](const std::pair<int, int>& p) { return p.first; });

哎呀,我没注意到“禁止使用lambda表达式”的要求,但这个特性为什么不可以使用,它本应该是语言的一部分啊? - stefaanv
我相信这不是大多数人在这里可以使用的C++语言的一部分(要么是由于编译器限制,要么是由于工作场所的某些要求)。 - Frerich Raabe

12
我建议您使用std::get作为函数对象,因为它已经作为库函数提供了!如果我们能写出这样的一行代码,是不是很棒呢?
std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);

...但情况比这更糟。你需要消除哪个get要使用的歧义:

int main() {
  std::vector<int> items;
  std::vector<std::pair<int, int>> pairs;

  pairs.push_back(std::make_pair(1, 3));
  pairs.push_back(std::make_pair(5, 7));

  std::transform(pairs.begin(), pairs.end(), std::back_inserter(items),
                 (const int& (*)(const std::pair<int, int>&))std::get<0>);

  return 0;
}

问题在于,std::get已经被重载,接受1.pair&、2.const pair&和3.pair&&作为参数,以便适用于任何类型的pair输入。不幸的是,这些重载妨碍了std::transform的模板类型推断,因此我们原来的行:
std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);

产量
 error: no matching function for call to ‘transform(std::vector<std::pair<int, int> >::iterator, std::vector<std::pair<int, int> >::iterator, std::back_insert_iterator<std::vector<int> >, <unresolved overloaded function type>)’
   std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);
                                                                                    ^
...

/usr/include/c++/4.8/bits/stl_algo.h:4915:5: note:   template argument deduction/substitution failed:
 note:   couldn't deduce template parameter ‘_UnaryOperation’
   std::transform(pairs.begin(), pairs.end(), std::back_inserter(items), std::get<0>);

当推导 std::transform 的模板时,它不知道你要请求哪个std::get的重载,所以你必须手动指定。将函数指针强制转换为正确的类型告诉编译器:“嘿,请使用其中一个重载,在此重载中 get 接受 const& 并返回 const&!”
但至少我们正在使用标准库组件(耶)?
而且就行代码行数而言,它不比其他选项更差: http://ideone.com/6dfzxz

1
有人能想到任何改进吗?像这样干净地使用 std::get 就太好了。...实际上,我应该使用 reinterperet_cast<const int& (*)(const std::pair<int, int>&)>(std::get<0>),但那似乎更糟糕... - NHDaly
我认为可以用lambda包装get函数来替换“硬”转换,并且可以指定参数。 - mr_T

3
这个怎么样?
items.reserve(pairs.size());
for (size_t it = 0; it < pairs.size(); ++it) {
    items.push_back(pairs[it].first);
}

易于理解和调试。

@kotlinski:谢谢,但这是一个常见的解决方案。如果可能的话,我想找到一个不需要任何循环的一步解决方案。 - justik
1
你要求一个用户友好的东西,那么发布一些C++的丑陋代码就会误导你 :) - Johan Kotlinski
+1:在这种情况下,最简单的方法就是避免循环。为什么要避免循环如果它们可以简化代码呢? - stefaanv
@Frerich:我的“为什么”实际上是指那个限制,但我承认那并不清楚。 - stefaanv
OP 实际上并没有要求无循环的解决方案。 - Johan Kotlinski
显示剩余2条评论

3
如何使用std::bind呢?
std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               std::bind(&TPairs::value_type::first, std::placeholders::_1));

(对于非C++11代码,请使用boost::bind替换std :: bind


3

C++11另一个可能的选择是std::mem_fn,它类似于使用std::bind的解决方案:

std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               std::mem_fn(&std::pair<int,int>::first)               
);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接