Comment vérifier qu'un élément est dans un std :: set?

329

Comment vérifiez-vous qu'un élément est dans un ensemble?

Existe-t-il un équivalent plus simple du code suivant:

myset.find(x) != myset.end()
fulmicoton
la source
4
Le seul moyen de devenir plus simple que cela serait un prédicat booléen: template <typename T> bool member (T const & item). Et cela serait mis en œuvre (sous les couvertures) en fonction de la ligne que vous demandez.
Don Wakefield

Réponses:

399

La façon typique de vérifier l' existence dans de nombreux conteneurs STL tels que std::map, std::set... est la suivante :

const bool is_in = container.find(element) != container.end();
se détendre
la source
25
ceci est spécifique aux ensembles et aux cartes. les vecteurs, listes, etc. n'ont pas de fonction membre de recherche.
wilhelmtell
8
IMO utilisant count () est meilleur car il est tout simplement plus court et il se transforme en bool comme indiqué dans la réponse de Pieter. Je ne comprends pas pourquoi cette réponse a été acceptée et autant de points ...
Огњен Шобајић
4
Par souci d'exhaustivité: vecteurs / listes peuvent utiliser std :: trouver: std::find(container.begin(), container.end(), element) != container.end(); Le problème O (N) demeure, bien sûr ...
Aconcagua
10
@MichaelMathews Avec votre variante: if(container.find(foo) == container.end())doit faire une recherche d'arbre pour trouver l'élément en premier - s'il n'est pas trouvé, alors vous devez faire une deuxième recherche d'arbre pour trouver le bon emplacement d'insertion. La variante originale if(container.insert(foo).second) {...}a le charme de ne nécessiter qu'une seule recherche d'arbre ...
Aconcagua
23
il y a un set.contains(x)qui renvoie un booléen dans la norme C ++ 20. Je ne sais pas pourquoi il nous a fallu jusqu'en 2020 pour que cela entre.
gremwell
215

Une autre façon de simplement dire si un élément existe est de vérifier la count()

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

La plupart du temps, cependant, je me retrouve à avoir besoin d'accéder à l'élément partout où je vérifie son existence.

Il faudrait donc que je trouve l'itérateur de toute façon. Alors, bien sûr, il vaut mieux simplement le comparer endaussi.

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

C ++ 20

En C ++ 20, set obtient une containsfonction, donc ce qui suit devient possible comme mentionné sur: https://stackoverflow.com/a/54197839/895245

if (myset.contains(x)) {
  // x is in the set
} else {
  // no x 
}
Pieter
la source
102
Notez qu'utiliser count()au lieu de find()n'est jamais meilleur mais potentiellement pire. C'est parce find()que reviendra après le premier match, count()itérera toujours sur tous les éléments.
Frerich Raabe
34
@Frerich qui n'est pertinent que pour multisetet multimapje pensais? C'est quand même bon de le souligner :)
Pieter
83
std :: set est généralement implémenté avec une structure arborescente ordonnée, donc count () et find () auront tous les deux O (logn). Ni itérera sur tous les éléments de l'ensemble.
Alan
14
@FrerichRaabe - Êtes-vous certain? Puisqu'il n'est possible que setde contenir un membre qui correspond, la fonction ne serait-elle pas implémentée de manière à s'arrêter après avoir localisé le premier élément, dans ce cas, comme le souligne Pieter? Réponse utile dans tous les cas!
Dan Nissenbaum
14
@DanNissenbaum Oui, vous avez parfaitement raison (tout comme + Peter et + Alan): pour std :: set, les deux fonctions sont équivalentes en termes de performances. Donc, même si la première partie de mon commentaire (qui count()n'est jamais plus rapide que find()) tient toujours, la deuxième partie n'est en effet pas applicable à std::set. Cependant, je suppose qu'un autre argument peut être avancé en faveur de find(): il est plus expressif, c'est-à-dire qu'il souligne que vous essayez de trouver un élément au lieu de compter le nombre d'occurrences.
Frerich Raabe
42

Juste pour clarifier, la raison pour laquelle il n'y a pas de membre comme contains()dans ces types de conteneurs est parce que cela vous ouvrirait à l'écriture de code inefficace. Une telle méthode ne ferait probablement qu'une opération this->find(key) != this->end()interne, mais réfléchissez à ce que vous faites lorsque la clé est effectivement présente; dans la plupart des cas, vous voudrez alors récupérer l'élément et en faire quelque chose. Cela signifie que vous devez en faire une seconde find(), ce qui est inefficace. Il est préférable d'utiliser directement la recherche, afin de pouvoir mettre en cache votre résultat, comme ceci:

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

Bien sûr, si vous ne vous souciez pas de l'efficacité, vous pouvez toujours lancer le vôtre, mais dans ce cas, vous ne devriez probablement pas utiliser C ++ ...;)

Tim
la source
44
Et les décors? Habituellement, vous avez déjà l'élément, mais vous voulez juste vérifier s'il est
présent
8
Avez-vous une référence à savoir si c'est la raison réelle pour laquelle une telle méthode / fonction n'est pas incluse dans la stl, ou est-ce simplement votre supposition éclairée?
Fabio A.
3
@FabioA. C'est ma supposition éclairée.
Tim
1
@Adhemar, la cohérence n'est pas exactement le côté fort de la STL ... ( list::remove, remove(makes_sense_only_for_vector, iterators)...)
Elazar Leibovich
3
Cela n'a pas de sens pour moi de ne pas inclure de fonctionnalité car quelqu'un pourrait l'utiliser de manière incorrecte s'il ne savait pas ce qu'il faisait. La programmation s'adresse à des gens qui peuvent penser par eux-mêmes et qui sont responsables de leur code et de ses performances
slawekwin
13

En C ++ 20, nous aurons enfin la std::set::containsméthode.

#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";
    }
}
Denis Sablukov
la source
6

