Come verificare che un elemento sia in uno std :: set?


329

Come si controlla che un elemento si trovi in ​​un set?

Esiste un equivalente più semplice del seguente codice:

myset.find(x) != myset.end()

4
L'unico modo per diventare più semplice di quello sarebbe un predicato booleano: template <typename T> bool member (T const & item). E questo sarebbe implementato (sotto le coperte) in termini di linea di cui stai chiedendo.
Don Wakefield,

Risposte:


399

Il modo tipico per verificare l'esistenza in molti contenitori STL, come std::map, std::set... è:

const bool is_in = container.find(element) != container.end();

25
questo è specifico per insiemi e mappe. vettori, elenchi ecc. non hanno una funzione di ricerca membro.
Wilhelmtell,

8
L'IMO usando count () è migliore perché è semplicemente più breve e converte in bool come notato nella risposta di Pieter. Non capisco perché questa risposta sia stata accettata e così tanti punti ...
Огњен Шобајић

4
Per ragioni di completezza: vettori / liste possono utilizzare std :: trovano: std::find(container.begin(), container.end(), element) != container.end(); Il problema O (N) rimane, ovviamente ...
Aconcagua,

10
@MichaelMathews Con la tua variante: if(container.find(foo) == container.end())devi prima cercare un albero per trovare l'elemento - se non lo trovi, allora devi fare una seconda ricerca ad albero per trovare la posizione di inserimento corretta. La variante originale if(container.insert(foo).second) {...}ha il fascino di aver bisogno di una sola ricerca di alberi ...
Aconcagua,

23
c'è un valore set.contains(x)che restituisce un valore booleano nello standard C ++ 20. Non so perché ci sia
voluto

215

Un altro modo per dire semplicemente se esiste un elemento è controllare count()

if (myset.count(x)) {
   // x is in the set, count is 1
} else {
   // count zero, i.e. x not in the set
}

La maggior parte delle volte, tuttavia, mi ritrovo a dover accedere all'elemento ovunque controlli la sua esistenza.

Quindi dovrei trovare l'iteratore comunque. Quindi, ovviamente, è meglio confrontarlo semplicemente con quello end.

set< X >::iterator it = myset.find(x);
if (it != myset.end()) {
   // do something with *it
}

C ++ 20

In C ++ 20 set ottiene una containsfunzione, quindi diventa possibile come indicato su: https://stackoverflow.com/a/54197839/895245

if (myset.contains(x)) {
  // x is in the set
} else {
  // no x 
}

102
Si noti che l'utilizzo al count()posto di find()non è mai migliore ma potenzialmente peggiore. Questo perché find()tornerà dopo la prima corrispondenza, count()ripeterà sempre su tutti gli elementi.
Frerich Raabe,

34
@Frerich è rilevante solo per multisete multimapho pensato? Ancora buono da sottolineare però :)
Pieter

83
std :: set è in genere implementato con una struttura ad albero ordinata, quindi count () e find () avranno entrambi O (logn). Né itererà su tutti gli elementi nel set.
Alan,

14
@FrerichRaabe - Sei sicuro? Dato che è possibile setcontenere solo un membro corrispondente, la funzione non sarebbe implementata in modo tale da arrestarsi dopo aver localizzato il primo elemento, in questo caso, come sottolinea Pieter? Risposta utile in ogni caso!
Dan Nissenbaum,

14
@DanNissenbaum Sì, hai ragione (e lo sono anche + Peter e + Alan): per std :: set, le due funzioni sono equivalenti in termini di prestazioni. Quindi, anche se la prima parte del mio commento ( count()non essendo mai più veloce di find()) è ancora valida, la seconda parte non è effettivamente applicabile std::set. Tuttavia, immagino che si possa fare un altro argomento a favore find(): è più espressivo, cioè sottolinea che stai cercando di trovare un elemento invece di contare il numero di occorrenze.
Frerich Raabe,

42

Giusto per chiarire, il motivo per cui non esiste un membro come contains()in questi tipi di contenitori è perché ti aprirebbe alla scrittura di codice inefficiente. Un tale metodo probabilmente farebbe semplicemente un this->find(key) != this->end()internamente, ma considera ciò che fai quando la chiave è effettivamente presente; nella maggior parte dei casi vorrai quindi ottenere l'elemento e fare qualcosa con esso. Ciò significa che dovresti fare un secondo find(), il che è inefficiente. È meglio utilizzare trova direttamente, quindi puoi memorizzare nella cache i risultati, in questo modo:

auto it = myContainer.find(key);
if (it != myContainer.end())
{
    // Do something with it, no more lookup needed.
}
else
{
    // Key was not present.
}

Ovviamente, se non ti interessa l'efficienza, puoi sempre creare il tuo, ma in quel caso probabilmente non dovresti usare C ++ ...;)


