Reputation: 64223
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
Reputation: 7985
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
.
Replace copy_if
with for_each
and do a push_back
on your vector.
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
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
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
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
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
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