Brent Bradburn
Brent Bradburn

Reputation: 54859

How to initialize a map from a set

I want to set the keys of a std::map using the elements of a std::set (or a std::vector).

Something like the following...

std::set<int> keys = { 3,4,6 };
std::map<int,string> results(keys); // syntax error

Can this be done without explicitly iterating over the set?

Upvotes: 2

Views: 2098

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69854

Can this be done without explicitly iterating over the set?

No. There is no way to know the keys in the set without iterating over it. You may write functions to make it appear as if there is an implicit transformation, but those functions must ultimately iterate the source collection.

A simple way is as follows:

#include <set>
#include <map>
#include <string>

auto build_map(const std::set<int>& source) -> std::map<int,std::string>
{
  std::map<int,std::string> results;
  for (auto const& i : source) {
    results[i];
  }
  return results;
}

int main()
{
  std::set<int> keys = { 3,4,6 };
  auto results = build_map(keys);
}

Of course we may templatise if that improves readability:

#include <set>
#include <vector>
#include <unordered_set>
#include <map>
#include <string>
#include <utility>

template<class MappedType, class SourceContainer>
auto build_map(SourceContainer&& source)
{
  using source_type = std::decay_t<SourceContainer>;
  using key_type = typename source_type::value_type;

  std::map<key_type , MappedType> results;
  for (auto const& i : source) {
    results[i];
  }
  return results;
}

int main()
{
  std::set<int> keys = { 3,4,6 };
  auto results = build_map<std::string>(keys);

  // also
  results = build_map<std::string>(std::vector<int>{3, 4, 6});
  results = build_map<std::string>(std::unordered_set<int>{3, 4, 6});
}

Upvotes: 1

Barry
Barry

Reputation: 302643

You can't. A map is not a set. They're fundamentally different containers, even if the underlying structure is similar.


That said, anything's possible. The range constructor of std::map is linear time if the elements of the range are already sorted, which set guarantees for us. So all you need to do is apply a transformer to every element to produce a new range. The simplest would be to just use something like boost::make_transform_iterator (or roll your own):

template <class K, class F
    class V = decltype(std::declval<F&>()(std::declval<K const&>()))::second_type>
std::map<K, V> as_map(std::set<K> const& s, F&& f) {
    return std::map<K,V>(
        boost::make_transform_iterator(s.begin(), f),
        boost::make_transform_iterator(s.end(), f));
}

std::map<int,string> results =
    as_map(keys, [](int i){
        return std::make_pair(i, std::string{});
    });

which if you always will want default initialization, can just reduce to:

template <class V, class K>
std::map<K, V> as_map_default(std::set<K> const& s) {
    auto f = [](K const& k) { return std::make_pair(k, V{}); }
    return std::map<K,V>(
        boost::make_transform_iterator(s.begin(), f),
        boost::make_transform_iterator(s.end(), f)); 
}

std::map<int,string> results = as_map_default<string>(keys);

Upvotes: 2

Related Questions