como encontrar a interseção de dois std :: set em C ++?

90

Tenho tentado encontrar a interseção entre dois std :: set em C ++, mas continuo recebendo um erro.

Eu criei um pequeno teste de amostra para este

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

O último programa não gera nenhuma saída, mas espero ter um novo conjunto (vamos chamá-lo s3) com os seguintes valores:

s3 = [ 1 , 3 ]

Em vez disso, estou recebendo o erro:

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

O que entendi desse erro, é que não existe uma definição set_intersectionque aceite Rb_tree_const_iterator<int>como parâmetro.

Além disso, suponho que o std::set.begin()método retorna um objeto desse tipo,

existe uma maneira melhor de encontrar a interseção de dois std::setem C ++? De preferência, uma função embutida?

Muito obrigado!

Eu gosto de tacos
fonte
"Espero ter um novo conjunto (vamos chamá-lo de s3)" Mas você não tem e não o fez. Não entendo onde você esperava que os resultados chegassem. Além disso, você não leu a documentação para descobrir quais argumentos passar.
Lightness Races in Orbit

Respostas:

105

Você não forneceu um iterador de saída para set_intersection

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

Corrija isso fazendo algo como

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

Você precisa de um std::insertiterador, pois o conjunto está vazio agora. Não podemos usar back_ ou front_inserter, pois set não suporta essas operações.

Karthik T
fonte
67
Eu gostaria de entender por que uma operação tão fundamental em conjuntos requer um encantamento tão detalhado. Por que não um set<T>& set::isect(set<T>&)método simples , que faz o necessário? (Eu pediria um set<T>& set::operator^(set<T>&), mas provavelmente é uma ponte longe demais.)
Ryan V. Bissell
3
@ RyanV.Bissell, este é um projeto semelhante a quase todos os algoritmos em <algorithm>consistência, se nada mais. Este estilo também, presumo, dá flexibilidade. E permite que o algos seja usado com vários contêineres, embora isso possa não acontecer aqui .. Além disso, sua assinatura pode não funcionar, você provavelmente precisará retornar um valor. E que, nos dias anteriores à cópia, a semântica seria uma cópia dupla, eu acho. Eu não tenho feito c ++ por um tempo agora, então aceite isso com uma pitada de sal
Karthik T
4
Ainda me considero um novato em STL, então a aplicação de grãos de sal também se aplica. Minha janela de edição de comentários expirou, então não posso corrigir o erro de retorno por referência. Meu comentário não foi uma reclamação sobre consistência, mas uma questão honesta sobre por que essa sintaxe deve necessariamente ter um gosto tão amargo. Talvez eu deva fazer esta uma pergunta SO.
Ryan V. Bissell
3
Na verdade, a maior parte da lib std C ++ é projetada dessa maneira obscura. Embora a elegância do design seja óbvia (grande genericidade, mas não só), a complexidade da API tem efeitos devastadores (principalmente porque as pessoas continuam reinventando rodas, já que não podem usar aquelas que vêm com seu compilador). Em outro mundo, os designers teriam sido esbofeteados por terem favorecido seu prazer ao de seu usuário. Neste mundo ... bem, pelo menos temos StackOverflow.
3
Esta é uma "sintaxe geral" - você também pode fazer set_intersection em um vetor e em uma lista e armazenar os resultados em um deque, e você deve ser capaz de fazer até mesmo isso de forma eficiente (é claro, é seu problema cuidar de que ambos os contêineres de origem são classificados antes de chamar isso). Não acho isso ruim, a única coisa que tenho problema é que pode haver também um método de setcontêiner que faz a interseção com outro conjunto. O tópico de passar um contêiner em vez de .begin()- .end()é outra coisa - será corrigido assim que o C ++ tiver conceitos.
Ethouris
25

Dê uma olhada no exemplo no link: http://en.cppreference.com/w/cpp/algorithm/set_intersection

Você precisa de outro contêiner para armazenar os dados de interseção, o código abaixo deve funcionar:

