johnco3
johnco3

Reputation: 2652

how to use a vector of keys to filter values from a map and produce a set using c++ ranges syntax

I am new to Eric Niebler's ranges-v3 library and I would like to solve the following simple problem:

I have a std::map containing the following:

std::map<std::string, int> map = {
    {"THIS", 1}, {"IS", 2}, {"A", 3}, {"TEST", 4}, {"WITH", 5}, {"MORE", 6}, {"KEYS", 7}
};

I have a std::vector containing a set of keys as follows:

std::vector<std::string> vec = {
    "THIS", "IS", "A", "TEST" 
};

How do I use the library (ideally using the pipe composition syntax) to filter out pairs from the map containing the keys in the vector and transcode the resultant view to a std::set<std::pair<std::string, int>>. Additionally to make the transcode interesting, make sure that the values in the map need to be odd. In this case the range code should:

use these keys:

THIS,IS,A,TEST,

against this map

{A,3},{IS,2},{KEYS,7},{MORE,6},{TEST,4},{THIS,1},{WITH,5},

with an odd map value predicate - produce the following result as a different type of container std::set

{A,3},{THIS,1},

This should be relatively straightforward but I cannot figure out the new syntax. Also any insight about eager vs lazy would be a plus to help me understand.

The following stripped down coliru example shows what I am looking for.

Upvotes: 3

Views: 1505

Answers (2)

康桓瑋
康桓瑋

Reputation: 42776

Compared with traversing the entire map, it is more efficient to just traverse vec (generally small in size) and find the value corresponding to the key value from the map since the search operation of the map is logarithmic.

std::set<std::pair<std::string, int>> set;
std::ranges::copy(
  vec | std::views::transform([&map](auto& key) { return map.find(key); })
      | std::views::filter([&map](auto it) { return it != map.end() && 
                                                    it->second % 2 == 1; })
      | std::views::transform([](auto it) { return *it; }),
  std::inserter(set, set.begin()));

Demo

Upvotes: 2

Shahriar
Shahriar

Reputation: 798

Are you looking for something like this:


    std::map<std::string, int> map = {{"THIS", 1}, {"IS", 2},   {"A", 3},   {"TEST", 4},
                                      {"WITH", 5}, {"MORE", 6}, {"KEYS", 7}};

    std::vector<std::string> keys = {"THIS", "IS", "A", "TEST"};

    auto output = map | ranges::views::filter([&](auto &&pair) {
                      return std::find(keys.begin(), keys.end(), pair.first) != keys.end() && pair.second % 2 == 1;
                  });

    std::set<std::pair<std::string, int>> set(output.begin(), output.end());

    for (auto [key, value] : set) std::cout << key << " " << value << std::endl;

Here I have filtered the map with the vector and created a range adaptor closure object. Then I have created a set from output.

About the laziness, Look at this code:


    std::map<std::string, int> map = {{"THIS", 1}, {"IS", 2},   {"A", 3},   {"TEST", 4},
                                      {"WITH", 5}, {"MORE", 6}, {"KEYS", 7}};

    std::vector<std::string> keys = {"THIS", "IS", "A", "TEST"};

    auto output = map | ranges::views::filter([&](auto &&pair) {
                      std::cout << "Seen" << std::endl;
                      return std::find(keys.begin(), keys.end(), pair.first) != keys.end() && pair.second % 2 == 1;
                  });

    std::cout << "I'm lazy!" << std::endl;

    std::set<std::pair<std::string, int>> set(output.begin(), output.end());

    for (auto [key, value] : set) std::cout << key << " " << value << std::endl;

I have added some prints. The output is:

I'm lazy!
Seen
Seen
Seen
Seen
Seen
Seen
Seen
A 3
THIS 1

Which shows that output is not evaluated until output is used to create the set.

Upvotes: 1

Related Questions