J'utilise beaucoup std::set<int>
et je dois souvent simplement vérifier si un tel ensemble contient un nombre ou non.
Je trouverais naturel d'écrire:
if (myset.contains(number))
...
Mais à cause du manque de contains
membre, j'ai besoin d'écrire la lourde:
if (myset.find(number) != myset.end())
..
ou le pas aussi évident:
if (myset.count(element) > 0)
..
Y a-t-il une raison à cette décision de conception?
count()
approche est qu'elle fait plus de travail que cecountains()
qu'elle aurait à faire.contains()
qui retourne unbool
serait perdre de précieuses informations sur l' endroit où l'élément est dans la collection .find()
préserve et renvoie ces informations sous la forme d'un itérateur, c'est donc un meilleur choix pour une bibliothèque générique comme STL. (Cela nebool contains()
veut pas dire que ce n'est pas très agréable ou même nécessaire.)contains(set, element)
fonction gratuite en utilisant l'interface publique de l'ensemble. Par conséquent, l'interface de l'ensemble est fonctionnellement complète; l'ajout d'une méthode pratique augmente simplement l'interface sans activer de fonction supplémentaire, ce qui n'est pas la méthode C ++.Réponses:
Je pense que c'était probablement parce qu'ils essayaient de faire
std::set
etstd::multiset
aussi similaire que possible. (Et a évidemmentcount
une signification parfaitement sensée pourstd::multiset
.)Personnellement, je pense que c'était une erreur.
Cela n'a pas l'air si mal si vous prétendez que
count
c'est juste une faute d'orthographecontains
et écrivez le test comme:C'est quand même dommage.
la source
.end()
).contains()
, au motif qu'il serait redondant car pour toutstd::set<T> s
etT t
, le résultat des.contains(t)
est exactement identique au résultat destatic_cast<bool>(s.count(t))
. Comme l'utilisation de la valeur dans une expression conditionnelle la convertirait implicitement enbool
, ils ont peut-être estimé que celacount()
servait assez bien l'objectif.if (myset.ICanHaz(element)) ...
: DPour pouvoir écrire
if (s.contains())
,contains()
il faut retourner unbool
(ou un type convertible enbool
, qui est une autre histoire), comme lebinary_search
fait.La raison fondamentale derrière la décision de conception de ne pas le faire de cette façon est que
contains()
qui retourne unbool
serait perdre de précieuses informations sur l' endroit où l'élément est dans la collection .find()
préserve et renvoie ces informations sous la forme d'un itérateur, c'est donc un meilleur choix pour une bibliothèque générique comme STL. Cela a toujours été le principe directeur d'Alex Stepanov, comme il l'a souvent expliqué (par exemple ici ).Quant à la
count()
approche en général, bien que ce soit souvent une solution de contournement correcte, le problème est qu'elle fait plus de travail que cecontains()
qu'elle aurait à faire .Cela ne veut pas dire qu'un
bool contains()
n'est pas très agréable à avoir ou même nécessaire. Il y a quelque temps, nous avons eu une longue discussion sur cette même question dans le groupe Norme ISO C ++ - Propositions futures.la source
contains()
, de toute évidence, interagirait fortement avec les conteneurs et les algorithmes existants, ce qui serait fortement impacté par les concepts et les plages - au moment prévu pour entrer dans C ++ 17 - et J'étais convaincu (à la suite de la discussion et de quelques échanges de courriels privés) que c'était une meilleure idée de les attendre en premier. Bien sûr, en 2015, il n'était pas clair que ni les concepts ni les plages ne parviendraient à C ++ 17 (en fait, il y avait de grands espoirs). Je ne suis pas sûr que cela vaille la peine de le poursuivre maintenant.std::set
(c'est ce que la question pose), je ne vois pas commentcount
ça marche plus qu'il n'encontains
faudrait. L'implémentation glibc decount
est (en gros)return find(value) == end() ? 0 : 1;
. Mis à part les détails sur l'opérateur ternaire par rapport au retour juste!= end()
(que je m'attendrais à ce que l'optimiseur supprime), je ne vois pas comment il y a plus de travail.myset.contains()
(s'il existait), ce serait une indication assez forte que cette information n'est pas précieuse ( à l'utilisateur dans ce contexte).count()
faire plus de travail qu'il n'encontains()
faudraitstd::set
? C'est unique etcount()
pourrait donc êtrereturn contains(x) ? 1 : 0;
exactement la même chose.Il en manque parce que personne ne l'a ajouté. Personne ne l'a ajouté car les conteneurs de la STL que le
std
bibliothèque incorporait étaient conçus pour être minimaux dans l'interface. (Notez questd::string
cela ne vient pas de la STL de la même manière).Si une syntaxe étrange ne vous dérange pas, vous pouvez la simuler:
utilisation:
Fondamentalement, vous pouvez écrire des méthodes d'extension pour la plupart des
std
types C ++ en utilisant cette technique.Il est beaucoup plus logique de simplement faire ceci:
mais je suis amusé par la méthode de la méthode d'extension.
Ce qui est vraiment triste, c'est que l'écriture d'un efficace
contains
pourrait être plus rapide sur unmultimap
oumultiset
, car ils n'ont qu'à trouver un élément, alors qu'ilcount
faut trouver chacun d'eux et les compter .Un multiset contenant 1 milliard d'exemplaires sur 7 (vous savez, au cas où vous en auriez épuisé) peut avoir un très lent
.count(7)
, mais pourrait avoir un très rapidecontains(7)
.Avec la méthode d'extension ci-dessus, nous pourrions accélérer les choses dans ce cas en utilisant
lower_bound
, en comparantend
, puis en comparant à l'élément. Faire cela pour un miaulement non ordonné ainsi qu'un miaulement ordonné nécessiterait cependant des surcharges sophistiquées de SFINAE ou spécifiques au conteneur.la source
std::set
ne peut pas contenir de doublons etstd::set::count
reviendra donc toujours0
ou1
.std::multiset::count
canbackticks
autour du mot «ensemble» est parce que je ne fais pas référencestd::set
spécifiquement. Pour que vous vous sentiez mieux, je vais ajouter plusieursVous examinez un cas particulier et ne voyez pas une image plus grande. Comme indiqué dans la documentation,
std::set
répond aux exigences du concept AssociativeContainer . Pour ce concept, cela n'a aucun sens d'avoir unecontains
méthode, car elle est pratiquement inutile pourstd::multiset
etstd::multimap
, maiscount
fonctionne bien pour tous. Bien que la méthodecontains
puisse être ajoutée en tant qu'alias pourcount
forstd::set
,std::map
et leurs versions hachées (commelength
poursize()
instd::string
), mais il semble que les créateurs de bibliothèques n'en ont pas vu le besoin réel.la source
string
c'est un monstre: il existait avant la STL, où il existaitlength
et toutes ces méthodes qui sont basées sur des index, puis a été «conteneurisé» pour s'adapter au modèle STL ... sans supprimer les méthodes existantes pour des raisons de compatibilité descendante . Voir GotW # 84: Monoliths Unstrung =>string
enfreint vraiment le principe de conception "quantité minimale de fonctions membres".contains
effort est égal sur un ensemble / carte, mais peut être fait plus rapidement quecount
sur un multi-ensemble / multi-carte.contains
méthode.size()
etempty()
sont des doublons, mais de nombreux conteneurs ont les deux.Bien que je ne sache pas pourquoi
std::set
a nocontains
maiscount
qui ne retourne que jamais0
ou1
, vous pouvez écrire unecontains
fonction d'assistance basée sur un modèle comme celle-ci:Et utilisez-le comme ceci:
la source
contains
méthode existe, elle est juste nommée d'une manière stupide.std::string
touxstd.::string
ne fait PAS partie de la STL! Il fait partie de la bibliothèque standard et a été modélisé rétroactivement ...count
peut être plus lent car il a effectivement besoin de faire deuxfind
s pour obtenir le début et la fin de la plage si le code est partagé avecmultiset
.contains
. Je ne vois rien de mal à cela. @MarkRansom le petit SFINAE est d'empêcher ce modèle de se lier à des choses qu'il ne devrait pas.La vraie raison
set
est un mystère pour moi, mais une explication possible pour cette même conceptionmap
pourrait être d'empêcher les gens d'écrire du code inefficace par accident:Ce qui donnerait deux
map
recherches.Au lieu de cela, vous êtes obligé d'obtenir un itérateur. Cela vous donne un indice mental que vous devez réutiliser l'itérateur:
qui consomme une seule
map
recherche.Lorsque nous réalisons cela
set
et que nousmap
sommes faits de la même chair, nous pouvons également appliquer ce principe àset
. Autrement dit, si nous voulons agir sur un élément dans leset
uniquement s'il est présent dans leset
, cette conception peut nous empêcher d'écrire du code comme ceci:Bien sûr, tout cela n'est qu'une simple spéculation.
la source
if (myMap.count("Meaning of universe"))
, alors ...?contains
qu'aveccount
.Depuis c ++ 20,
bool contains( const Key& key ) const
est disponible.
la source
Et binary_search?
la source
std::unordered_set
, mais pourstd::set
cela.contains () doit renvoyer un booléen. En utilisant le compilateur C ++ 20, j'obtiens la sortie suivante pour le code:
la source
Une autre raison est que cela donnerait au programmeur la fausse impression que std :: set est un ensemble au sens de la théorie mathématique des ensembles. S'ils implémentent cela, alors de nombreuses autres questions suivront: si un std :: set a contains () pour une valeur, pourquoi ne l'a-t-il pas pour un autre ensemble? Où sont union (), intersection () et autres opérations et prédicats d'ensemble?
La réponse est, bien sûr, que certaines des opérations set sont déjà implémentées en tant que fonctions dans (std :: set_union () etc.) et d'autres sont aussi trivialement implémentées que contains (). Les fonctions et objets fonctionnels fonctionnent mieux avec les abstractions mathématiques que les membres d'objet, et ils ne sont pas limités au type de conteneur particulier.
Si l'on a besoin d'implémenter une fonctionnalité d'ensemble mathématique complète, il a non seulement le choix du conteneur sous-jacent, mais aussi le choix des détails d'implémentation, par exemple, sa fonction théorie_union () fonctionnerait-elle avec des objets immuables, mieux adaptés à la programmation fonctionnelle , ou modifierait-il ses opérandes et économiserait-il de la mémoire? Serait-il implémenté en tant qu'objet fonction dès le début ou il serait préférable d'implémenter une fonction C et d'utiliser std :: function <> si nécessaire?
Dans l'état actuel des choses, std :: set n'est qu'un conteneur, bien adapté pour l'implémentation de set au sens mathématique, mais il est presque aussi loin d'être un ensemble théorique que std :: vector d'être un vecteur théorique.
la source