99

I have been trying to find the intersection between two std::set in C++, but I keep getting an error.

I created a small sample test for this

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;

int main() {
  set<int> s1;
  set<int> s2;

  s1.insert(1);
  s1.insert(2);
  s1.insert(3);
  s1.insert(4);

  s2.insert(1);
  s2.insert(6);
  s2.insert(3);
  s2.insert(0);

  set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end());
  return 0;
}

The latter program does not generate any output, but I expect to have a new set (let's call it s3) with the following values:

s3 = [ 1 , 3 ]

Instead I'm getting the error:

test.cpp: In function ‘int main()’:
test.cpp:19: error: no matching function for call to ‘set_intersection(std::_Rb_tree_const_iterator<int>, std::_Rb_tree_const_iterator<int>, std::_Rb_tree_const_iterator<int>, std::_Rb_tree_const_iterator<int>)’

What I understand out of this error, is that there's no definition in set_intersection that accepts Rb_tree_const_iterator<int> as parameter.

Furthermore, I suppose the std::set.begin() method returns an object of such type,

is there a better way to find the intersection of two std::set in C++? Preferably a built-in function?

Thanks a lot!

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
ILikeTacos
  • 13,823
  • 16
  • 52
  • 81
  • _"I expect to have a new set (let's call it s3)"_ But you don't, and you didn't. I don't understand where you expected the results to go. Also you didn't read the documentation to find out what arguments to pass. – Lightness Races in Orbit May 24 '19 at 11:28

5 Answers5

119

You haven't provided an output iterator for set_intersection

template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator set_intersection ( InputIterator1 first1, InputIterator1 last1,
                                  InputIterator2 first2, InputIterator2 last2,
                                  OutputIterator result );

Fix this by doing something like

...;
set<int> intersect;
set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
                 std::inserter(intersect, intersect.begin()));

You need a std::insert iterator since the set is as of now empty. We cannot use std::back_inserter or std::front_inserter since set doesn't support those operations.

Intrastellar Explorer
  • 764
  • 2
  • 14
  • 44
Karthik T
  • 29,587
  • 4
  • 58
  • 84
  • 85
    I would like to understand why such a fundamental operation on sets requires such an arcanely verbose incantation. Why not a simple `set& set::isect(set&)` method, that does the needful? (I'd ask for an `set& set::operator^(set&)`, but that's likely a bridge too far.) – Ryan V. Bissell Aug 28 '14 at 03:22
  • 3
    @RyanV.Bissell this is a similar design to almost all algorithms in `` so consistency if nothing else. This style also, I assume, gives you flexibility. And allows the algos to be used with several containers, though that might not happen here.. Also your signature might not work, you probably need to return a value. And that in the days before copy semantics would have been a double copy i think. I havent done c++ for a while now so take this with a pinch or 3 of salt – Karthik T Aug 28 '14 at 05:25
  • 4
    I still consider myself an STL novice, so application of salt grains also applies. My comment editing window has expired, so I cannot correct the return-by-reference faux-pas. My comment was not a complaint about consistency, but an honest question about why this syntax must necessarily taste so bitter. Perhaps I should make this an SO Question. – Ryan V. Bissell Aug 28 '14 at 11:55
  • @RyanV.Bissell thats a good idea, since it is more of a design choice of the entire `` library, so it would be interesting to see pros/cons – Karthik T Sep 03 '14 at 02:01
  • 3
    Actually, most of the C++ std lib is designed in this obscure fashion. While the elegance of the design is obvious (w.r.t genericity, but not only), the complexity of the API has devastating effects (mostly because people keep on reinventing wheels since they can't use those that ship with their compiler). In another world, the designers would have been slapped for having favored their pleasure over their user's. In this world ... well, at least we have StackOverflow. –  Mar 30 '16 at 12:12
  • 3
    This is a "general syntax" - you can also do set_intersection on a vector and on a list and store results into a deque, and you should be able to do even this thing efficiently (of course, it's your problem to take care that both source containers are sorted prior to calling this). I don't find it bad, the only thing I have problem with is that there could be also a method of `set` container that does intersection with another set. The topic of passing a container instead of `.begin()` - `.end()` is another thing - this will be fixed once C++ has concepts. – Ethouris Oct 10 '16 at 06:45
  • @Karthik T, nice solution! One way to optimize it even further is to use "intersect.end()" in the inserter (instead of "intersect.begin()"), because intersect.end() is always the right place for the new item to be added and it would save us O(log(n)) time for each insertion. – Sobi Dec 01 '16 at 20:02
  • 1
    @Sobi: that `intersect.begin()` is evaluated only once, when the result set is empty and it has the same value as `intersect.end()`. It's not evaluated once per output element... – 6502 Jan 06 '17 at 18:00
  • 1
    The algorithms API wouldn't be so bad if STL supported the concept of _ranges_, where a range is anything that has a begin/end iterator pair. The range algorithms in Boost implement this idea quite nicely. – Emile Cormier May 30 '17 at 23:09
  • @RyanV.Bissell Sorry, that I'm that late. It seems that a significant number of people share your point. (I like the idea of operators as well.) I wonder why nobody mentioned that such an operator could be added easily. As I didn't dare to modify the well accepted answer I added my own at the end. – Scheff's Cat Jul 13 '17 at 11:28
  • @Scheff It doesn't return a new set (as you and others suggest) because doing it efficiently requires copy-elision or move semantics which didn't exist when the STL was designed. Also, the container type can't be inferred from the iterators (set? vector? deque? …) Thirdly, including such functions could result in combinatorial explosion… Building the needed functions using the STL is easy. For example, nowadays we can write `template Container set_intersection(InpIt0 first0, InpIt0 last0, InpIt1 first1, InpIt1 last1)` – joki Oct 17 '17 at 08:50
  • @joki "It doesn't return a new set..." Sorry, I didn't got you. What do you mean with "It" and what (possibly) wrong statement of mine concerning "It" you found? (I didn't say anything about `std::set_intersection()` but just commented my suggested overloaded operators.) – Scheff's Cat Oct 17 '17 at 10:31
  • @Scheff sorry, my comment was probably too terse to be comprehensible – I was referring to the implied question about why `std::set_intersection` doesn't return a set or why no such operators exist. I just wanted to point out that being defined purely in terms of iterators makes `std::set_intersection` much more general (for example, unlike your suggestion it even works with sorted arrays). Since it's now possible to efficiently return containers by value, I agree with you that a more convenient interface could be provided, but my suggestion would be one with the signature in my comment above. – joki Oct 17 '17 at 14:35
  • @joki Ah, yepp. I didn't consider or even worried about the "why `std::set_intersection` doesn't return a set" and focused only on the DIY operators. – Scheff's Cat Oct 17 '17 at 14:38
25

Have a look at the sample in the link: http://en.cppreference.com/w/cpp/algorithm/set_intersection

You need another container to store the intersection data, below code suppose to work:

std::vector<int> common_data;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(common_data));
billz
  • 41,716
  • 7
  • 75
  • 95
