¿Cómo encontrar la intersección de dos std :: set en C ++?

93

He estado tratando de encontrar la intersección entre dos std :: set en C ++, pero sigo recibiendo un error.

Creé una pequeña prueba de muestra para esto

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

El último programa no genera ninguna salida, pero espero tener un nuevo conjunto (llamémoslo s3) con los siguientes valores:

s3 = [ 1 , 3 ]

En cambio, obtengo el 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>)

Lo que entiendo de este error es que no hay una definición set_intersectionque acepte Rb_tree_const_iterator<int>como parámetro.

Además, supongo que el std::set.begin()método devuelve un objeto de ese tipo,

¿Hay una mejor manera de encontrar la intersección de dos std::seten C ++? ¿Preferiblemente una función incorporada?

¡Muchas gracias!

Me gustan los tacos
fuente
"Espero tener un nuevo conjunto (llamémoslo s3)" Pero no es así, y no fue así. No entiendo a dónde esperabas que fueran los resultados. Además, no leyó la documentación para averiguar qué argumentos pasar.
Lightness Races in Orbit

Respuestas:

113

No ha proporcionado un iterador de salida para set_intersection

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

Solucione esto haciendo algo como

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

Necesita un std::insertiterador ya que el conjunto está ahora vacío. No podemos usar back_ o front_inserter ya que set no admite esas operaciones.

Karthik T
fuente
71
Me gustaría entender por qué una operación tan fundamental en conjuntos requiere un encantamiento tan arcano y detallado. ¿Por qué no un set<T>& set::isect(set<T>&)método sencillo , que hace lo necesario? (Yo pediría un set<T>& set::operator^(set<T>&), pero es probable que sea un puente demasiado lejos.)
Ryan V. Bissell
3
@ RyanV.Bissell este es un diseño similar a casi todos los algoritmos en <algorithm>tan consistencia si nada más. Este estilo también, supongo, te da flexibilidad. Y permite que los algos se utilicen con varios contenedores, aunque eso podría no suceder aquí. Además, es posible que su firma no funcione, probablemente necesite devolver un valor. Y eso en los días anteriores a la semántica de la copia creo que habría sido una copia doble. No he hecho C ++ por un tiempo, así que tómate esto con una pizca o 3 de sal
Karthik T
4
Todavía me considero un novato en STL, por lo que también se aplica la aplicación de granos de sal. Mi ventana de edición de comentarios ha expirado, por lo que no puedo corregir el paso en falso de retorno por referencia. Mi comentario no fue una queja sobre la coherencia, sino una pregunta honesta sobre por qué esta sintaxis debe tener necesariamente un sabor tan amargo. Quizás debería hacer de esta una pregunta SO.
Ryan V. Bissell
3
En realidad, la mayoría de las librerías estándar de C ++ están diseñadas de esta forma oscura. Si bien la elegancia del diseño es obvia (con genéricoidad, pero no solo), la complejidad de la API tiene efectos devastadores (principalmente porque la gente sigue reinventando ruedas ya que no pueden usar las que vienen con su compilador). En otro mundo, los diseñadores habrían sido abofeteados por haber favorecido su placer sobre el de sus usuarios. En este mundo ... bueno, al menos tenemos StackOverflow.
3
Esta es una "sintaxis general": también puede hacer set_intersection en un vector y en una lista y almacenar los resultados en una deque, y debería poder hacer incluso esta cosa de manera eficiente (por supuesto, es su problema cuidar que ambos Los contenedores de origen se ordenan antes de llamar a esto). No lo encuentro mal, lo único con lo que tengo problema es que también podría haber un método de setcontenedor que se cruza con otro conjunto. El tema de pasar un contenedor en lugar de .begin()- .end()es otra cosa - esto se solucionará una vez que C ++ tenga conceptos.
Ethouris
25

Eche un vistazo a la muestra en el enlace: http://en.cppreference.com/w/cpp/algorithm/set_intersection

Necesita otro contenedor para almacenar los datos de la intersección, se supone que el código siguiente funciona:

