C++ std::transform вектор пар-> сначала в новый вектор

Извините за немного начинающий вопрос. Есть вектор и вектор пар

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

Как спроектировать функтор?

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

Может быть, есть более удобный для пользователя метод без лямбда-выражений и циклов. Спасибо за вашу помощь.

5 ответов

Решение

Я действительно хочу, чтобы вы использовали 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&& в качестве параметров, так что он будет работать для любой пары в качестве входных данных. К сожалению, перегрузки мешают выводу типа шаблона для 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::get вы просите при выводе шаблона для std::transform, поэтому вы должны указать это вручную. Приведение указателя функции к нужному типу говорит компилятору: "Эй, пожалуйста, используйте перегрузку, где get занимает const& и возвращает const&!"

Но, по крайней мере, мы используем стандартные библиотечные компоненты (ура)?

И с точки зрения количества строк, это не хуже, чем другие варианты: http://ideone.com/6dfzxz

Прежде всего, вы должны использовать 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.

См. ответ Фририха или Котлинского для C++ 03.

C++ 11 решение с лямбда-выражением:

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

Как насчет этого?

items.reserve(pairs.size());
for (size_t it = 0; it < pairs.size(); ++it) {
    items.push_back(pairs[it].first);
}

Простой для понимания и отладки.

Другая возможность из 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)               
);

Как насчет использования std::bind?

std::transform(pairs.begin(), 
               pairs.end(), 
               std::back_inserter(items), 
               std::bind(&TPairs::value_type::first, std::placeholders::_1));

(Заменить std::bind от boost::bind для не-C++11 кода)

Другие вопросы по тегам