Estou usando muito std::set<int>
e, frequentemente, só preciso verificar se esse conjunto contém um número ou não.
Acho natural escrever:
if (myset.contains(number))
...
Mas, devido à falta de um contains
membro, preciso escrever o incômodo:
if (myset.find(number) != myset.end())
..
ou não tão óbvio:
if (myset.count(element) > 0)
..
Existe uma razão para esta decisão de design?
count()
abordagem é que ela faz mais trabalho do quecountains()
deveria.contains()
que retorna umbool
iria perder informações valiosas sobre onde o elemento está na coleção .find()
preserva e retorna essas informações na forma de um iterador, portanto, é a melhor escolha para uma biblioteca genérica como a STL. (Isso não quer dizer que umbool contains()
não seja muito bom de se ter ou mesmo necessário.)contains(set, element)
função gratuita usando a interface pública do conjunto. Portanto, a interface do conjunto é funcionalmente completa; adicionar um método de conveniência apenas aumenta a interface sem habilitar nenhuma função adicional, o que não é o método C ++.Respostas:
Acho que provavelmente foi porque eles estavam tentando fazer
std::set
e ostd::multiset
mais parecido possível. (E, obviamente,count
tem um significado perfeitamente sensato parastd::multiset
.)Pessoalmente, acho que isso foi um erro.
Não parece tão ruim se você fingir que
count
é apenas um erro de ortografiacontains
e escrever o teste como:Ainda é uma pena.
fonte
.end()
).contains()
, com o fundamento de que seria redundante porque para qualquerstd::set<T> s
eT t
, o resultado des.contains(t)
é exatamente idêntico ao resultado destatic_cast<bool>(s.count(t))
. Visto que usar o valor em uma expressão condicional implicitamente o converteria embool
, eles podem ter achado quecount()
serviu ao propósito muito bem.if (myset.ICanHaz(element)) ...
: DPara ser capaz de escrever
if (s.contains())
,contains()
deve retornar umbool
(ou um tipo conversível parabool
, que é outra história), comobinary_search
faz.A razão fundamental por trás da decisão de design de não fazer isso dessa forma é que, se
contains()
retornasse umbool
, perderia informações valiosas sobre onde o elemento está na coleção .find()
preserva e retorna essas informações na forma de um iterador, portanto, é a melhor escolha para uma biblioteca genérica como a STL. Esse sempre foi o princípio orientador de Alex Stepanov, como ele sempre explicou (por exemplo, aqui ).Quanto à
count()
abordagem em geral, embora geralmente seja uma solução alternativa adequada, o problema é que ela funciona mais docontains()
que o necessário .Isso não quer dizer que um
bool contains()
não seja muito bom ou mesmo necessário. Há algum tempo, tivemos uma longa discussão sobre esse mesmo problema no grupo ISO C ++ Standard - Future Proposals.fonte
contains()
, obviamente, interagiria fortemente com os contêineres e algoritmos existentes, que seriam fortemente impactados por conceitos e intervalos - na época esperada para chegar ao C ++ 17 - e Eu estava convencido (como resultado da discussão e também de algumas trocas de e-mail privadas) que seria uma ideia melhor esperar por eles primeiro. Claro, em 2015 não estava claro se nem os conceitos nem as faixas não iriam para o C ++ 17 (na verdade, havia grandes esperanças de que isso acontecesse). Não tenho certeza se vale a pena perseguir agora, no entanto.std::set
(que é sobre o que a pergunta pergunta), não vejo comocount
funciona mais do quecontains
deveria. A implementação glibc decount
is (aproximadamente)return find(value) == end() ? 0 : 1;
. Além dos detalhes sobre o operador ternário vs. apenas retornar!= end()
(que eu esperaria que o otimizador removesse), não consigo ver como há mais trabalho.myset.contains()
(se existisse), isso seria uma indicação muito forte de que essa informação não é valiosa ( para o usuário nesse contexto).count()
faz mais trabalho do quecontains()
deveriastd::set
? É único, entãocount()
poderia serreturn contains(x) ? 1 : 0;
exatamente o mesmo.Falta porque ninguém o adicionou. Ninguém o adicionou porque os contêineres do STL que a
std
biblioteca incorporou foram projetados para serem mínimos na interface. (Observe questd::string
não veio do STL da mesma forma).Se você não se importa com alguma sintaxe estranha, você pode fingir:
usar:
Basicamente, você pode escrever métodos de extensão para a maioria dos C ++
std
tipos usando esta técnica.Faz muito mais sentido apenas fazer isso:
mas me divirto com o método do método de extensão.
O que é realmente triste é que escrever um eficiente
contains
pode ser mais rápido em ummultimap
oumultiset
, já que eles só precisam encontrar um elemento, enquantocount
tem que encontrar cada um deles e contá-los .Um multiset contendo 1 bilhão de cópias de 7 (você sabe, no caso de você acabar) pode ter um muito lento
.count(7)
, mas poderia ter um muito rápidocontains(7)
.Com o método de extensão acima, poderíamos torná-lo mais rápido para este caso usando
lower_bound
, comparando eend
, em seguida, comparando com o elemento. Fazer isso para um miado não ordenado, bem como um miado ordenado, exigiria SFINAE sofisticado ou sobrecargas específicas do contêiner.fonte
std::set
que não pode conter duplicatas e, portantostd::set::count
, sempre retornará0
ou1
.std::multiset::count
canbackticks
palavra "set" é porque não estou me referindostd::set
especificamente. Para fazer você se sentir melhor, adicionarei multiVocê está olhando para um caso específico e não vendo o quadro geral. Conforme declarado na documentação,
std::set
atende aos requisitos do conceito AssociativeContainer . Para esse conceito não faz sentido tercontains
método, pois é praticamente inútil parastd::multiset
estd::multimap
, mascount
funciona bem para todos eles. Embora métodocontains
pode ser adicionado como um alias paracount
parastd::set
,std::map
e suas versões hash (comolength
porsize()
emstd::string
), mas parece que os criadores da biblioteca não viu necessidade real para isso.fonte
string
é um monstro: ele existia antes da STL, onde tinhalength
e todos aqueles métodos que são baseados em índice, e então foi "contentorizado" para caber no modelo STL ... sem remover os métodos existentes por razões de compatibilidade com versões anteriores . Veja GotW # 84: Monoliths Unstrung =>string
realmente viola o princípio de design da "quantidade mínima de funções membro".contains
é igual em esforço em um conjunto / mapa, mas pode ser feito mais rápido do quecount
em um multiset / multimapa.contains
método.size()
eempty()
são duplicatas, mas muitos contêineres têm os dois.Embora eu não saiba por
std::set
que não tem,contains
mascount
que sempre retorna0
ou1
, você pode escrever umacontains
função auxiliar modelada como esta:E use-o assim:
fonte
contains
método existe, ele é apenas nomeado de maneira estúpida.std::string
tossestd.::string
NÃO faz parte do STL! Faz parte da biblioteca padrão e foi modelado retroativamente ...count
pode ser mais lento, pois efetivamente precisa fazer doisfind
s para obter o início e o fim do intervalo se o código for compartilhado commultiset
.contains
. Não vejo nada de errado nisso. @MarkRansom o pequeno SFINAE é para evitar que este template se vincule a coisas que não deveria.O verdadeiro motivo de
set
é um mistério para mim, mas uma possível explicação para esse mesmo design nomap
poderia ser evitar que as pessoas escrevam códigos ineficientes por acidente:O que resultaria em duas
map
pesquisas.Em vez disso, você é forçado a obter um iterador. Isso dá uma dica mental de que você deve reutilizar o iterador:
que consome apenas uma
map
pesquisa.Quando percebemos isso
set
emap
somos feitos da mesma carne, podemos aplicar esse princípio também paraset
. Ou seja, se quisermos atuar em um item noset
somente se ele estiver presente noset
, esse design pode nos impedir de escrever código como este:Claro que tudo isso é mera especulação.
fonte
if (myMap.count("Meaning of universe"))
muito bem, então ...?contains
comcount
.Desde c ++ 20,
bool contains( const Key& key ) const
está disponível.
fonte
E sobre binary_search?
fonte
std::unordered_set
, mas funcionariastd::set
.contains () deve retornar um bool. Usando o compilador C ++ 20, obtenho a seguinte saída para o código:
fonte
Outra razão é que isso daria a um programador a falsa impressão de que std :: set é um conjunto no sentido da teoria matemática dos conjuntos. Se eles implementassem isso, então muitas outras questões se seguiriam: se um std :: set tem contains () para um valor, por que não tem para outro conjunto? Onde estão union (), intersection () e outras operações de conjunto e predicados?
A resposta é, obviamente, que algumas das operações de conjunto já estão implementadas como funções em (std :: set_union () etc.) e outras são tão trivialmente implementadas como contains (). Funções e objetos de função funcionam melhor com abstrações matemáticas do que membros de objeto e não estão limitados ao tipo de contêiner específico.
Se alguém precisa implementar uma funcionalidade de conjunto matemático completo, ele não tem apenas uma escolha de contêiner subjacente, mas também uma escolha de detalhes de implementação, por exemplo, sua função teoria_union () funcionaria com objetos imutáveis, mais adequados para programação funcional , ou modificaria seus operandos e economizaria memória? Seria implementado como objeto de função desde o início ou seria melhor implementar uma função C e usar std :: function <> se necessário?
Como está agora, std :: set é apenas um contêiner, bem adequado para a implementação de set no sentido matemático, mas está quase tão longe de ser um conjunto teórico quanto std :: vector de ser um vetor teórico.
fonte