Como verificar se um elemento está em um std :: set?

329

Como você verifica se um elemento está em um conjunto?

Existe um equivalente mais simples do seguinte código:

myset.find(x) != myset.end()
fulmicoton
fonte
4
A única maneira de simplificar isso seria um predicado booleano: template <typename T> membro bool (T const & item). E isso seria implementado (nos bastidores) em termos da linha que você está perguntando.
9119 Don Wakefield

Respostas:

399

A forma habitual para verificar se há existência em muitos contêineres STL, como std::map, std::set... é:

const bool is_in = container.find(element) != container.end();
descontrair
fonte
25
isso é específico para conjuntos e mapas. vetores, listas etc. não têm uma função de membro find.
11139 wilhelmtell
8
O IMO usando count () é melhor porque é simplesmente mais curto e converte em bool, conforme observado na resposta de Pieter. Eu não entendo pontos porque esta resposta foi aceito e tantos ...
Огњен Шобајић
4
Por uma questão de exaustividade: vetores / listas pode usar std :: encontrar: std::find(container.begin(), container.end(), element) != container.end(); O (N) problema permanece, é claro ...
Aconcagua
10
@MichaelMathews Com a sua variante: é if(container.find(foo) == container.end())necessário fazer uma pesquisa em árvore para encontrar o elemento primeiro - se não for encontrado, você precisará fazer uma segunda pesquisa em árvore para encontrar o local de inserção correto. A variante originais if(container.insert(foo).second) {...}tem o charme que ele precisa de apenas uma única pesquisa árvore ...
Aconcagua
23
existe um set.contains(x)que retorna um bool no padrão C ++ 20. Eu não sei por que ele nos levou até 2020 para conseguir que no.
gremwell
215

Outra maneira de simplesmente dizer se existe um elemento é verificar o count()

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

Na maioria das vezes, no entanto, me vejo precisando de acesso ao elemento sempre que verifico sua existência.

Então, eu teria que encontrar o iterador de qualquer maneira. Então, é claro, é melhor simplesmente compará-lo endtambém.

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

C ++ 20

No conjunto C ++ 20, obtém uma containsfunção, portanto, o seguinte se torna possível conforme mencionado em: https://stackoverflow.com/a/54197839/895245

if (myset.contains(x)) {
  // x is in the set
} else {
  // no x 
}
Pieter
fonte
102
Observe que usar em count()vez de find()nunca é melhor, mas potencialmente pior. Isso ocorre porque find()retornará após a primeira partida, count()sempre iterará sobre todos os elementos.
Frerich Raabe
34
@ Frichich isso é relevante apenas para multisete multimapeu pensei? Ainda bom ressaltar embora :)
Pieter
83
O std :: set normalmente é implementado com uma estrutura em árvore ordenada, portanto, count () e find () terão O (logn). Nem iterará sobre todos os elementos no conjunto.
Alan
14
@FrerichRaabe - Você tem certeza? Como só é possível setconter um membro que corresponda, a função não seria implementada de forma a parar após localizar o primeiro elemento, neste caso, como Pieter aponta? Resposta útil em qualquer caso!
Dan Nissenbaum 27/03
14
@ DanNissenbaum Sim, você está exatamente certo (assim como + Peter e + Alan): para std :: set, as duas funções são equivalentes em termos de desempenho. Portanto, mesmo que a primeira parte do meu comentário ( count()nunca sendo mais rápida que find()) ainda seja válida, a segunda parte não é de fato aplicável a std::set. No entanto, acho que outro argumento pode ser feito a favor de find(): é mais expressivo, ou seja, enfatiza que você está tentando encontrar um elemento em vez de contar o número de ocorrências.
Frerich Raabe 27/03
42

Apenas para esclarecer, a razão pela qual não há membros como contains()esses tipos de contêineres é porque isso abriria você para escrever código ineficiente. Esse método provavelmente faria apenas this->find(key) != this->end()internamente, mas considere o que você faz quando a chave está realmente presente; na maioria dos casos, você deseja obter o elemento e fazer algo com ele. Isso significa que você teria que fazer um segundo find(), o que é ineficiente. É melhor usar o find diretamente, para que você possa armazenar em cache o resultado da seguinte maneira:

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

Obviamente, se você não se importa com eficiência, sempre pode fazer o seu próprio, mas nesse caso você provavelmente não deveria estar usando C ++ ...;)

Tim
fonte
44
E os sets? Normalmente, você já tem o elemento, mas só quero verificar se ele está dentro.
Elazar Leibovich
8
Você tem alguma referência para saber se esse é o motivo real desse método / função não estar incluído no stl ou se é apenas um palpite?
Fabio A.
3
@FabioA. É o meu palpite.
Tim
1
@Adhemar, a consistência não é exatamente do lado forte da STL ... ( list::remove, remove(makes_sense_only_for_vector, iterators)...)
Elazar Leibovich
3
Não faz sentido não incluir um recurso, porque alguém pode usá-lo incorretamente se não souber o que está fazendo. A programação é para as pessoas que podem pensar por si mesmos e que são responsáveis pelo seu código e seu desempenho
slawekwin
13

