3

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.

johnco3
  • 2,401
  • 4
  • 35
  • 67

2 Answers2

2

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

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
1

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.

Shahriar
  • 768
  • 4
  • 11
  • I'm new to this so ... - If I understand it correctly, each time the filter lambda is called (which occurs for each NV pair in the map), we search all of the globally defined keys for a matching key string, and if there is a match we check that the value within the pair is odd. This will happen for each NV pair in the map if I understand correctly. Is there a more efficient way? – johnco3 Feb 04 '22 at 00:08
  • 1
    The more efficient way would be going through the vector instead of the map which is the other answer. – Shahriar Feb 04 '22 at 08:09
  • do you think it would be possible to pre-sort the map and vector containers and use ranges set_intersection to be even more efficient. Both containers (map and vector) in practice will be very large. I recall using std::set_intersection in the past and I had to pre-sort the inputs for both containers (which were different types like here) but I cannot figure out how to use the ranges equivalent so I could have the expressiveness of functional code – johnco3 Feb 04 '22 at 21:43
  • 1
    @johnco3 The map is sorted by default. If both containers are sorted, Then the optimal algorithm to find common elements would take O(n + m). You are searching one in another which takes O (n * logm). If you are concerned with performance, I suggest writing a custom algorithm. Otherwise, this is enough. – Shahriar Feb 05 '22 at 20:41
  • thanks for the follow up, to that end I posted another question with a live coliru link http://coliru.stacked-crooked.com/a/7893faec83660442 - in this case I am using the std::set_intersecton instead of the corresponding one from ranges which is a bit more involved. The issue here is trying to make a custom output type – johnco3 Feb 05 '22 at 21:17