9

Suppose I have a vector<int> myVec; and I want to convert it to a set, I can have a one liner-

set<int> mySet(myVec.begin(), myVec.end());

This is something that can be found easily.

Now I have vector<pair<int, int>>, and I want to obtain the set of the second values in each of the pairs. How should I use a set constructor to achieve this? Is it possible?

Assuming I have C++11, C++14, C++17.

Also, I would appreciate if I can get some information about how to do similar tweaks in a general sense for different containers.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
user3903448
  • 320
  • 1
  • 8
  • looking forward to see clever answers, i'd probably write a custom iterator for the vector that when dereferenced returns the `second`, though that wouldnt be a one liner ;) – 463035818_is_not_an_ai Sep 25 '18 at 13:04

3 Answers3

16

Asking for one-liner in this case inevitably leads to a solution with range-v3:

#include <range/v3/view/map.hpp>

const std::vector<std::pair<int, int>> myVec{{1, 10}, {2, 20} , {3, 30}};
const std::set<int> mySet = myVec | ranges::view::values;

And a similar approach with Boost range¹:

#include <boost/range/adaptor/map.hpp>

using boost::adaptors::map_values;
const auto mySet = boost::copy_range<std::set<int>>(myVec | map_values); 

You might consider this approach the simplest, however (no libraries, but C++17 required):

for (const auto& [first, second] : myVec)
    mySet.insert(second);

¹ Thanks to @Caleth for suggesting an improvement in the comments.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • 2
    This is awesome. The ranges API is really cool, I hope it makes it to the standard soon. – jdehesa Sep 25 '18 at 13:13
  • 1
    It's hidden in the doc's for [`iterator_range`](https://www.boost.org/doc/libs/1_68_0/libs/range/doc/html/range/reference/utilities/iterator_range.html) – Caleth Sep 25 '18 at 13:35
  • @Caleth That indeed qualifies as _hidden_. – lubgr Sep 25 '18 at 13:38
6

This almost-one-liner should work for you:

#include <algorithm>
#include <iterator>
#include <iostream>
#include <set>
#include <utility>
#include <vector>

int main()
{
    std::vector<std::pair<int, int>> myVec = { {1, 2}, {3, 4} };
    std::set<int> mySet;
    std::transform(myVec.begin(), myVec.end(), std::inserter(mySet, mySet.begin()),
                   [](const std::pair<int, int>& elem) { return elem.second; });
    for (int value : mySet)
    {
        std::cout << value << std::endl;
    }
    return 0;
}

Output:

2
4
jdehesa
  • 58,456
  • 7
  • 77
  • 121
  • 2
    I think this is best You can get in standard C++, but still this ain't no one-liner – bartop Sep 25 '18 at 13:05
  • 5
    Worth noting that your "one-liner" with `transform` is much longer than the equivalent for-loop/insert would've been. Assuming normal spacing and full names with structured bindings... 143 characters vs 69. – Barry Sep 25 '18 at 13:57
  • @Barry Yep, that's true. I'm not saying this should necessarily be preferred to a loop, but I think the OP was looking for a "functional" solution to the problem. – jdehesa Sep 25 '18 at 15:22
  • @jdehesa Yeah, I wouldn't consider `transform` to be a functional solution either. ¯\\_(ツ)_/¯ – Barry Sep 25 '18 at 15:27
5

The most C++y way to do this is probably to define a custom iterator type which produces only the second value.

You can use very similar code as that for a more common query, which is to take the values from a map: iterator adapter to iterate just the values in a map?

Or, you know, just write a loop. Nothing wrong with that.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436