Как скопировать файл с карты на вектор? - PullRequest
9 голосов
/ 15 ноября 2011

Я хотел бы скопировать значения, которые соответствуют предикату (равные целые числа), от map<string,int> до vector<int>.

Это то, что я пробовал:

#include <map>
#include <vector>
#include <algorithm>

int main()
{
    std::vector< int > v;
    std::map< std::string, int > m;

    m[ "1" ] = 1;
    m[ "2" ] = 2;
    m[ "3" ] = 3;
    m[ "4" ] = 4;
    m[ "5" ] = 5;

    std::copy_if( m.begin(), m.end(), v.begin(),
                  [] ( const std::pair< std::string,int > &it )
                  {
                    return ( 0 == ( it.second % 2 ) );
                  }
                  );
}

Сообщение об ошибке от g ++ 4.6.1:

error: cannot convert 'std::pair<const std::basic_string<char>, int>' to 'int' in assignment

Есть ли способ настроить пример, чтобы сделатьвышеуказанная копия?

Ответы [ 6 ]

12 голосов
/ 15 ноября 2011

С boost::range это так же просто, как:

boost::push_back(
    v,
    m | boost::adaptors::map_values 
      | boost::adaptors::filtered([](int val){ return 0 == (val % 2); }));
12 голосов
/ 15 ноября 2011

Проблема

Копирование завершается неудачно, потому что вы копируете с map::iterator, который перебирает pair<string const,int>, на vector::iterator, который перебирает int.

Решение

Замените copy_if на for_each и введите push_back для вашего вектора.

Пример

std::for_each( m.begin(), m.end(),
    [&v] ( std::pair< std::string const,int > const&it ) {
        if ( 0 == ( it.second % 2 ) ) {
            v.push_back(it.second);
        }
    }
);
5 голосов
/ 15 ноября 2011

Ошибка компилятора на самом деле довольно лаконична:

error: cannot convert 'std::pair<const std::basic_string<char>, int>' to 'int' in assignment

И это именно то, в чем проблема.map, с которого вы копируете, имеет итераторы, которые обращаются к pair<KEY,VALUE>, и нет способа неявно преобразовать a pair<KEY,VALUE> в VALUE.

, потому чтоиз этого вы не можете использовать copy или copy_if для копирования из map в vector;но Стандартная библиотека предоставляет алгоритм, который вы можете использовать, творчески названный transform.transform очень похож на copy в том, что он требует двух исходных итераторов и целевого итератора.Разница в transform также принимает унарную функцию, которая выполняет фактическое преобразование.Используя лямбду C ++ 11, вы можете скопировать все содержимое map в vector следующим образом:

transform( m.begin(), m.end(), back_inserter(v), [] (const MyMap::value_type& vt)
{
  return vt.second;
});

Что если вы не хотите копировать все содержимоеmap, но только некоторые элементы, отвечающие определенным критериям?Просто, просто используйте transform_if.

Что это, вы говорите?В стандартной библиотеке нет transform_if?Ну, да, у вас есть точка там.К сожалению, в стандартной библиотеке нет transform_if.Однако написание одного является достаточно простой задачей.Вот код:

template<class InputIterator, class OutputIterator, class UnaryFunction, class Predicate>
OutputIterator transform_if(InputIterator first, 
                            InputIterator last, 
                            OutputIterator result, 
                            UnaryFunction f, 
                            Predicate pred)
{
    for (; first != last; ++first)
    {
        if( pred(*first) )
            *result++ = f(*first);
    }
    return result; 
}

Как и следовало ожидать, использование transform_if похоже на взятие copy_if и объединение его с transform.Вот некоторый псевдокод для демонстрации:

transform_if( m.begin(), m.end(), back_inserter(v),
  [] (const MyMap::value_type& vt) // The UnaryFunction takes a pair<K,V> and returns a V
  {
    return vt.second;
  }, [] (const MyMap::value_type& vt) // The predicate returns true if this item should be copied
  {
     return 0 == (vt.second%2);
  } );
2 голосов
/ 15 ноября 2011

std::copy_if не позволит вам переходить с одного типа на другой, только для фильтрации того, что копировать.

Вы можете использовать std::transform, чтобы избавиться от ключа, а затем использовать std::remove_if:

  std::vector<int> v;
  std::transform(m.begin(), m.end(), std::back_inserter(v),
                  [] ( const std::pair< std::string,int > &it )
                  {
                    return it.second;
                  });
  v.erase(
      std::remove_if(
          v.begin(), v.end(), [](const int value){ return (value % 2) != 0; }),
      v.end());

Однако простой цикл for будет более эффективным и намного проще для чтения.

2 голосов
/ 15 ноября 2011

Я не могу понять, почему простое решение для цикла не является предпочтительным подходом, для этой проблемы

for (std::map< std::string, int >::iterator it = m.begin(); it != m.end(); ++it )
{
   if ((it->second % 2) == 0)
      v.push_back(it->second);
}

За исключением того, что он делает код более читабельным, он работает лучше. Я написал простой тест, чтобы увидеть, как работает цикл for по сравнению с другими предлагаемыми решениями:

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <stdlib.h>
#include <time.h>
#include <sstream>

int main(int argc, char *argv[])
{
    std::map< std::string, int > m;
    std::vector<int> v;

    // Fill the map with random values...
    srand ( time(NULL) );

    for (unsigned i=0; i<10000; ++i)
    {
      int r = rand();
      std::stringstream out;
      out << r;
      std::string s = out.str();

      m[s] = r;
    } 

    /////////// FOR EACH ////////////////////

    clock_t start1 = clock();
    for (unsigned k=0; k<10000; k++)
    {
      v.clear();
      std::for_each( m.begin(), m.end(),
      [&v] ( const std::pair< std::string,int > &it ) {
      if ( 0 == ( it.second % 2 ) ) {
          v.push_back(it.second);
      }
      }
      );
    }
    clock_t end1=clock();
    std::cout << "Execution Time for_each : " << (end1-start1) << std::endl;

    /////////// TRANSFORM ////////////////////

    clock_t start2 = clock();
    for (unsigned k=0; k<10000; k++)
    {
      v.clear();
      std::transform(m.begin(), m.end(), std::back_inserter(v),
            [] ( const std::pair< std::string,int > &it )
            {
              return it.second;
            });
      v.erase(
    std::remove_if(
        v.begin(), v.end(), [](const int value){ return (value % 2) != 0; }),
    v.end());
    }
    clock_t end2 = clock();
    std::cout << "Execution Time transform : " << (end2-start2) << std::endl;


     /////////// SIMPLE FOR LOOP ////////////////////
    clock_t start3 = clock();
    for (unsigned k=0; k<10000; k++)
    {
      v.clear();
      for (std::map< std::string, int >::iterator it = m.begin(); it != m.end(); ++it )
      {
    if ((it->second % 2) == 0)
      v.push_back(it->second);
      }
    }
    clock_t end3=clock();
    std::cout << "Execution Time Simple For Loop : " << (end3-start3) << std::endl;

}

Я получил следующие результаты:

Execution Time for_each : 7330000
Execution Time transform : 11090000
Execution Time Simple For Loop : 6530000
0 голосов
/ 15 ноября 2011

Предположительно, вы просто хотите получить связанные значения из map, а не ключей.

Версия STL SGI имеет итераторы select1st и select2nd для задач такого типа.

Лично, однако, я не думаю, что это действительно должно быть сделано с копией - вы трансформируете данные, а не копируете их.Поэтому я бы посоветовал использовать std::transform с функтором для возврата второго элемента в паре.

...