6

See std::set_intersection. You must add an output iterator, where you will store the result:

#include <iterator>
std::vector<int> s3;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(s3));

See Ideone for full listing.

Olaf Dietsche
  • 66,104
  • 6
  • 91
  • 177
  • 3
    Note that back_inserter won't work if you want the result to also be a set, then you need std::inserter like Karthik used. – Joseph Garvin Dec 09 '15 at 15:13
4

Just comment here. I think it is time to add union, intersect operation to the set interface. Let's propose this in the future standards. I have been using the std for a long time, each time I used the set operation I wished the std were better. For some complicated set operation, like intersect, you may simply (easier?) modify the following code:

template <class InputIterator1, class InputIterator2, class OutputIterator>
  OutputIterator set_intersection (InputIterator1 first1, InputIterator1 last1,
                                   InputIterator2 first2, InputIterator2 last2,
                                   OutputIterator result)
{
  while (first1!=last1 && first2!=last2)
  {
    if (*first1<*first2) ++first1;
    else if (*first2<*first1) ++first2;
    else {
      *result = *first1;
      ++result; ++first1; ++first2;
    }
  }
  return result;
}

copied from http://www.cplusplus.com/reference/algorithm/set_intersection/

For example, if your output is a set, you can output.insert(*first1). Furthermore, you function may not be templated.If you code can be shorter than using the std set_intersection function then go ahead with it.

If you want to do a union of two set you can simply setA.insert(setB.begin(), setB.end()); This is much simpler than the set_union method. However, this will not work with vector.

Kemin Zhou
  • 4,375
  • 1
  • 29
  • 47
4

The first (well-voted) comment of the accepted answer complains about a missing operator for the existing std set operations.

On one hand, I understand the lack of such operators in the standard library. On the other hand, it is easy to add them (for the personal joy) if desired. I overloaded

  • operator *() for intersection of sets
  • operator +() for union of sets.

Sample test-set-ops.cc:

#include <algorithm>
#include <iterator>
#include <set>

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC> operator * (
  const std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  std::set<T, CMP, ALLOC> s;
  std::set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(),
    std::inserter(s, s.begin()));
  return s;
}

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC> operator + (
  const std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  std::set<T, CMP, ALLOC> s;
  std::set_union(s1.begin(), s1.end(), s2.begin(), s2.end(),
    std::inserter(s, s.begin()));
  return s;
}

// sample code to check them out:

#include <iostream>

using namespace std;

template <class T>
ostream& operator << (ostream &out, const set<T> &values)
{
  const char *sep = " ";
  for (const T &value : values) {
    out << sep << value; sep = ", ";
  }
  return out;
}