44
E i set? Di solito hai già l'elemento, ma vuoi solo controllare se è dentro.
Elazar Leibovich

8
Hai qualche riferimento al fatto se questa è la vera ragione per cui un tale metodo / funzione non è incluso nello stl, o è solo la tua ipotesi istruita?
Fabio A.

3
@FabioA. È la mia ipotesi colta.
Tim

1
@Adhemar, la coerenza non è esattamente il lato forte di STL ... ( list::remove, remove(makes_sense_only_for_vector, iterators)...)
Elazar Leibovich

3
Non ha senso per me non includere una funzione perché qualcuno potrebbe usarla in modo errato se non sapesse cosa stavano facendo. La programmazione è per le persone che possono pensare da sole e sono responsabili del loro codice e delle sue prestazioni
slawekwin,

13

In C ++ 20 finalmente avremo il std::set::containsmetodo.

#include <iostream>
#include <string>
#include <set>

int main()
{
    std::set<std::string> example = {"Do", "not", "panic", "!!!"};

    if(example.contains("panic")) {
        std::cout << "Found\n";
    } else {
        std::cout << "Not found\n";
    }
}

6

Se avessi intenzione di aggiungere una containsfunzione, potrebbe apparire così:

#include <algorithm>
#include <iterator>

template<class TInputIterator, class T> inline
bool contains(TInputIterator first, TInputIterator last, const T& value)
{
    return std::find(first, last, value) != last;
}

template<class TContainer, class T> inline
bool contains(const TContainer& container, const T& value)
{
    // This works with more containers but requires std::begin and std::end
    // from C++0x, which you can get either:
    //  1. By using a C++0x compiler or
    //  2. Including the utility functions below.
    return contains(std::begin(container), std::end(container), value);

    // This works pre-C++0x (and without the utility functions below, but doesn't
    // work for fixed-length arrays.
    //return contains(container.begin(), container.end(), value);
}

template<class T> inline
bool contains(const std::set<T>& container, const T& value)
{
    return container.find(value) != container.end();
}

Funziona con std::setaltri contenitori STL e persino array a lunghezza fissa:

void test()
{
    std::set<int> set;
    set.insert(1);
    set.insert(4);
    assert(!contains(set, 3));

    int set2[] = { 1, 2, 3 };
    assert(contains(set2, 3));
}

Modificare:

Come sottolineato nei commenti, ho involontariamente usato una funzione nuova in C ++ 0x ( std::begine std::end). Ecco l'implementazione quasi banale di VS2010:

namespace std {

template<class _Container> inline
    typename _Container::iterator begin(_Container& _Cont)
    { // get beginning of sequence
    return (_Cont.begin());
    }

template<class _Container> inline
    typename _Container::const_iterator begin(const _Container& _Cont)
    { // get beginning of sequence
    return (_Cont.begin());
    }

template<class _Container> inline
    typename _Container::iterator end(_Container& _Cont)
    { // get end of sequence
    return (_Cont.end());
    }

template<class _Container> inline
    typename _Container::const_iterator end(const _Container& _Cont)
    { // get end of sequence
    return (_Cont.end());
    }

template<class _Ty,
    size_t _Size> inline
    _Ty *begin(_Ty (&_Array)[_Size])
    { // get beginning of array
    return (&_Array[0]);
    }

template<class _Ty,
    size_t _Size> inline
    _Ty *end(_Ty (&_Array)[_Size])
    { // get end of array
    return (&_Array[0] + _Size);
    }

}

1
@Adhemar, in realtà era inefficiente, ma per niente per il motivo che hai citato.
Sam Harwell,

@Paul: assicurati di includere la specializzazione per std::sete ricorda che è appropriato solo se l' unica cosa che devi sapere è l'esistenza.
Sam Harwell,

@ 280Z28: std :: begin (container)? Qual è lo standard STL? Non si compila sul mio gcc.
stefaanv,

@stefannv: heh, è ​​nuovo per C ++ 0x. Ho aggiunto l'implementazione dal mio compilatore sopra.
Sam Harwell,

2
@Adhemar: se sai che il set contiene un valore, allora sei già il valore. L'unico motivo per cui avresti bisogno dell'iteratore è cancellare l'elemento dall'insieme. Se tutto ciò che serve è sapere se una raccolta contiene o meno un valore, questa soluzione non è meno efficiente di qualsiasi altra soluzione.
Sam Harwell,

4

È inoltre possibile verificare se un elemento è impostato o meno durante l'inserimento dell'elemento. La versione a singolo elemento restituisce una coppia, con la sua coppia membro :: prima impostata su un iteratore che punta all'elemento appena inserito o all'elemento equivalente già presente nel set. L'elemento pair :: second nella coppia è impostato su true se è stato inserito un nuovo elemento o false se esiste già un elemento equivalente.

