как найти пересечение двух std::set в C++?

Я пытался найти пересечение между двумя std::set в С++, но все время получаю сообщение об ошибке.

Я создал небольшой образец теста для этого

#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;
}

Последняя программа не генерирует никакого вывода, но я ожидаю получить новый набор (назовем его s3) со следующими значениями:

s3 = [ 1 , 3 ]

Вместо этого я получаю сообщение об ошибке:

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>)’

Что я понимаю из этой ошибки, так это то, что в set_intersection нет определения, которое принимает Rb_tree_const_iterator<int> в качестве параметра.

Кроме того, я предполагаю, что метод std::set.begin() возвращает объект такого типа,

есть ли лучший способ найти пересечение двух std::set в С++? Предпочтительно встроенная функция?

Большое спасибо!


person ILikeTacos    schedule 19.11.2012    source источник
comment
Я ожидаю получить новый набор (назовем его s3) Но у вас его нет, и у вас его не было. Я не понимаю, где вы ожидали результатов. Также вы не читали документацию, чтобы узнать, какие аргументы передавать.   -  person Lightness Races in Orbit    schedule 24.05.2019


Ответы (5)


Вы не указали выходной итератор для set_intersection.

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

Исправьте это, выполнив что-то вроде

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

Вам нужен итератор std::insert, так как набор на данный момент пуст. Мы не можем использовать std::back_inserter или std::front_inserter, так как набор не поддерживает эти операции.

