7

How to retrieve all keys (or values) from a std::map and put them into a vector? covers the ways to populate a std::vector from the keys in a map pre-C++11.

Is there a way to do this in C++11 using lambdas, etc, that means we can do it in one line so we can initialize the vector from the map instead of create a vector and populate it in 2 actions?

e.g. vector<int> v(???(m.begin(),m.end()));

Pure C++11 is preferred but boost is acceptable... the aim is to do this in one line without it being overly complicated and "showing off", so it's not confusing to other developers.

For comparison the "obvious" C++11 solution is:

vector<int> v;
v.reserve(m.size()); //not always needed
for(auto &x : map)
  v.push_back(x.first)
Community
  • 1
  • 1
Mr. Boy
  • 60,845
  • 93
  • 320
  • 589

7 Answers7

5

Use boost::adaptor::map_keys in Boost.Range.

#include <iostream>
#include <vector>
#include <map>
#include <boost/range/adaptor/map.hpp>

int main()
{
    const std::map<int, std::string> m = {
        {1, "Alice"},
        {2, "Bob"},
        {3, "Carol"}
    };

    auto key_range = m | boost::adaptors::map_keys;
    const std::vector<int> v(key_range.begin(), key_range.end());

    for (int x : v) {
        std::cout << x << std::endl;
    }
}

Output:

1
2
3
Akira Takahashi
  • 2,912
  • 22
  • 107
4

There you go, C++11 one-liner :)

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <iterator>

int main(int, char**)
{
    std::map<int, std::string> map {
        {1, "one"},
        {2, "two"},
        {3, "three"}
    };
    std::vector<int> keys;


    // Reserve enough space (constant-time)
    keys.reserve(map.size());

    // Retrieve the keys and store them into the vector
    std::transform(map.begin(), map.end(), std::back_inserter(keys),
        [](decltype(map)::value_type const &pair) {return pair.first;}
    );//   ^^^^^^^^^^^^^^^^^^^^^^^^^ Will benefit from C++14's auto lambdas


    // Display the vector
    std::copy(keys.begin(), keys.end(),
        std::ostream_iterator<int>(std::cout, " "));

    return 0;
}

std::transform is freaking powerful.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • 2
    As I mentioned, `back_inserter` is going to resize the vector for you causing multiple memory allocations and data copies. It is also going to increment the element counter on each element. You have none of these performance detrimental operations when using transform iterators. – Maxim Egorushkin Aug 20 '14 at 11:54
  • @MaximYegorushkin fixed. What do you mean by "element counter" ? – Quentin Aug 20 '14 at 11:59
  • The number of elements contained in the `vector` and reported by `vector::size`. `push_back` keeps incrementing it. This is cheap, but not free. – Maxim Egorushkin Aug 20 '14 at 13:00
  • @MaximYegorushkin Can you explain how your answer avoids resizing the vector's storage? – Oktalist Aug 20 '14 at 13:03
  • @Oktalist : he uses `vector`'s constructor, which in turn will directly `reserve()` based on `std::distance(it1, it2)`. Maxim : I don't think a single increment would even be measurable... – Quentin Aug 20 '14 at 13:07
  • 1
    @Oktalist When passing forward iterators to `vector`'s constructor it allocates storage once, sets data members to its final values and then invokes `uninitialized_copy`. – Maxim Egorushkin Aug 20 '14 at 13:08
  • @Quentin Single increment per element inserted. – Maxim Egorushkin Aug 20 '14 at 13:09
  • `map::iterator` is not a `RandomAccessIterator`, so `std::distance` is not free either. – Oktalist Aug 20 '14 at 13:09
  • @MaximYegorushkin that's what I meant. – Quentin Aug 20 '14 at 13:12
  • 2
    You see how simple one-liner with `back_inserter` is fraught with performance issues. That `vector::reserve` makes performance better, however it now clutters your code. You don't get such issues with transform iterators. I am not sure `transform` is even warranted here, you can instead do straight-forward range-for-loop: `for(auto& kv : m) keys.push_back(kv.first)` - easier to type, easier to read. – Maxim Egorushkin Aug 20 '14 at 13:38
  • @Oktalist This is essentially `std::distance()` vs multiple memory allocations and data copies on `vector::push_back()`. Choose your poison. – Maxim Egorushkin Aug 20 '14 at 13:44
  • I'm not sure N incrementations (if they ever take place, which is not even guaranteed) make this "fraught with performance issues". Boost iterators sure are neat and all, but I'm aiming at the vanilla solution here. – Quentin Aug 20 '14 at 13:45
  • _performance issues_ - multiple memory allocations and data copies. And increment is the least of my worries, but still it is not free, however negligible. – Maxim Egorushkin Aug 20 '14 at 13:46
  • I had forgotten the `reserve()` call, yes. Now that I fixed it on your own advice, you can stop ranting about allocation and copies... – Quentin Aug 20 '14 at 13:47
  • You added `reserve()` call and it is not a one-liner any more. You could even appreciate now the beauty of the transform iterator version which essentially does `reserve()` for you... I think people use C++ because they do care about allocations and data copies, otherwise they use easier languages. – Maxim Egorushkin Aug 20 '14 at 13:51
  • 1
    I'd love to see how yours is a one-liner. Just replace the semicolon with a comma and mine is, if that matters so much to you. Perhaps I'll dedicate a statue to Boost iterators another day. – Quentin Aug 20 '14 at 14:02
  • 1
    This seems to fail in terms of requiring multiple lines, and being overly complex. A simple `for(auto x : map){...}` is probably less code and way simpler. – Mr. Boy Aug 20 '14 at 15:54
  • @John I don't think constructing and populating an `std::vector` in one single line as seen in OP's post is possible at all with the standard library only. So, this answer is more of a vanilla C++11 reference implementation. Which, granted, would be nicer without that ugly parameter type for the lambda. – Quentin Aug 20 '14 at 15:59
  • 1
    @John Meh, one line solutions are cool and all, but this one has the benefit of not requiring an external library (although everyone should be using Boost). As for the lambda parameter name, maybe `decltype(map)::value_type const&` is more palatable? – Praetorian Aug 20 '14 at 17:53
  • @Praetorian it has the certain advantage not to repeat template arguments. Thanks ! – Quentin Aug 20 '14 at 18:06
