29 Stimmen

Mystische Einschränkung von std::binary_search

Beschreibung des Problems:
Betrachten Sie eine Struktur mit einer std::string name Mitglied. Nehmen wir zur Verdeutlichung an, dass es sich um ein struct Human die Informationen über Personen darstellen. Neben der name es kann auch viele andere Datenelemente haben.
Es soll ein Container vorhanden sein std::vector<Human> vec wobei die Objekte bereits sortiert sind nach name . Zur Verdeutlichung sei angenommen, dass alle Namen eindeutig sind.
Das Problem ist : mit einer Zeichenkette nameToFind herausfinden, ob ein Element mit diesem Namen im Array existiert.

Lösung und meine Fortschritte:
Die offensichtliche und natürliche Lösung scheint die Durchführung einer binären Suche unter Verwendung der std::binary_search Funktion. Es gibt jedoch ein Problem: Der Typ des gesuchten Elements ( std::string ) unterscheidet sich vom Typ der Elemente im Container ( Human ), und std::binary_search benötigt eine Regel zum Vergleich dieser Elemente. Ich habe versucht, dieses Problem auf drei Arten zu lösen, die im Folgenden beschrieben werden. Die ersten beiden dienen nur zur Veranschaulichung der Entwicklung meiner Lösung und der Probleme, auf die ich gestoßen bin. Meine Hauptfrage bezieht sich auf den dritten Weg.

Versuch 1: umwandeln std::string a Human .

Schreiben Sie eine Vergleichsfunktion:

bool compareHumansByNames( const Human& lhs, const Human& rhs )
{
   return lhs.name < rhs.name;
}

Fügen Sie dann einen Konstruktor hinzu, der eine Human Objekt aus std::string :

struct Human
{
   Human( const std::string& s );
   //... other methods

   std::string name;
   //... other members
};

und verwenden Sie die binary_search in folgender Form:

std::binary_search( vec.begin(), vec.end(), nameToFind, compareHumansByNames );

Scheint zu funktionieren, aber es gibt zwei große Probleme:
Erstens, wie man andere Datenelemente initialisiert, aber Human::name Das Setzen von magischen Werten kann zur Erzeugung eines Objekts führen, das semantisch illegal ist.
Zweitens müssen wir diesen Konstruktor als nicht explicit um implizite Konvertierungen während des Algorithmus zu ermöglichen. Die negativen Folgen dieser Vorgehensweise sind bekannt.
Außerdem ist eine solche vorübergehende Human Objekt wird bei jeder Iteration neu erstellt, was sich als recht teuer erweisen kann.

Versuch 2: umwandeln Human a std::string .

Wir können versuchen, eine operator string () zum Human Klasse, die ihre name und verwenden Sie dann den Vergleich für zwei std::string s. Dieser Ansatz ist jedoch auch aus folgenden Gründen ungünstig:

Erstens wird der Code wegen des besprochenen Problems nicht sofort kompiliert aquí . Wir müssen noch etwas mehr tun, damit der Compiler die entsprechenden operator < .
Zweitens, was bedeutet "einen Menschen in einen String umwandeln"? Das Vorhandensein einer solchen Konvertierung kann zu einer semantisch falschen Verwendung der Klasse führen Human was unerwünscht ist.

Versuch 3: Vergleich ohne Umrechnungen.

Die beste Lösung, die ich bisher gefunden habe, ist die Erstellung einer

struct Comparator
{
   bool operator() ( const Human& lhs, const std::string& rhs )
   {
      return lhs.name < rhs;
   }
   bool operator() ( const std::string& lhs, const Human& rhs )
   {
      return lhs < rhs.name;
   }
};

und die binäre Suche als

binary_search( vec.begin(), vec.end(), nameToFind, Comparator() );

Dies kompiliert und wird korrekt ausgeführt, alles scheint in Ordnung zu sein, aber hier beginnt der interessante Teil:

Werfen Sie einen Blick auf http://www.sgi.com/tech/stl/binary_search.html . Hier heißt es: " ForwardIterator ist der Werttyp denselben Typ wie T . ". Ziemlich verwirrende Einschränkung, und meine letzte Lösung bricht sie. Schauen wir mal, was der C++-Standard dazu sagt:


25.3.3.4 binäre_Suche

template<class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value);

template<class ForwardIterator, class T, class Compare>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp);

Erfordert: Der Typ T ist LessThanComparable (20.1.2).


Es wird nichts ausdrücklich gesagt über ForwardIterator Typs. Aber in der Definition von LessThanComparable gegeben in 20.1.2 es wird über den Vergleich von zwei Elementen von der gleichen Art . Und hier ist das, was ich nicht verstehe. Bedeutet es tatsächlich, dass die Typ des gesuchten Objekts とのことです。 Typ der Objekte des Containers doit gleich sein, und meine Lösung bricht diese Einschränkung ? Oder bezieht sie sich nicht auf den Fall, dass die comp Komparator verwendet wird, und zwar nur dann, wenn der Standard operator < zum Vergleich herangezogen wird? Im ersten Fall bin ich verwirrt, wie man std::binary_search um dies zu lösen, ohne auf die oben genannten Probleme zu stoßen.

Vielen Dank im Voraus für Ihre Hilfe und dass Sie sich die Zeit genommen haben, meine Frage zu lesen.

Nota: Ich verstehe, dass das Schreiben einer binären Suche von Hand keine Zeit in Anspruch nimmt und das Problem sofort lösen wird, aber um das Rad nicht neu zu erfinden, möchte ich std::binary_search verwenden. Außerdem ist es für mich sehr interessant, herauszufinden, ob es eine solche Einschränkung gemäß dem Standard gibt.

12voto

Alexandre C. Punkte 53706

Wenn Ihr Ziel darin besteht, herauszufinden, ob es eine Human mit einem bestimmten Namen, dann sollte das Folgende sicher funktionieren:

const std::string& get_name(const Human& h)
{
    return h.name;
}

...

bool result = std::binary_search(
    boost::make_transform_iterator(v.begin(), &get_name),
    boost::make_transform_iterator(v.end(), &get_name),
    name_to_check_against);

5voto

Kerrek SB Punkte 445528

[Vollständige Neuformulierung; Kommentare nicht beachten]

Der Wortlaut wurde von C++03 in C++0x geändert. In letzterem gibt es keine Anforderung mehr für T weniger als vergleichbar zu sein, vermutlich um diese unnötige Einschränkung zu mildern.

Der neue Standard verlangt lediglich, dass comp(e, value) impliziert !comp(value, e) . Solange Ihr Komparator also beide Richtungen implementiert, sollten Sie rechtlich in der Lage sein, eine string als Wert mit einem Komparator-Faktor, der beide asymmetrischen Vergleiche implementiert (d. h. Ihr "Versuch 3").

0voto

Billy ONeal Punkte 100691

Ich denke, die Norm will damit sagen, dass der Ausdruck fucntor(a, b) muss eine gültige strenge schwache Ordnung sein, unabhängig davon, ob der Algorithmus beschließt, etwas zu tun wie functor(*begin, *(begin + 1)) . Daher denke ich, dass Ihr Komparator eine Überlast von operator()(Human, Human) um konform zu sein.

Ich denke jedoch, dass dies ein eines der Dinge, die die Norm nicht ausdrücklich erlaubt, für die es aber nur wenige oder gar keine Implementierungen gibt, die den von der Norm gebotenen Spielraum ausnutzen .

0voto

AnT Punkte 300728

Ich sehe nirgendwo in der Norm, dass die Typen der Werte, die an die Vergleichsfunktion (oder an die < Operator) durch binary_search müssen identisch sein. Daher denke ich, dass es formal völlig in Ordnung ist, einen Komparator zu verwenden, der mit zwei unterschiedlichen Wertetypen arbeitet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X