int main()
{
  set<int> s1 { 1, 2, 3, 4 };
  cout << "s1: {" << s1 << " }" << endl;
  set<int> s2 { 0, 1, 3, 6 };
  cout << "s2: {" << s2 << " }" << endl;
  cout << "I: {" << s1 * s2 << " }" << endl;
  cout << "U: {" << s1 + s2 << " }" << endl;
  return 0;
}

Compiled and tested:

$ g++ -std=c++11 -o test-set-ops test-set-ops.cc 

$ ./test-set-ops     
s1: { 1, 2, 3, 4 }
s2: { 0, 1, 3, 6 }
I: { 1, 3 }
U: { 0, 1, 2, 3, 4, 6 }

$ 

What I don't like is the copy of return values in the operators. May be, this could be solved using move assignment but this is still beyond my skills.

Due to my limited knowledge about these "new fancy" move semantics, I was concerned about the operator returns which might cause copies of the returned sets. Olaf Dietsche pointed out that these concerns are unnecessary as std::set is already equipped with move constructor/assignment.

Although I believed him, I was thinking how to check this out (for something like "self-convincing"). Actually, it is quite easy. As templates has to be provided in source code, you can simply step through with the debugger. Thus, I placed a break point right at the return s; of the operator *() and proceeded with single-step which leaded me immediately into std::set::set(_myt&& _Right): et voilà – the move constructor. Thanks, Olaf, for the (my) enlightment.

For the sake of completeness, I implemented the corresponding assignment operators as well

  • operator *=() for "destructive" intersection of sets
  • operator +=() for "destructive" union of sets.

Sample test-set-assign-ops.cc:

#include <iterator>
#include <set>

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC>& operator *= (
  std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  auto iter1 = s1.begin();
  for (auto iter2 = s2.begin(); iter1 != s1.end() && iter2 != s2.end();) {
    if (*iter1 < *iter2) iter1 = s1.erase(iter1);
    else {
      if (!(*iter2 < *iter1)) ++iter1;
      ++iter2;
    }
  }
  while (iter1 != s1.end()) iter1 = s1.erase(iter1);
  return s1;
}

template <class T, class CMP = std::less<T>, class ALLOC = std::allocator<T> >
std::set<T, CMP, ALLOC>& operator += (
  std::set<T, CMP, ALLOC> &s1, const std::set<T, CMP, ALLOC> &s2)
{
  s1.insert(s2.begin(), s2.end());
  return s1;
}

// sample code to check them out:

#include <iostream>

using namespace std;

template <class T>
ostream& operator << (ostream &out, const set<T> &values)
{
  const char *sep = " ";
  for (const T &value : values) {
    out << sep << value; sep = ", ";
  }
  return out;
}

int main()
{
  set<int> s1 { 1, 2, 3, 4 };
  cout << "s1: {" << s1 << " }" << endl;
  set<int> s2 { 0, 1, 3, 6 };
  cout << "s2: {" << s2 << " }" << endl;
  set<int> s1I = s1;
  s1I *= s2;
  cout << "s1I: {" << s1I << " }" << endl;
  set<int> s2I = s2;
  s2I *= s1;
  cout << "s2I: {" << s2I << " }" << endl;
  set<int> s1U = s1;
  s1U += s2;
  cout << "s1U: {" << s1U << " }" << endl;
  set<int> s2U = s2;
  s2U += s1;
  cout << "s2U: {" << s2U << " }" << endl;
  return 0;
}

Compiled and tested:

$ g++ -std=c++11 -o test-set-assign-ops test-set-assign-ops.cc 

$ ./test-set-assign-ops
s1: { 1, 2, 3, 4 }
s2: { 0, 1, 3, 6 }
s1I: { 1, 3 }
s2I: { 1, 3 }
s1U: { 0, 1, 2, 3, 4, 6 }
s2U: { 0, 1, 2, 3, 4, 6 }

$
Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • 1
    [`std::set`](http://en.cppreference.com/w/cpp/container/set/set) already implements the necessary move constructor and assignment operator, so no need to worry about that. Also the compiler most likely employs [return value optimization](https://stackoverflow.com/q/12953127/1741542) – Olaf Dietsche Jul 13 '17 at 20:01
  • @OlafDietsche Thanks for your comment. I checked this out and improved the answer respectively. About RVO, I already had certain discussions with my colleagues until I showed them in the debugger of VS2013 that it doesn't happen (at least in our devel. platform). Actually, it is not that important except if code is performance critical. In the latter case, I keep for now not to rely on RVO. (That's actually not that difficult in C++...) – Scheff's Cat Jul 14 '17 at 06:12
  • @Scheff well Scheff(not Bose), nice explanation. – JeJo May 04 '18 at 15:33
  • Even now VS's support for C++17's guaranteed elision is woeful. – Lightness Races in Orbit May 24 '19 at 11:31