Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

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)
like image 531
Mr. Boy Avatar asked Aug 20 '14 10:08

Mr. Boy


People also ask

Can a vector be a key in a map?

In C++ we can use arrays or vector as a key against to a int value like: map<vector<int> ,int > m; Can I do same in MATLAB by containers.

Can we use vector as key in Hashmap?

For the hashmap, we can use the inserted value as the key and its vector index as the corresponding hashmap value.

How do you initialize an element on a map?

One way to initialize a map is to copy contents from another map one after another by using the copy constructor. Syntax: map<string, string>New_Map(old_map);

Can you initialize a vector?

You can initialize a vector by using an array that has been already defined. You need to pass the elements of the array to the iterator constructor of the vector class. The array of size n is passed to the iterator constructor of the vector class.


5 Answers

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
like image 91
Akira Takahashi Avatar answered Oct 09 '22 02:10

Akira Takahashi


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.

like image 28
Quentin Avatar answered Oct 09 '22 04:10

Quentin


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.

like image 29
Maxim Egorushkin Avatar answered Oct 09 '22 03:10

Maxim Egorushkin


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

like image 2
Felix Glas Avatar answered Oct 09 '22 02:10

Felix Glas


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.

like image 1
Scroog1 Avatar answered Oct 09 '22 04:10

Scroog1