Si vous deviez ajouter une containsfonction, cela pourrait ressembler à ceci:

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

Cela fonctionne avec d' std::setautres conteneurs STL et même des tableaux de longueur fixe:

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

Éditer:

Comme indiqué dans les commentaires, j'ai involontairement utilisé une fonction nouvelle pour C ++ 0x ( std::beginet std::end). Voici l'implémentation quasi-triviale de 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);
    }

}
Sam Harwell
la source
1
@Adhemar, c'était en fait inefficace, mais pas du tout pour la raison que vous avez mentionnée.
Sam Harwell
@Paul: Assurez-vous d'inclure la spécialisation pour std::set, et rappelez-vous que ce n'est approprié que si la seule chose que vous devez savoir est l'existence.
Sam Harwell,
@ 280Z28: std :: begin (conteneur)? De quelle norme STL s'agit-il? Il ne compile pas sur mon gcc.
Stefaanv
@stefannv: heh, c'est nouveau pour C ++ 0x. J'ai ajouté l'implémentation de mon compilateur ci-dessus.
Sam Harwell
2
@Adhemar: Si vous savez que l'ensemble contient une valeur, alors vous avez déjà la valeur. La seule raison pour laquelle vous auriez besoin de l'itérateur est d'effacer l'élément de l'ensemble. Si tout ce dont vous avez besoin est de savoir si une collection contient ou non une valeur, cette solution n'est pas moins efficace que toute autre solution.
Sam Harwell
4

Vous pouvez également vérifier si un élément est dans l'ensemble ou non lors de l'insertion de l'élément. La version à élément unique renvoie une paire, avec sa paire de membres :: first définie sur un itérateur pointant vers l'élément nouvellement inséré ou vers l'élément équivalent déjà dans l'ensemble. L'élément pair :: second de la paire est défini sur true si un nouvel élément a été inséré ou sur false si un élément équivalent existait déjà.

Par exemple: Supposons que l'ensemble a déjà 20 comme élément.

 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.

Si l'élément est nouvellement inséré, pair :: first pointera vers la position du nouvel élément dans set.

Prashant Shubham
la source
2

Écrivez votre propre:

template<class T>
bool checkElementIsInSet(const T& elem, const std::set<T>& container)
{
  return container.find(elem) != container.end();
}
stefaanv
la source
4
vient de le faire: le modèle <classe T> booléen statique en ligne contient (const std :: set <T> & S, T x) {return (S.find (x)! = S.end ()); }
fulmicoton
4
@paul ne crée pas de fonctions globales statiques. placez plutôt votre fonction dans un espace de noms anonyme: c'est la façon C ++ de créer des fonctions qui ne seront pas liées à d'autres unités de compilation. aussi, votre paramètre T doit être une référence const, pour la const-correct et pour l'efficacité.
wilhelmtell
-1: Pas de modèle et pas du tout dans le style STL. C'est très bien si vous n'utilisez pas STL, mais si vous utilisez STL, vous devez au moins essayer de suivre ses normes.
Sam Harwell
1
@ 280Z28: Je suis désolé que mon code ne soit pas conforme à vos normes, je montrais juste que si vous n'aimez pas l'interface de STL, vous pouvez écrire la vôtre. Bon sang, pas de modèle? À quel point doit-elle être modelée? Votre exemple semble bien, cela ne signifie pas que le mien est mauvais. Il est tout simplement plus axé sur l'ensemble comme l'a demandé l'OP.
Stefaanv
1
@ 280Z28: Je faisais juste un point. Je pensais que les gens seraient assez intelligents pour obtenir l'image.
Stefaanv
2

j'utilise

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

Mais ce n'est pas aussi efficace que

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

Ma version ne fait que gagner du temps en écrivant le code. Je le préfère de cette façon pour un codage compétitif.

Manas Bondale
la source
Oui count(). Toute personne incapable de comprendre qu'une fonction de retour d'entier utilisée dans une expression booléenne teste une valeur non nulle va avoir de très nombreux autres échecs dans la grande mer des idiomes C / C ++. Et, comme indiqué ci-dessus, devrait vraiment être aussi efficace pour les ensembles, ce qui était la question.
Ron Burk
0

J'ai pu écrire une containsfonction générale pour std::listet 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

Cela nettoie un peu la syntaxe.

Mais je ne pouvais pas utiliser la magie des paramètres de modèle de modèle pour faire fonctionner les conteneurs stl arbitraires.

// 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() ;
}

Tout commentaire sur l'amélioration de la dernière réponse serait bien.

bobobobo
la source
Désolé, je ne peux pas vraiment écrire de code de bloc dans les commentaires, mais qu'en est-il template<typename CONTAINER, typename CONTAINEE> bool contains(const CONTAINER& container, const CONTAINEE& needle) { return find(container.begin(), container.end(), needle) != container.end();
fulmicoton
Cela ne fonctionne pas, car std :: vector a besoin d'un allocateur supplémentaire comme argument de modèle et std :: set a besoin d'un allocateur et d'un argument de modèle moins. Ces lignes fonctionnent: template <template <class <class, class> class STLContainer, class T, class A> bool contains (STLContainer <T, A> container, T elt) {return find (container.begin (), container.end ( ), elt)! = container.end (); } template <template <class, class, class> class STLContainer, class T, class L, class A> bool contains (STLContainer <T, A, L> container, T elt) {return find (container.begin (), container .end (), elt)! = container.end (); }
tgmath
0

// Syntaxe générale

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

/ * dans le code ci-dessous, j'essaie de trouver l'élément 4 dans et int défini s'il est présent ou non * /

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.
 }
sanjeev
la source