114 votes

Comment trouver l'intersection de deux ensembles STL ?

J'ai essayé de trouver l'intersection entre deux std::set en C++, mais je continue à obtenir une erreur.

J'ai créé un petit test pour cela

#include 
#include 
#include 
#include 
using namespace std;

int main() {
  set s1;
  set 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;
}

Le dernier programme ne génère aucun résultat, mais je m'attends à avoir un nouveau set (appelons-le s3) avec les valeurs suivantes :

s3 = [ 1 , 3 ]

À la place, je reçois l'erreur :

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

De ce que je comprends de cette erreur, c'est qu'il n'y a pas de définition dans set_intersection qui accepte Rb_tree_const_iterator en tant que paramètre.

De plus, je suppose que la méthode std::set.begin() renvoie un objet de ce type,

Y a-t-il un meilleur moyen de trouver l'intersection de deux std::set en C++? De préférence une fonction intégrée?

136voto

Karthik T Points 19418

Vous n'avez pas fourni un itérateur de sortie pour set_intersection

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

Corrigez cela en faisant quelque chose comme

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

Vous avez besoin d'un itérateur std::insert car l'ensemble est actuellement vide. Nous ne pouvons pas utiliser std::back_inserter ou std::front_inserter car l'ensemble ne prend pas en charge ces opérations.

25voto

billz Points 28166

Jetez un œil à l'exemple dans le lien : http://en.cppreference.com/w/cpp/algorithm/set_intersection

Vous avez besoin d'un autre conteneur pour stocker les données d'intersection, le code ci-dessous est censé fonctionner :

std::vector common_data;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(common_data));

7voto

Olaf Dietsche Points 35264

Voir std::set_intersection. Vous devez ajouter un itérateur de sortie, où vous stockerez le résultat :

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

Voir Ideone pour le listing complet.

7voto

Scheff Points 8113

Le premier commentaire (bien voté) de la réponse acceptée se plaint du manque d'opérateur pour les opérations std set existantes.

D'une part, je comprends le manque de tels opérateurs dans la bibliothèque standard. D'autre part, il est facile de les ajouter (pour le plaisir personnel) si désiré. J'ai surchargé

  • operator *() pour l'intersection des ensembles
  • operator +() pour l'union des ensembles.

Exemple test-set-ops.cc:

#include 
#include 
#include 

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

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

// code d'exemple pour les tester :

#include 

using namespace std;

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

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

Compilé et testé :

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

$ 

Ce que je n'aime pas, c'est la copie des valeurs de retour dans les opérateurs. Peut-être que cela pourrait être résolu en utilisant une assignation par déplacement, mais cela dépasse encore mes compétences.

En raison de mes connaissances limitées concernant ces "nouvelles et élégantes" sémantiques de déplacement, je m'inquiétais des retours des opérateurs qui pourraient entraîner des copies des ensembles retournés. Olaf Dietsche a souligné que ces préoccupations étaient inutiles car std::set est déjà équipé d'un constructeur/d'une affectation par déplacement.

Bien que je lui aie fait confiance, je réfléchissais à la manière de le vérifier (pour quelque chose de "convaincant pour moi-même"). En fait, c'est assez facile. Comme les modèles doivent être fournis dans le code source, vous pouvez simplement avancer pas à pas avec le débogueur. Ainsi, j'ai placé un point d'arrêt juste avant le return s; de l'opérateur *() et j'ai procédé étape par étape, ce qui m'a immédiatement mené à std::set::set(_myt&& _Right) : et voilà - le constructeur de déplacement. Merci, Olaf, pour l'(ma) illumination.

Pour des raisons de complétude, j'ai également implémenté les opérateurs d'affectation correspondants

  • operator *=() pour l'intersection "destructive" des ensembles
  • operator +=() pour l'union "destructive" des ensembles.

Exemple test-set-assign-ops.cc:

#include 
#include 

template , class ALLOC = std::allocator >
std::set& operator *= (
  std::set &s1, const std::set &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 ALLOC = std::allocator >
std::set& operator += (
  std::set &s1, const std::set &s2)
{
  s1.insert(s2.begin(), s2.end());
  return s1;
}

// code d'exemple pour les tester :

#include 

using namespace std;

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

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

Compilé et testé :

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

$

4voto

Kemin Zhou Points 31

Il suffit de commenter ici. Je pense qu'il est temps d'ajouter l'opération d'union et d'intersection à l'interface de l'ensemble. Proposons cela dans les normes futures. J'utilise le std depuis longtemps, chaque fois que j'utilise l'opération d'ensemble, je souhaite que le std soit meilleur. Pour certaines opérations d'ensemble complexes, comme intersect, vous pouvez simplement (plus facilement ?) modifier le code suivant :

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

copié de http://www.cplusplus.com/reference/algorithm/set_intersection/

Par exemple, si votre sortie est un ensemble, vous pouvez utiliser output.insert(*first1). De plus, votre fonction peut ne pas être basée sur un modèle. Si votre code peut être plus court que l'utilisation de la fonction std set_intersection, alors allez-y.

Si vous souhaitez faire l'union de deux ensembles, vous pouvez simplement faire setA.insert(setB.begin(), setB.end()); Ceci est beaucoup plus simple que la méthode set_union. Cependant, cela ne fonctionnera pas avec un vecteur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X