std::vector<int> common_data;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(common_data));
billz
fuente
6
back_inserterno funciona con setya que setno tiene push_backfunción.
Jack Aidley
6

Consulte std :: set_intersection . Debe agregar un iterador de salida, donde almacenará el resultado:

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

Consulte Ideone para obtener una lista completa.

Olaf Dietsche
fuente
3
Tenga en cuenta que back_inserter no funcionará si desea que el resultado también sea un conjunto, entonces necesita std :: inserter como el que usó Karthik.
Joseph Garvin
4

Solo comente aquí. Creo que es hora de agregar unión, operación de intersección a la interfaz establecida. Propongamos esto en los estándares futuros. He estado usando el std durante mucho tiempo, cada vez que usaba la operación de configuración deseaba que el std fuera mejor. Para alguna operación de conjunto complicada, como intersección, puede simplemente (¿más fácil?) Modificar el siguiente código:

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

copiado de http://www.cplusplus.com/reference/algorithm/set_intersection/

Por ejemplo, si su salida es un conjunto, puede output.insert (* first1). Además, es posible que su función no tenga plantilla. Si su código puede ser más corto que el uso de la función std set_intersection, continúe con él.

Si desea hacer una unión de dos conjuntos, simplemente puede setA.insert (setB.begin (), setB.end ()); Esto es mucho más simple que el método set_union. Sin embargo, esto no funcionará con vector.

Kemin Zhou
fuente
4

El primer comentario (bien votado) de la respuesta aceptada se queja de que falta un operador para las operaciones de conjunto estándar existentes.

Por un lado, entiendo la falta de tales operadores en la biblioteca estándar. Por otro lado, es fácil agregarlos (para la alegría personal) si lo desea. Yo sobrecargué

  • operator *() para intersección de conjuntos
  • operator +() para unión de conjuntos.

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

Compilado y probado:

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

$ 

Lo que no me gusta es la copia de los valores devueltos en los operadores. Puede ser, esto podría resolverse usando la asignación de movimiento, pero esto aún está más allá de mis habilidades.

Debido a mi conocimiento limitado acerca de esta semántica de movimientos "nueva y elegante", estaba preocupado por los retornos del operador que podrían causar copias de los conjuntos devueltos. Olaf Dietsche señaló que estas preocupaciones son innecesarias ya std::setque ya está equipado con un constructor / asignación de movimientos.

Aunque le creí, estaba pensando en cómo comprobar esto (para algo como "autoconvencimiento"). De hecho, es bastante fácil. Como las plantillas deben proporcionarse en el código fuente, simplemente puede pasar por el depurador. Por lo tanto, coloqué un punto de ruptura justo en el return s;del operator *()y procedí con un solo paso que me condujo inmediatamente a std::set::set(_myt&& _Right): et voilà - el constructor de movimientos. Gracias, Olaf, por la (mi) iluminación.

En aras de la integridad, también implementé los operadores de asignación correspondientes

  • operator *=() para la intersección "destructiva" de conjuntos
  • operator +=() para la unión "destructiva" de conjuntos.

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

Compilado y probado:

$ 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
fuente
1
std::setya implementa el constructor de movimientos y el operador de asignación necesarios, por lo que no hay necesidad de preocuparse por eso. Además, es muy probable que el compilador emplee la optimización del valor de retorno
Olaf Dietsche
@OlafDietsche Gracias por tu comentario. Revisé esto y mejoré la respuesta respectivamente. Sobre RVO, ya tuve ciertas discusiones con mis colegas hasta que les mostré en el depurador de VS2013 que no sucede (al menos en nuestra plataforma de desarrollo). En realidad, no es tan importante, excepto si el código es crítico para el rendimiento. En este último caso, por ahora sigo sin confiar en RVO. (Eso en realidad no es tan difícil en C ++ ...)
Scheff
@Scheff bueno Scheff (no Bose), buena explicación.
JeJo
Incluso ahora, el soporte de VS para la elisión garantizada de C ++ 17 es lamentable.
Lightness Races in Orbit