come trovare l'intersezione di due std :: set in C ++?


93

Ho provato a trovare l'intersezione tra due std :: set in C ++, ma continuo a ricevere un errore.

Ho creato un piccolo test di esempio per questo

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

Quest'ultimo programma non genera alcun output, ma mi aspetto di avere un nuovo set (chiamiamolo s3) con i seguenti valori:

s3 = [ 1 , 3 ]

Invece ricevo l'errore:

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

Quello che ho capito da questo errore, è che non c'è una definizione set_intersectionche accetta Rb_tree_const_iterator<int>come parametro.

Inoltre, suppongo che il std::set.begin()metodo restituisca un oggetto di questo tipo,

c'è un modo migliore per trovare l'intersezione di due std::setin C ++? Preferibilmente una funzione incorporata?

Molte grazie!


"Mi aspetto di avere un nuovo set (chiamiamolo s3)" Ma non lo fai, e non l'hai fatto. Non capisco dove ti aspettavi che andassero i risultati. Inoltre non hai letto la documentazione per scoprire quali argomenti passare.
Gare di leggerezza in orbita

Risposte:


113

Non hai fornito un iteratore di output per set_intersection

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

Risolvi il problema facendo qualcosa di simile

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

Hai bisogno di un std::insertiteratore poiché il set è ora vuoto. Non possiamo usare back_ o front_inserter poiché set non supporta queste operazioni.


71
Vorrei capire perché un'operazione così fondamentale sui set richiede un incantesimo così arcano e verboso. Perché non un set<T>& set::isect(set<T>&)metodo semplice , che fa il necessario? (Chiederei set<T>& set::operator^(set<T>&)un ponte, ma probabilmente è troppo lontano.)
Ryan V. Bissell

3
@ RyanV.Bissell questo è un design simile a quasi tutti gli algoritmi in <algorithm>modo coerente se non altro. Anche questo stile, presumo, ti dà flessibilità. E consente agli algoritmi di essere utilizzati con diversi contenitori, anche se qui potrebbe non accadere .. Inoltre la tua firma potrebbe non funzionare, probabilmente dovrai restituire un valore. E che nei giorni precedenti la semantica della copia sarebbe stata una doppia copia, credo. Non ho fatto C ++ per un po 'di tempo, quindi prendilo con un pizzico o 3 di sale
Karthik T

4
Mi considero ancora un principiante STL, quindi vale anche l'applicazione di grani di sale. La mia finestra di modifica dei commenti è scaduta, quindi non posso correggere il passo falso di ritorno per riferimento. Il mio commento non era una lamentela sulla coerenza, ma una domanda onesta sul perché questa sintassi debba necessariamente avere un sapore così amaro. Forse dovrei farne una domanda SO.
Ryan V. Bissell

3
In realtà, la maggior parte della libreria std C ++ è progettata in questo modo oscuro. Mentre l'eleganza del design è ovvia (rispetto alla genericità, ma non solo), la complessità dell'API ha effetti devastanti (soprattutto perché le persone continuano a reinventare le ruote poiché non possono usare quelle fornite con il loro compilatore). In un altro mondo, i designer sarebbero stati schiaffeggiati per aver privilegiato il loro piacere sul loro utente. In questo mondo ... beh, almeno abbiamo StackOverflow.

3
Questa è una "sintassi generale" - puoi anche eseguire set_intersection su un vettore e su un elenco e memorizzare i risultati in una deque, e dovresti essere in grado di fare anche questa cosa in modo efficiente (ovviamente, è tuo problema fare attenzione che entrambi i contenitori di origine vengono ordinati prima di chiamare this). Non lo trovo male, l'unica cosa con cui ho problemi è che potrebbe esserci anche un metodo di setcontenitore che si interseca con un altro insieme. L'argomento del passaggio di un contenitore invece di .begin()- .end()è un'altra cosa - verrà risolto una volta che C ++ avrà i concetti.
Ethouris

25

Dai un'occhiata all'esempio nel link: http://en.cppreference.com/w/cpp/algorithm/set_intersection

Hai bisogno di un altro contenitore per memorizzare i dati di intersezione, di seguito il codice supponiamo che funzioni:

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

