BЈовић
BЈовић

Reputation: 64223

How to copy_if from map to vector?

I'd like to copy values that match a predicate (equal ints) from a map<string,int> to a vector<int>.

This is what I tried:

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

The error message from g++ 4.6.1 is :

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

Is there a way to adjust the example to do the above copy?

Upvotes: 13

Views: 5716

Answers (6)

Kleist
Kleist

Reputation: 7985

Problem

The copy fails because you're copying from a map::iterator which iterates over pair<string const,int> to a vector::iterator which iterates over int.

Solution

Replace copy_if with for_each and do a push_back on your vector.

Example

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

Upvotes: 13

Mankarse
Mankarse

Reputation: 40603

With boost::range it is as easy as:

boost::push_back(
    v,
    m | boost::adaptors::map_values 
      | boost::adaptors::filtered([](int val){ return 0 == (val % 2); }));

Upvotes: 14

pnezis
pnezis

Reputation: 12321

I cannot understand why the simple for loop solution is not the preferred approach, for this problem

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

Except that it makes the code more readable it performs better. I wrote a simple benchmark to see how a for loop performs compared to the other proposed solutions:

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

}

The results I got are the following:

Execution Time for_each : 7330000
Execution Time transform : 11090000
Execution Time Simple For Loop : 6530000

Upvotes: 2

larsmoa
larsmoa

Reputation: 12932

std::copy_if won't allow you to transfer from one type to another, only to filter what to copy.

You could use std::transform to get rid of the key and then use 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());

However, a plain for loop would be more efficient and a lot easier to read.

Upvotes: 2

John Dibling
John Dibling

Reputation: 101456

The compiler error is actually quite succinct:

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

And that's exactly what the problem is. The map you're copying from has iterators that dereference to a pair<KEY,VALUE>, and there's no way to implicitly transform a pair<KEY,VALUE> to just a VALUE.

Because of this, you can't use copy or copy_if to copy from a map to a vector; but the Standard Library does provide an algorithm you can use, creatively called transform. transform is very similar to copy in that it takes two source iterators and a destination iterator. The difference is transform also takes a unary function that does the actual transformation. Using a C++11 lambda, you can copy the entire contents of a map to a vector like this:

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

What if you don't want to copy the entire contents of the map, but only some elements meeting certian criteria? Simple, just use transform_if.

What's that, you say? There is no transform_if in the Standard Library? Well yeah, you do have a point there. Frustratingly, there is no transform_if in the Standard Library. However writing one is a simple enough task. Here's the code:

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

As you might expect, using transform_if is like taking copy_if and mashing it together with transform. Here's some psudo-code to demonstrate:

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

Upvotes: 7

Jerry Coffin
Jerry Coffin

Reputation: 490108

Presumably you just want to retrieve the associated values from the map, not the keys.

The SGI version of STL has select1st and select2nd iterators for this kind of task.

Personally, however, I don't think this should really be done with copy -- you're transforming the data, not copying it. As such, I'd advise using std::transform with a functor to return the second item in the pair.

Upvotes: 0

Related Questions