як знайти перетин двох std :: set в C ++?


93

Я намагався знайти перетин між двома std :: set в C ++, але я постійно отримую помилку.

Я створив для цього невеликий зразок тесту

#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у C ++? Бажано вбудовану функцію?

Дуже дякую!


"Я сподіваюся на новий набір (назвемо це s3)" Але у вас немає, і ви цього не зробили. Я не розумію, куди ви очікували результатів. Також ви не читали документацію, щоб з'ясувати, які аргументи передавати.
Перегони легкості на орбіті

Відповіді:


113

Ви не надали вихідний ітератор для 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ітератор, оскільки набір на даний момент порожній. Ми не можемо використовувати back_ або front_inserter, оскільки набір не підтримує ці операції.


70
Я хотів би зрозуміти, чому така фундаментальна операція над множинами вимагає такого таємничого багатослівного заклинання. Чому б не простий set<T>& set::isect(set<T>&)метод, який робить необхідне? (Я б попросив set<T>& set::operator^(set<T>&), але це, швидше за все, міст занадто далеко.)
Райан В. Бісселл,

3
@ RyanV.Bissell це схожа конструкція майже для всіх алгоритмів із <algorithm>такою послідовністю, якщо нічим іншим. Цей стиль також, я припускаю, надає вам гнучкості. І дозволяє використовувати алгоритми з декількома контейнерами, хоча тут цього може не статися .. Також ваш підпис може не працювати, вам, ймовірно, потрібно повернути значення. І що за днів до семантики копіювання, я думаю, було б подвійною копією. Я давно не робив c ++, тому візьміть це з щіпкою або 3 солі
Karthik T

4
Я все ще вважаю себе початківцем STL, тому застосовується також використання сольових зерен. Моє вікно редагування коментарів минуло, тому я не можу виправити помилкові помилки повернення за посиланням. Мій коментар був не скаргою на послідовність, а чесним запитанням про те, чому цей синтаксис обов’язково повинен бути таким гірким. Можливо, мені слід зробити це SO питання.
Райан В. Бісселл,

3
Насправді, більшість бібліотек C ++ std розроблена таким незрозумілим чином. Хоча елегантність дизайну очевидна (загальна спільність, але не тільки), складність API має руйнівні наслідки (здебільшого тому, що люди продовжують винаходити колеса, оскільки не можуть використовувати ті, що поставляються разом із їх компілятором). В іншому світі дизайнери були б ошляпані за те, що вони віддали перевагу своїм задоволенням, аніж своїм користувачам. У цьому світі ... ну, принаймні, у нас є StackOverflow.

3
Це "загальний синтаксис" - ви також можете виконати set_intersection у векторі та у списку та зберегти результати у дека, і ви повинні мати змогу робити навіть це річ ефективно (звичайно, ваша проблема подбати про те, щоб обидва вихідні контейнери сортуються до виклику цього). Я не вважаю це поганим, єдине, з чим я маю проблеми, це те, що міг бути також метод setконтейнера, який перетинається з іншим набором. Тема передачі контейнера замість .begin()- .end()інша річ - це буде виправлено, коли на C ++ з’являться поняття.
Ethouris

25

Подивіться на зразок за посиланням: 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));

6
back_inserterне працює з , setяк setне має push_backфункції.
Джек Ейдлі,

6

Див. Std :: set_intersection . Ви повинні додати вихідний ітератор, де ви будете зберігати результат:

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

Повний перелік див. У Ideone .


3
Зверніть увагу, що back_inserter не буде працювати, якщо ви хочете, щоб результат також був набором, тоді вам потрібен std :: inserter, як Karthik.
Джозеф Гарвін,

4

Просто коментуйте тут. Я думаю, настав час додати об'єднання, перетинати операцію до заданого інтерфейсу. Давайте запропонуємо це в майбутніх стандартах. Я використовую std протягом тривалого часу, кожного разу, коли я використовував встановлену операцію, я хотів, щоб std був кращим. Для деяких складних операцій з набором, таких як перетинання, ви можете просто (простіше?) Змінити такий код:

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. Однак це не спрацює з вектором.


4

Перший (добре проголосований) коментар прийнятої відповіді скаржиться на відсутність оператора для існуючих операцій std set.

З одного боку, я розумію відсутність таких операторів у стандартній бібліотеці. З іншого боку, їх легко додати (для особистої радості) за бажанням. Я перевантажив

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

$

1
std::setвже реалізує необхідний конструктор переміщення та оператор присвоєння, тому про це не потрібно турбуватися. Також компілятор, швидше за все, використовує оптимізацію поверненого значення
Олаф Дітше,

@OlafDietsche Дякую за ваш коментар. Я перевірив це та покращив відповідь відповідно. Щодо RVO, я вже мав певні дискусії зі своїми колегами, поки не показав їм у налагоджувачі VS2013, що цього не відбувається (принаймні на нашій платформі devel.). Насправді це не так важливо, крім випадків, коли код є критично важливим для продуктивності. В останньому випадку я поки що не покладаюся на RVO. (Це насправді не так складно в C ++ ...)
Scheff

@Scheff ну Scheff (не Бозе), гарне пояснення.
JeJo

Навіть зараз підтримка VS гарантованого випробування C ++ 17 є жалюгідною.
Гонки легкості на орбіті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.