6
back_inserternon funziona in setquanto setnon ha alcuna push_backfunzione.
Jack Aidley

6

Vedere std :: set_intersection . Devi aggiungere un iteratore di output, dove memorizzerai il risultato:

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

Vedi Ideone per l'elenco completo.


3
Nota che back_inserter non funzionerà se vuoi che anche il risultato sia un set, quindi hai bisogno di std :: inserter come Karthik usato.
Joseph Garvin,

4

Commenta qui. Penso che sia ora di aggiungere unione, operazione di intersezione all'interfaccia impostata. Proponiamo questo negli standard futuri. Uso lo std da molto tempo, ogni volta che ho utilizzato l'operazione di set desideravo che lo std fosse migliore. Per alcune complicate operazioni sugli insiemi, come intersect, puoi semplicemente (più facile?) Modificare il codice seguente:

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

copiato da http://www.cplusplus.com/reference/algorithm/set_intersection/

Ad esempio, se l'output è un set, puoi output.insert (* first1). Inoltre, la funzione potrebbe non essere modellata.Se il codice può essere più breve rispetto all'utilizzo della funzione std set_intersection, procedere con esso.

Se vuoi fare un'unione di due set puoi semplicemente setA.insert (setB.begin (), setB.end ()); Questo è molto più semplice del metodo set_union. Tuttavia, questo non funzionerà con il vettore.


4

Il primo commento (ben votato) della risposta accettata si lamenta di un operatore mancante per le operazioni esistenti sugli std set.

Da un lato, capisco la mancanza di tali operatori nella libreria standard. D'altra parte, è facile aggiungerli (per la gioia personale) se lo si desidera. Ho sovraccaricato

  • operator *() per l'intersezione di insiemi
  • operator +() per l'unione di insiemi.

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

Compilato e testato:

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

$ 

Quello che non mi piace è la copia dei valori di ritorno negli operatori. Può essere, questo potrebbe essere risolto usando l'assegnazione di mosse, ma questo va ancora oltre le mie capacità.

A causa della mia limitata conoscenza di queste "nuove fantasiose" semantiche di spostamento, ero preoccupato per i ritorni degli operatori che potrebbero causare copie dei set restituiti. Olaf Dietsche ha sottolineato che queste preoccupazioni non sono necessarie in quanto std::setè già dotato di mossa costruttore / incarico.

Sebbene gli credessi, stavo pensando a come verificarlo (per qualcosa come "auto-convincente"). In realtà è abbastanza facile. Poiché i modelli devono essere forniti nel codice sorgente, puoi semplicemente eseguire il debugger. Quindi, ho posizionato un punto di interruzione proprio alla fine return s;di operator *()e ho proceduto con un passaggio singolo che mi ha portato immediatamente in std::set::set(_myt&& _Right): et voilà - il costruttore di mosse. Grazie, Olaf, per la (mia) illuminazione.

Per ragioni di completezza, ho implementato anche gli operatori di assegnazione corrispondenti

  • operator *=() per l'intersezione "distruttiva" di insiemi
  • operator +=() per l'unione "distruttiva" di insiemi.

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

Compilato e testato:

$ 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::setimplementa già il costruttore di spostamento e l'operatore di assegnazione necessari, quindi non devi preoccuparti di questo. Anche il compilatore molto probabilmente utilizza l' ottimizzazione del valore di ritorno
Olaf Dietsche

@OlafDietsche Grazie per il tuo commento. Ho verificato e migliorato la risposta rispettivamente. Riguardo a RVO, ho già avuto alcune discussioni con i miei colleghi fino a quando ho mostrato loro nel debugger di VS2013 che non succedeva (almeno nella nostra piattaforma di sviluppo). In realtà, non è così importante tranne se il codice è critico per le prestazioni. In quest'ultimo caso, continuo a non fare affidamento su RVO. (In realtà non è così difficile in C ++ ...)
Scheff

@Scheff bene Scheff (non Bose), bella spiegazione.
JeJo

Anche ora il supporto di VS per l'elisione garantita di C ++ 17 è doloroso.
Gare di leggerezza in orbita
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.