2

Something like the following would do:

#include <vector>
#include <map>
#include <boost/iterator/transform_iterator.hpp>

int main() {
    std::map<std::string, int> m{{"abc", 1}, {"def", 2}};
    auto extractor = [](decltype(m)::value_type const& kv) { return kv.first; };
    std::vector<std::string> v(
          boost::make_transform_iterator(m.begin(), extractor)
        , boost::make_transform_iterator(m.end(), extractor)
        );
}

Note that passing iterators to vector's constructor is the most efficient way to initialize a vector compared to solutions that use push_back or resize a vector filling it with default values first.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
2

No, there's no way to do this in pure C++11 in one line using any of std::vectors constructor overloads, without e.g. creating your own iterator adaptor.

It's trivial to do in two lines though, e.g.:

std::vector<Key> v;
for (const auto& p : m) v.push_back(p.first);

It would also be easy to create your own iterator adaptor for this purpose, for example:

template <typename InputIt>
struct key_it : public InputIt {
    key_it(InputIt it) : InputIt(it) {}
    auto operator*() { return (*this)->first; }
};

// Helper function for automatic deduction of iterator type.
template <typename InputIt>
key_it<InputIt> key_adapt(InputIt it) {
    return {it};
}

Now you can create and populate your std::vector in one line using:

std::vector<Key> v{key_adapt(std::begin(m)), key_adapt(std::end(m))};

Live example

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • I think a simple loop is probably as good as it gets then. Having to write my own functor/class is exactly what I hoped C++11 would allow me to avoid :( – Mr. Boy Aug 20 '14 at 15:56
  • @John I think that's as good as it gets, unless you want to plunge into the Boost mire... – Felix Glas Aug 20 '14 at 16:45
1

A slight refinement of Quentin's solution:

std::vector<int> keys(map.size());
transform(map.begin(), map.end(), keys.begin(),
    [](std::pair<int, std::string> const &p) { return p.first; });

or more readably:

std::vector<int> keys(map.size());
auto get_key = [](std::pair<int, std::string> const &p) { return p.first; };
transform(map.begin(), map.end(), keys.begin(), get_key);

probably better having:

int get_key(std::pair<int, std::string> const &p) { return p.first; }

std::vector<int> get_keys(const std::map<int, std::string> &map)
{
    std::vector<int> keys(map.size());
    transform(map.begin(), map.end(), keys.begin(), get_key);
    return keys;
}

then calling:

std::vector<int> keys = get_keys(map);

if it's going to be used lots.

Community
  • 1
  • 1
Scroog1
  • 3,539
  • 21
  • 26
0

The std::vector class has two relevant constructors:

  1. vector(std::initializer_list<T>) [C++11]
  2. vector(InputIterator first, InputIterator last) [C++98]

The first is the new C++11 constructor that allows you to do things like:

std::vector<int> v{ 1, 2, 3 };

The second allows you to do things like:

std::vector<int> w{ v.rbegin(), v.rend() }; // 3, 2, 1

I don't see a way to use the initializer_list constructor (as you don't have the items available up-front), so your best bet would be to create a key_iterator class that works on a std::map<T, K>::iterator and returns (*i).first instead of (*i). For example:

std::vector<int> keys{ key_iterator(m.begin()), key_iterator(m.end()) };

This also requires you to write the key_iterator class, which you can use the Boost iterator adapters to simplify the task. It may be easier just to use the 2 line version.

reece
  • 7,945
  • 1
  • 26
  • 28
0

Whoopse, I realize this does not actually answer your question (must learn to read properly)!

I would probably go with something like this:

#include <map>
#include <vector>
#include <iostream>

int main()
{
    std::map<int, std::string> m =
    {
        {1, "A"}, {2, "B"}, {3, "C"}
    };

    std::vector<int> v;
    v.reserve(m.size());

    for(auto&& i: m)
        v.emplace_back(i.first);

    for(auto&& i: v)
        std::cout << i << '\n';
}
Galik
  • 47,303
  • 4
  • 80
  • 117