Ad esempio: supponiamo che l'insieme abbia già 20 come elemento.

 std::set<int> myset;
 std::set<int>::iterator it;
 std::pair<std::set<int>::iterator,bool> ret;

 ret=myset.insert(20);
 if(ret.second==false)
 {
     //do nothing

 }
 else
 {
    //do something
 }

 it=ret.first //points to element 20 already in set.

Se l'elemento è stato appena inserito rispetto a pair :: first punterà alla posizione del nuovo elemento nel set.


2

Scrivi il tuo:

template<class T>
bool checkElementIsInSet(const T& elem, const std::set<T>& container)
{
  return container.find(elem) != container.end();
}

4
appena fatto: template <class T> static inline bool contiene (const std :: set <T> & S, T x) {return (S.find (x)! = S.end ()); }
fulmicoton

4
@paul non crea funzioni globali statiche. metti invece la tua funzione in uno spazio dei nomi anonimo: questo è il modo C ++ di creare funzioni che non si collegheranno ad altre unità di compilazione. inoltre, il parametro T deve essere un riferimento const, per correttezza const ed efficienza.
Wilhelmtell,

-1: non modellato e per niente in stile STL. Questo va bene se non stai usando STL, ma se stai usando STL dovresti almeno provare a seguire i suoi standard.
Sam Harwell,

1
@ 280Z28: Mi dispiace che il mio codice non sia conforme ai tuoi standard, stavo solo dimostrando che se non ti piace l'interfaccia di STL, puoi scriverne uno tuo. Cavolo, non hai un modello? Quanto deve essere modellato? Il tuo esempio sembra a posto, ciò non significa che il mio sia cattivo. È solo più focalizzato sul set, come è stato chiesto dall'OP.
stefaanv,

1
@ 280Z28: stavo solo facendo un punto. Ho pensato che le persone sarebbero state abbastanza intelligenti per ottenere l'immagine.
stefaanv,

2

Io uso

if(!my_set.count(that_element)) //Element is present...
;

Ma non è così efficiente

if(my_set.find(that_element)!=my_set.end()) ....;

La mia versione mi fa solo risparmiare tempo nello scrivere il codice. Lo preferisco così per la codifica competitiva.


Sì, count(). Chiunque non sia in grado di capire che una funzione di ritorno di numeri interi utilizzata in un'espressione booleana sta testando un valore diverso da zero avrà molti, molti altri travagli nel grande mare di idiomi C / C ++. E, come notato sopra, dovrebbe essere davvero efficiente per i set, che era la domanda.
Ron Burk,

0

Sono stato in grado di scrivere una containsfunzione generale per std::liste std::vector,

template<typename T>
bool contains( const list<T>& container, const T& elt )
{
  return find( container.begin(), container.end(), elt ) != container.end() ;
}

template<typename T>
bool contains( const vector<T>& container, const T& elt )
{
  return find( container.begin(), container.end(), elt ) != container.end() ;
}

// use:
if( contains( yourList, itemInList ) ) // then do something

Questo pulisce un po 'la sintassi.

Ma non ho potuto usare la magia dei parametri del modello di modello per rendere questo lavoro arbitrario contenitori stl.

// NOT WORKING:
template<template<class> class STLContainer, class T>
bool contains( STLContainer<T> container, T elt )
{
  return find( container.begin(), container.end(), elt ) != container.end() ;
}

Qualsiasi commento sul miglioramento dell'ultima risposta sarebbe utile.


Mi dispiace non posso davvero scrivere il codice di blocco nei commenti, ma che dire template<typename CONTAINER, typename CONTAINEE> bool contains(const CONTAINER& container, const CONTAINEE& needle) { return find(container.begin(), container.end(), needle) != container.end();
fulmicoton

Non funziona, perché std :: vector ha bisogno di un allocatore aggiuntivo come argomento template e std :: set necessita di un allocatore e meno argomento template. Queste righe funzioneranno: template <template <classe, classe> classe STLContainer, classe T, classe A> bool contiene (STLContainer <T, A> container, T elt) {return find (container.begin (), container.end ( ), elt)! = container.end (); } template <template <classe, classe, classe> classe STLContainer, classe T, classe L, classe A> bool contiene (STLContainer <T, A, L> container, T elt) {return find (container.begin (), container .end (), elt)! = container.end (); }
tgmath,

0

// Sintassi generale

       set<int>::iterator ii = find(set1.begin(),set1.end(),"element to be searched");

/ * nel codice seguente sto cercando di trovare l'elemento 4 in e int impostato se è presente o meno * /

set<int>::iterator ii = find(set1.begin(),set1.end(),4);
 if(ii!=set1.end())
 {
    cout<<"element found";
    set1.erase(ii);// in case you want to erase that element from set.
 }
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.