Mr. Boy
Mr. Boy

Reputation: 63816

Is it possible to initialize a vector from the keys in a map?

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)

Upvotes: 7

Views: 5471

Answers (7)

Quentin
Quentin

Reputation: 63154

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.

Upvotes: 4

Felix Glas
Felix Glas

Reputation: 15524

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

Upvotes: 2

Scroog1
Scroog1

Reputation: 3589

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.

Upvotes: 1

Galik
Galik

Reputation: 48635

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

Upvotes: 0

Akira Takahashi
Akira Takahashi

Reputation: 3012

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

Upvotes: 5

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136495

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.

Upvotes: 2

reece
reece

Reputation: 8165

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.

Upvotes: 0

Related Questions