std::vector<int> common_data;
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(), std::back_inserter(common_data));
billz
fonte
6
back_inserternão funciona setcomo setnão tem push_backfunção.
Jack Aidley
6

Veja std :: set_intersection . Você deve adicionar um iterador de saída, onde armazenará o resultado:

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

Veja Ideone para uma lista completa.

Olaf Dietsche
fonte
3
Note que back_inserter não funcionará se você quiser que o resultado também seja um conjunto, então você precisa de std :: inserter como Karthik usou.
Joseph Garvin
4

Basta comentar aqui. Acho que é hora de adicionar a operação de união e interseção à interface definida. Vamos propor isso nos padrões futuros. Eu tenho usado o std por um longo tempo, cada vez que usei a operação de conjunto desejei que o std fosse melhor. Para alguma operação de conjunto complicada, como intersect, você pode simplesmente (mais fácil?) Modificar o seguinte 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 exemplo, se sua saída for um conjunto, você pode output.insert (* first1). Além disso, sua função não pode ser modelada. Se seu código pode ser mais curto do que usar a função std set_intersection, vá em frente com ela.

Se você quiser fazer uma união de dois conjuntos, você pode simplesmente setA.insert (setB.begin (), setB.end ()); Isso é muito mais simples do que o método set_union. No entanto, isso não funcionará com vetor.

Kemin Zhou
fonte
4

O primeiro comentário (bem votado) da resposta aceita reclama sobre a falta de um operador para as operações de conjunto padrão existentes.

Por um lado, entendo a falta de tais operadores na biblioteca padrão. Por outro lado, é fácil adicioná-los (para a alegria pessoal) se desejar. Eu sobrecarreguei

  • operator *() para interseção de conjuntos
  • operator +() para união de conjuntos.

Amostra 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 e testado:

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

$ 

O que eu não gosto é a cópia dos valores de retorno nas operadoras. Pode ser, isso poderia ser resolvido usando a atribuição de movimento, mas isso ainda está além das minhas habilidades.

Devido ao meu conhecimento limitado sobre essas semânticas de movimento "nova fantasia", fiquei preocupado com os retornos do operador que poderiam causar cópias dos conjuntos retornados. Olaf Dietsche apontou que essas preocupações são desnecessárias, pois std::setjá está equipado com o construtor / atribuição de movimento.

Embora acreditasse nele, estava pensando em como verificar isso (algo como "autoconvencimento"). Na verdade, é bem fácil. Como os modelos devem ser fornecidos no código-fonte, você pode simplesmente avançar com o depurador. Assim, coloquei um ponto de interrupção bem no return s;de operator *()e continuei com uma única etapa que me levou imediatamente a std::set::set(_myt&& _Right): et voilà - o construtor de movimento. Obrigado, Olaf, pela (minha) iluminação.

Para fins de integridade, implementei os operadores de atribuição correspondentes também

  • operator *=() para interseção "destrutiva" de conjuntos
  • operator +=() para união "destrutiva" de conjuntos.

Amostra 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 e testado:

$ 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
fonte
1
std::setjá implementa o construtor de movimento e o operador de atribuição necessários, portanto, não precisa se preocupar com isso. Além disso, o compilador provavelmente emprega otimização de valor de retorno
Olaf Dietsche
@OlafDietsche Obrigado pelo seu comentário. Eu verifiquei isso e melhorei a resposta, respectivamente. Sobre RVO, já tive algumas discussões com meus colegas até mostrar a eles no depurador do VS2013 que isso não acontece (pelo menos em nossa plataforma de desenvolvimento). Na verdade, não é tão importante, exceto se o código for crítico para o desempenho. Neste último caso, continuo por agora a não depender do RVO. (Isso não é tão difícil em C ++ ...)
Scheff
@Scheff bem Scheff (não Bose), bela explicação.
JeJo
Mesmo agora, o suporte do VS para eliminação garantida do C ++ 17 é lamentável.
Lightness Races in Orbit