No C ++ 20 , finalmente obteremos o std::set::containsmétodo.

#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
fonte
6

Se você fosse adicionar uma containsfunção, ela poderia ser assim:

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

Isso funciona com std::setoutros contêineres STL e até matrizes de comprimento fixo:

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

Editar:

Conforme apontado nos comentários, usei involuntariamente uma função nova para C ++ 0x ( std::begine std::end). Aqui está a implementação quase trivial do 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
fonte
1
@ Adhemar, na verdade era ineficiente, mas não pelo motivo que você mencionou.
Sam Harwell
@ Paul: Certifique-se de incluir a especialização std::sete lembre-se de que isso é apropriado apenas se a única coisa que você precisa saber é a existência.
Sam Harwell
@ 280Z28: std :: begin (contêiner)? Que padrão STL é esse? Não compila no meu gcc.
stefaanv
@stefannv: heh, é novo para C ++ 0x. Eu adicionei a implementação do meu compilador acima.
Sam Harwell
2
@ Adhemar: Se você sabe que o conjunto contém um valor, então você já o valor. O único motivo para você precisar do iterador é apagar o elemento do conjunto. Se tudo o que você precisa é saber se uma coleção contém ou não um valor, essa solução não é menos eficiente do que qualquer outra solução.
Sam Harwell
4

Você também pode verificar se um elemento está definido ou não durante a inserção do elemento. A versão de elemento único retorna um par, com seu par de membros :: first definido para um iterador apontando para o elemento recém-inserido ou para o elemento equivalente já existente no conjunto. O elemento pair :: second no par é definido como true se um novo elemento foi inserido ou false se um elemento equivalente já existir.

Por exemplo: Suponha que o conjunto já tenha 20 como um 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 o elemento for inserido recentemente, o pair :: first apontará para a posição do novo elemento no conjunto.

Prashant Shubham
fonte
2

Escreva o seu próprio:

template<class T>
bool checkElementIsInSet(const T& elem, const std::set<T>& container)
{
  return container.find(elem) != container.end();
}
stefaanv
fonte
4
apenas o fiz: template <classe T> estático inline estático contém (const std :: set <T> & S, Tx) {return (S.find (x)! = S.end ()); }
fulmicoton
4
@paul não cria funções globais estáticas. coloque sua função em um espaço para nome anônimo: essa é a maneira C ++ de criar funções que não serão vinculadas a outras unidades de compilação. Além disso, seu parâmetro T deve ser uma referência const, para const-correção e eficiência.
11139 wilhelmtell
-1: Não templated e não em tudo em grande estilo STL. Isso é bom se você não estiver usando STL, mas se estiver usando STL, pelo menos tente seguir seus padrões.
Sam Harwell
1
@ 280Z28: Lamento que meu código não atenda aos seus padrões, eu estava apenas mostrando que se você não gosta da interface da STL, pode escrever sua própria. Eita, não templated? Como tem que ser? Seu exemplo parece bom, isso não significa que o meu seja ruim. É apenas mais focado no cenário, como foi solicitado pelo OP.
9788 stefaanv
1
@ 280Z28: Eu estava apenas fazendo um ponto. Eu pensei que as pessoas seriam inteligentes o suficiente para entender a imagem.
stefaanv
2

eu uso

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

Mas não é tão eficiente quanto

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

Minha versão economiza apenas meu tempo escrevendo o código. Eu prefiro assim para a codificação competitiva.

Manas Bondale
fonte
Sim, count(). Qualquer pessoa que não consiga entender que uma função de retorno inteiro usada em uma expressão booleana está testando diferente de zero terá muitos, muitos outros problemas no grande mar de expressões em C / C ++. E, como observado acima, realmente deve ser tão eficiente para conjuntos, que era a questão.
Ron Burk
0

Consegui escrever uma containsfunção geral para 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

Isso limpa um pouco a sintaxe.

Mas eu não poderia usar a mágica de parâmetro de modelo de modelo para fazer com que isso funcionasse com contêineres stl arbitrários.

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

Qualquer comentário sobre como melhorar a última resposta seria bom.

bobobobo
fonte
Desculpe, não posso realmente escrever código de bloco no comentário, mas que sobre template<typename CONTAINER, typename CONTAINEE> bool contains(const CONTAINER& container, const CONTAINEE& needle) { return find(container.begin(), container.end(), needle) != container.end();
fulmicoton
Não está funcionando, porque std :: vector precisa de um alocador adicional como argumento de modelo e std :: set precisa de um alocador e um argumento de menos modelo. Essas linhas funcionam: modelo <modelo <classe, classe> classe STLContainer, classe T, classe A> bool contém (STLContainer <T, A> container, T elt) {return find (container.begin (), container.end ( ), elt)! = container.end (); } modelo <modelo <classe, classe, classe> classe STLContainer, classe T, classe L, classe A> bool contém (STLContainer <T, A, L> container, T elt) {return find (container.begin (), container .end (), elt)! = container.end (); }
tgmath 9/09/13
0

// Sintaxe geral

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

/ * no código abaixo, estou tentando encontrar o elemento 4 e o int definido se ele estiver presente ou não * /

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
fonte