person Karthik T    schedule 19.11.2012
comment
Я хотел бы понять, почему такая фундаментальная операция над множествами требует такого загадочного многословного заклинания. Почему бы не использовать простой метод set<T>& set::isect(set<T>&), который делает все необходимое? (Я бы попросил set<T>& set::operator^(set<T>&), но это, вероятно, слишком далеко.) - person Ryan V. Bissell; 28.08.2014
comment
@ RyanV.Bissell, это дизайн, аналогичный почти всем алгоритмам в <algorithm>, поэтому согласованность, если не что иное. Этот стиль также, я полагаю, дает вам гибкость. И позволяет использовать алгоритмы с несколькими контейнерами, хотя здесь этого может не произойти. Также ваша подпись может не работать, вам, вероятно, нужно вернуть значение. И я думаю, что в дни, предшествующие копированию, семантика была бы двойной копией. Я не занимался С++ какое-то время, так что возьмите это с щепоткой или тремя солью - person Karthik T; 28.08.2014
comment
Я до сих пор считаю себя новичком в STL, поэтому применимо и применение соляных зерен. Срок действия окна редактирования моего комментария истек, поэтому я не могу исправить оплошность возврата по ссылке. Мой комментарий был не жалобой на согласованность, а честным вопросом о том, почему этот синтаксис обязательно должен быть таким горьким на вкус. Возможно, мне следует сделать это SO-вопросом. - person Ryan V. Bissell; 28.08.2014
comment
@ RyanV.Bissell, это хорошая идея, поскольку это скорее выбор дизайна всей библиотеки <algorithm>, поэтому было бы интересно увидеть плюсы и минусы. - person Karthik T; 03.09.2014
comment
На самом деле, большая часть стандартной библиотеки C++ разработана таким непонятным образом. В то время как элегантность дизайна очевидна (относительно универсальности, но не только), сложность API имеет разрушительные последствия (в основном потому, что люди продолжают изобретать колеса, поскольку они не могут использовать те, которые поставляются с их компилятором). В другом мире дизайнеры получили бы пощечину за то, что предпочли собственное удовольствие пользовательскому. В этом мире… ну, по крайней мере, у нас есть StackOverflow. - person ; 30.03.2016
comment
Это общий синтаксис — вы также можете использовать set_intersection для вектора и списка и сохранять результаты в деке, и вы должны быть в состоянии сделать даже это эффективно (конечно, ваша проблема — позаботиться о том, чтобы оба исходных контейнера сортируются до вызова this). Я не нахожу это плохим, единственное, с чем у меня есть проблема, это то, что может быть также метод set контейнера, который пересекается с другим набором. Другое дело передача контейнера вместо .begin() - .end() - это будет исправлено, когда в C++ появятся концепции. - person Ethouris; 10.10.2016
comment
@Картик Т, хорошее решение! Один из способов еще больше оптимизировать его — использовать intersect.end() в средстве вставки (вместо intersect.begin()), потому что intersect.end() всегда является правильным местом для добавления нового элемента, и это сэкономит нам O(log(n)) времени для каждой вставки. - person Sobi; 01.12.2016
comment
@Sobi: это intersect.begin() оценивается только один раз, когда набор результатов пуст и имеет то же значение, что и intersect.end(). Он не оценивается один раз для каждого выходного элемента... - person 6502; 06.01.2017
comment
API алгоритмов не был бы таким плохим, если бы STL поддерживала концепцию диапазонов, где диапазон — это все, что имеет пару итераторов начало/конец. Алгоритмы диапазона в Boost прекрасно реализуют эту идею. - person Emile Cormier; 31.05.2017
comment
@ RyanV.Bissell Извините, что я так опоздал. Кажется, что значительное число людей разделяет вашу точку зрения. (Мне также нравится идея операторов.) Интересно, почему никто не упомянул, что такой оператор можно легко добавить. Поскольку я не осмелился изменить общепринятый ответ, я добавил свой собственный в конце. - person Scheff's Cat; 13.07.2017
comment
@Scheff Он не возвращает новый набор (как вы и другие предлагаете), потому что для его эффективного выполнения требуется семантика копирования или перемещения, которой не существовало при разработке STL. Кроме того, тип контейнера не может быть выведен из итераторов (set? vector? deque?...) В-третьих, включение таких функций может привести к комбинаторному взрыву... Создание необходимых функций с помощью STL несложно. Например, сегодня мы можем написать template <typename Container, typename InpIt0, typename InpIt1> Container set_intersection(InpIt0 first0, InpIt0 last0, InpIt1 first1, InpIt1 last1) - person joki; 17.10.2017
comment
@joki Он не возвращает новый набор ... Извините, я вас не понял. Что вы имеете в виду под этим и какое (возможно) неправильное мое высказывание по этому поводу вы нашли? (Я ничего не сказал о std::set_intersection(), а просто прокомментировал предложенные мной перегруженные операторы.) - person Scheff's Cat; 17.10.2017
comment
@Scheff извините, мой комментарий, вероятно, был слишком кратким, чтобы его можно было понять - я имел в виду подразумеваемый вопрос о том, почему std::set_intersection не возвращает набор или почему таких операторов не существует. Я просто хотел отметить, что определение исключительно с точки зрения итераторов делает std::set_intersection гораздо более общим (например, в отличие от вашего предложения, он работает даже с отсортированными массивами). Поскольку теперь можно эффективно возвращать контейнеры по значению, я согласен с вами, что можно было бы предоставить более удобный интерфейс, но мое предложение было бы с подписью в моем комментарии выше. - person joki; 17.10.2017
comment
@joki А, да. Я не рассматривал и даже не беспокоился о том, почему std::set_intersection не возвращает набор, и сосредоточился только на операторах DIY. - person Scheff's Cat; 17.10.2017

Взгляните на образец по ссылке: http://en.cppreference.com/w/cpp/algorithm/set_intersection

Вам нужен другой контейнер для хранения данных пересечения, ниже код должен работать:

std::vector<int> common_data;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(common_data));
person billz    schedule 19.11.2012
comment
back_inserter не работает с set, так как set не имеет функции push_back. - person Jack Aidley; 13.10.2017

См. std::set_intersection. Вы должны добавить итератор вывода, где вы будете хранить результат:

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

Полный список см. на странице Ideone.

