0

Is there a way to construct a map with two ranges in C++? That is, instead of calling a default constructor and consequently inserting elements to a map:

for (size_t i = 0; i < Vector_1.size() && i < Vector_2.size(); i++) {
    mymap[Vector_1[i]] = Vector_2[i];
}

I'd like to build a map in place by calling some sort of a constructor which would take two ranges as arguments, something like this:

std::map<t1, t2> mymap(Vector_1.begin(), Vector_1.end(), Vector_2.begin(), Vector_2.end());

Or:

mymap.insert(Vector_1.begin(), Vector_1.end(), Vector_2.begin(), Vector_2.end());

I haven't found any, but maybe there is still a way to do it. Is there a shortcut to initialize a map from two ranges, or at least insert two ranges into a map?

Kaiyakha
  • 1,463
  • 1
  • 6
  • 19
  • 2
    all constructors are listed here: https://en.cppreference.com/w/cpp/container/map/map. You'd need to zip the two ranges to get one start and one end iterator – 463035818_is_not_an_ai Nov 14 '22 at 12:16
  • 1
    https://ericniebler.github.io/range-v3/structranges_1_1views_1_1zip__fn.html and in the future https://en.cppreference.com/w/cpp/ranges/zip_view – bolov Nov 14 '22 at 13:00
  • What are you hoping to accomplish from this shortcut? If you were using `std::unordered_map`, then `reserve` would be an optimization. – MarkB Nov 14 '22 at 16:39

2 Answers2

5

You could use Eric Niebler's range-v3 library, zip the two vectors in a view, and write that view out to a map.

[Demo]

#include <fmt/ranges.h>
#include <map>
#include <range/v3/all.hpp>
#include <vector>

int main() {
    std::vector<char> cs{'a', 'b', 'c'};
    std::vector<int> is{1, 2, 3};
    auto m{ ranges::views::zip(cs, is) | ranges::to<std::map<char, int>>() };
    fmt::print("{}", m);
}

// Outputs: {'a': 1, 'b': 2, 'c': 3}

As Armin Montigny indicated, the std::ranges::views::zip won't be available until C++23; and the same happens with std::ranges::to. Here you can see some compilers already implement std::ranges::views::zip but not yet std::ranges::to.

rturrado
  • 7,699
  • 6
  • 42
  • 62
1

Is there a shortcut to initialize a map from two ranges, or at least insert two ranges into a map?

It is straightforward to create your own "shortcut", which typically takes the form of a free function.

#include <cassert>
#include <vector>
#include <list>
#include <map>
#include <ranges>
#include <iostream>

template <std::ranges::range K, std::ranges::range V>
std::map<std::ranges::range_value_t<K>, std::ranges::range_value_t<V>> createMapFromRanges(K&& krng, V&& vrng)
{
    std::map<std::ranges::range_value_t<K>, std::ranges::range_value_t<V>> map;
    assert(std::size(krng) == std::size(vrng));
    auto [k, kend] = std::tuple{krng.begin(), krng.end()};
    auto [v, vend] = std::tuple{vrng.begin(), vrng.end()};
    while (k != kend && v != vend)
        map.emplace(*k++,*v++);
    return map;
}
    
int main()
{
    auto map = createMapFromRanges(std::vector<int>{1,2,3}, std::list<int>{4,5,6});
    for (auto& elem : map) {
        std::cout << elem.first << '/' << elem.second << '\n';
    }
    return 0;
}

P.S. I see many posts referencing other libraries (e.g. boost, range-v3) or unsupported features (e.g. zip_view). Certainly, I am in favor of reusing high quality libraries. Unfortunately, many projects simply cannot use them (i.e. restricted by the organization).

MarkB
  • 672
  • 2
  • 9