Ich benutze viel std::set<int>
und oft muss ich einfach überprüfen, ob ein solcher Satz eine Zahl enthält oder nicht.
Ich würde es natürlich finden, zu schreiben:
if (myset.contains(number))
...
Aber wegen des Fehlens eines contains
-Mitglieds muss ich das umständlich schreiben:
if (myset.find(number) != myset.end())
..
oder das nicht so offensichtlich:
if (myset.count(element) > 0)
..
Gibt es einen Grund für diese Designentscheidung?
Ich denke, es war wahrscheinlich, weil sie versuchten, std::set
und std::multiset
so ähnlich wie möglich zu machen. (Und natürlich hat count
eine absolut sinnvolle Bedeutung für std::multiset
.)
Ich persönlich denke, das war ein Fehler.
Es sieht nicht ganz so schlimm aus, wenn Sie so tun, als wäre count
nur ein Schreibfehler von contains
und schreiben Sie den Test als:
if (myset.count(element))
...
Es ist immer noch eine Schande.
Um if (s.contains())
schreiben zu können, muss contains()
einen bool
(oder einen Typ, der in bool
konvertierbar ist, was eine andere Geschichte ist) zurückgeben, wie binary_search
Funktioniert.
Der grundlegende Grund hinter der Entwurfsentscheidung , dies nicht auf diese Weise zu tun , ist Das contains()
, das ein bool
zurückgibt, würde wertvolle Informationen darüber verlieren, wo sich das Element in der Auflistung befindet . find()
behält diese Informationen in Form eines Iterators bei und gibt sie zurück. Daher ist es eine bessere Wahl für eine generische Bibliothek wie STL. Dies war für Alex Stepanov schon immer der Leitsatz, wie er oft erklärte (zum Beispiel hier ).
Bezüglich des count()
-Ansatzes im Allgemeinen ist das Problem, obwohl es oft eine gute Umgehung ist, dass es mehr Arbeit leistet als eincontains()
= müsste machen.
Das soll nicht heißen, dass eine bool contains()
nicht sehr gut zu haben oder sogar notwendig ist. Vor einiger Zeit hatten wir eine lange Diskussion über genau dieses Problem in der Gruppe ISO C++ Standard - Future Proposals.
Es fehlt ihm, weil niemand es hinzugefügt hat. Niemand fügte es hinzu, weil die Container aus der STL, die in die std
-Bibliothek aufgenommen wurden, so konzipiert waren, dass sie nur eine minimale Schnittstelle hatten. (Beachten Sie, dass std::string
nicht auf dieselbe Weise aus der STL stammt.).
Wenn Ihnen eine seltsame Syntax nichts ausmacht, können Sie sie fälschen:
template<class K>
struct contains_t {
K&& k;
template<class C>
friend bool operator->*( C&& c, contains_t&& ) {
auto range = std::forward<C>(c).equal_range(std::forward<K>(k));
return range.first != range.second;
// faster than:
// return std::forward<C>(c).count( std::forward<K>(k) ) != 0;
// for multi-meows with lots of duplicates
}
};
template<class K>
containts_t<K> contains( K&& k ) {
return {std::forward<K>(k)};
}
benutzen:
if (some_set->*contains(some_element)) {
}
Grundsätzlich können Sie mit dieser Methode Erweiterungsmethoden für die meisten C++ std
-Typen schreiben.
Es macht viel mehr Sinn, dies einfach zu tun:
if (some_set.count(some_element)) {
}
aber ich bin amüsiert über die Methode der Erweiterungsmethode.
Das wirklich Traurige ist, dass das Schreiben eines effizienten contains
auf einem multimap
oder multiset
schneller sein könnte, da sie nur ein Element finden müssen, während count
jedes von ihnen finden und zählen muss.
Ein Multiset mit einer Milliarde Kopien von 7 (Sie wissen, falls Sie ausgehen) kann .count(7)
wirklich langsam sein, kann aber contains(7)
sehr schnell haben.
Mit der obigen Erweiterungsmethode könnten wir es für diesen Fall schneller machen, indem Sie lower_bound
verwenden, mit end
vergleichen und dann mit dem Element vergleichen. Dies für ein ungeordnetes Meow sowie ein geordnetes Meow zu tun, würde jedoch ausgefallene SFINAE- oder Containerspezifische Überladungen erfordern.
Sie betrachten den speziellen Fall und sehen kein größeres Bild. Wie in Dokumentation angegebenstd::set
erfüllt die Anforderungen von AssociativeContainer concept. Für dieses Konzept macht es keinen Sinn, eine contains
-Methode zu haben, da es für std::multiset
und std::multimap
ziemlich nutzlos ist, aber count
für alle von ihnen gut funktioniert. Die Methode contains
könnte zwar als Alias für count
für std::set
, std::map
und deren Hash-Versionen (wie length
für size()
in std::string
) hinzugefügt werden, sieht jedoch so aus, als ob die Bibliotheksersteller dies nicht für nötig gehalten hätten.
Obwohl ich nicht weiß, warum std::set
keine contains
hat, sondern count
, die immer nur 0
oder 1
, Zurückgibt, können Sie eine contains
-Hilfsfunktion wie folgt schreiben:
template<class Container, class T>
auto contains(const Container& v, const T& x)
-> decltype(v.find(x) != v.end())
{
return v.find(x) != v.end();
}
Und benutze es so:
if (contains(myset, element)) ...
Der wahre Grund für set
ist für mich ein Rätsel, aber eine mögliche Erklärung für dasselbe Design in map
könnte darin bestehen, zu verhindern, dass Personen versehentlich ineffizienten Code schreiben:
if (myMap.contains("Meaning of universe"))
{
myMap["Meaning of universe"] = 42;
}
Was zu zwei map
-Suchvorgängen führen würde.
Stattdessen müssen Sie einen Iterator erhalten. Dies gibt Ihnen einen mentalen Hinweis, dass Sie den Iterator wiederverwenden sollten:
auto position = myMap.find("Meaning of universe");
if (position != myMap.cend())
{
position->second = 42;
}
das verbraucht nur eine map
-Suche.
Wenn wir erkennen, dass set
und map
aus demselben Fleisch bestehen, können wir dieses Prinzip auch auf set
anwenden. Wenn wir also nur dann ein Element in der set
bearbeiten möchten, wenn es in der set
vorhanden ist, kann dieses Design uns daran hindern, Code wie folgt zu schreiben:
struct Dog
{
std::string name;
void bark();
}
operator <(Dog left, Dog right)
{
return left.name < right.name;
}
std::set<Dog> dogs;
...
if (dogs.contain("Husky"))
{
dogs.find("Husky")->bark();
}
Natürlich ist das alles nur eine Spekulation.
Was ist mit binary_search?
set <int> set1;
set1.insert(10);
set1.insert(40);
set1.insert(30);
if(std::binary_search(set1.begin(),set1.end(),30))
bool found=true;