person Olaf Dietsche    schedule 19.11.2012
comment
Обратите внимание, что back_inserter не будет работать, если вы хотите, чтобы результат также был набором, тогда вам понадобится std::inserter, как использовал Картик. - person Joseph Garvin; 09.12.2015

Первый (хорошо проголосовавший) комментарий к принятому ответу жалуется на отсутствие оператора для существующих операций стандартного набора.

С одной стороны, я понимаю отсутствие таких операторов в стандартной библиотеке. С другой стороны, их легко добавить (для личного удовольствия) при желании. я перегрузил

  • operator *() для пересечения множеств
  • operator +() для объединения наборов.

Образец 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;
}

Скомпилировано и протестировано:

$ 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 }

$ 

Что мне не нравится, так это копирование возвращаемых значений в операторах. Может быть, это можно было бы решить с помощью присваивания ходов, но это все еще выше моих навыков.

Из-за моих ограниченных знаний об этой «новой причудливой» семантике перемещений я был обеспокоен возвратом оператора, который может привести к копированию возвращаемых наборов. Олаф Дитше указал, что эти опасения не нужны, поскольку std::set уже оснащен конструктором перемещения/назначением.

Я хоть и поверил ему, но думал, как это проверить (для чего-то вроде "самоубедительности"). На самом деле, это довольно легко. Поскольку шаблоны должны быть предоставлены в исходном коде, вы можете просто выполнить их с помощью отладчика. Таким образом, я поставил точку останова прямо на return s; из operator *() и приступил к одношаговому выполнению, которое сразу же привело меня к std::set::set(_myt&& _Right): и вуаля, конструктору перемещения. Спасибо, Олаф, за (моё) просвещение.

Для полноты картины я также реализовал соответствующие операторы присваивания.

  • operator *=() за "деструктивное" пересечение множеств
  • operator +=() за "деструктивное" объединение множеств.

Образец 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;
}

Скомпилировано и протестировано:

$ 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 }

$
person Scheff's Cat    schedule 13.07.2017
comment
std::set уже реализует необходимый конструктор перемещения и оператор присваивания, поэтому нет необходимости беспокоиться об этом. Также компилятор, скорее всего, использует оптимизацию возвращаемого значения. - person Olaf Dietsche; 13.07.2017
comment
@OlafDietsche Спасибо за ваш комментарий. Я проверил это и улучшил ответ соответственно. По поводу RVO у меня уже были определенные дискуссии с коллегами, пока я не показал им в отладчике VS2013, что этого не происходит (по крайней мере, на нашей девел. платформе). На самом деле это не так важно, за исключением случаев, когда код критичен к производительности. В последнем случае я пока не полагаюсь на РВО. (На самом деле это не так сложно в С++...) - person Scheff's Cat; 14.07.2017
comment
@Scheff Хорошо, Шефф (не Бозе), хорошее объяснение. - person JeJo; 04.05.2018
comment
Даже сейчас поддержка VS гарантированного исключения C++ 17 плачевна. - person Lightness Races in Orbit; 24.05.2019

Просто прокомментируйте здесь. Я думаю, что пора добавить операцию union, intersect к заданному интерфейсу. Давайте предложим это в будущих стандартах. Я использую стандарт в течение долгого времени, каждый раз, когда я использовал операцию набора, я хотел, чтобы стандарт был лучше. Для некоторых сложных операций над множествами, таких как пересечение, вы можете просто (проще?) изменить следующий код:

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;
}

скопировано с http://www.cplusplus.com/reference/algorithm/set_intersection/

Например, если ваш вывод представляет собой набор, вы можете output.insert(*first1). Кроме того, ваша функция может не быть шаблонной. Если ваш код может быть короче, чем при использовании функции std set_intersection, продолжайте.

Если вы хотите объединить два набора, вы можете просто setA.insert(setB.begin(), setB.end()); Это намного проще, чем метод set_union. Однако это не будет работать с вектором.

person Kemin Zhou    schedule 09.03.2017