Vector mit Struct über mehrere Spalten sortieren



  • Hallo,

    in einem Vector halte ich ein Struct Benutzer mit den Eigenschaften VN, NN, PW etc. Um nach den jeweiligen Eigenschaften sortieren zu können, habe ich folgende Operator-Überladung definiert. Funktioniert soweit nach Wunsch.

    bool operator<(const TBenutzer& B1, const TBenutzer& B2)
    {
        if(Sort_Benutzer == sbBenutzer_ID)
            return B1.iID_Benutzer < B2.iID_Benutzer;
        else if (Sort_Benutzer == sbBenutzer_VN)
            return stricmp(B1.cBenutzer_VN, B2.cBenutzer_VN) < 0;
        else if (Sort_Benutzer == sbBenutzer_NN)
            return stricmp(B1.cBenutzer_NN, B2.cBenutzer_NN) < 0;
        else if (Sort_Benutzer == sbBenutzer_PW)
            return stricmp(B1.cBenutzer_PW, B2.cBenutzer_PW) < 0;
        else{
            ShowMessage("Unzulässiger Sortierbegriff in Bn");
            return false;
        }
    }
    

    Wie stelle ich es aber an, eine komplexere Sortierordnung zu realisieren?
    Z.B. VN aufwärts, NN abwärts, PW aufwärts

    Gruß
    Leo



  • Ich habe davon leider keine Ahnung. Ansonsten würde ich empfehlen, verschiedene Sortierfunktionen zu machen ( bool SortBB(const TBenutzer& B1, const TBenutzer& B2, const bool& aufsteigend) ) oder dem Operator eben eine dritte Variable hinzuzufügen, und dann eben per ::operator< etc.



  • Hallo,

    eine Antwort, die mit

    FragtNicht^^ schrieb:

    Ich habe davon leider keine Ahnung. (...)

    beginnt, hat natürlich einen gewissen Unterhaltungswert ...

    Erklär' doch bitte genauer, wie das mit dem 3. Operator gehen soll.

    Damit wir uns nicht missverstehen. Mit komplexerer Sortierung meinte ich nicht VN ODER NN ODER PE, sondern VN UND NN UND PE.

    In SQL würde das wie folgt aussehen:

    SELECT TBenutzer.VN, TBenutzer.NN, TBenutzer.PW
    FROM TBenutzer
    ORDER BY TBenutzer.VN, TBenutzer.NN DESC , TBenutzer.PW;
    

    Gruß
    Leo



  • Ich habe hier eine mögliche Lösung für dein Problem. Ich benutze hierfür Funktionsobjekte in Zusammenhang mit der std::sort() Funktion. Sicherlich lässt sich an dem Code einiges verbessern, aber immerhin läuft es 😃 . Der zu sortierende Datentyp ist hier lediglich eine Struktur mit drei Integern, das Beispiel lässt sich aber problemlos an deinen Datentyp anpassen.

    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <cstdlib>
    #include <iomanip>
    
    using namespace std;
    
    //Datentyp für die Sortierung
    struct Beispiel
    {
       int v1, v2, v3;
    
       Beispiel(int V1 = 0, int V2 = 0, int V3 = 0) : v1(V1), v2(V2), v3(V3) {}
    
       Beispiel& operator()(int V1, int V2, int V3)
       {
           v1 = V1;
           v2 = V2;
           v3 = V3;
    
           return *this;
       }
    };
    
    //Ausgabe von Beispiel an einen Stream
    ostream& operator<<(ostream &Stream, const Beispiel &b)
    {
      return Stream << setw(4) << b.v1 << " " << setw(4) << b.v2 << " " << setw(4) << b.v3;
    }
    
    /*
       Unser Funktionsobjekt für std::sort()
    
       Man kann diesen Typ sicherlich noch über Templates verallgemeinern, doch 
       spare ich mir das für dieses Beispiel hier. 
    */
    struct Sorter: public binary_function<Beispiel,Beispiel,bool>
    { 
    
       /*
        Für jeden Member des zu sortierenden Datentyps muss eine neue Struktur 
        definiert werden, die von dieser abstrakten SortierBedingungsklasse erbt. 
        m_Aufsteigend speichert, ob der entsprechende Member des Typs 
        auf- oder absteigend sortiert werden soll, während die Methode bedingung
        entsprechend vergleicht.    
       */
       class SortierBedingung
       {
          protected:
    
            bool m_Aufsteigend;
    
          public:
    
            SortierBedingung(bool aufsteigend) : m_Aufsteigend(aufsteigend) {}
    
            /*
               int bedingung(const Beispiel &t1, const Beispiel &t2)
    
               Der Rückgabewert sollte unabhängig vom Datentyp des entsprechenden 
               Members folgendes zurückgeben:
    
               Rückgabe < 0 : t1.member < t2.member
               Rückgabe = 0 : t1.member = t2.member
               Rückgabe > 0 : t1.member > t2.member
            */
            virtual int bedingung(const Beispiel &t1, const Beispiel &t2)=0;
    
       };
    
       //Sortierung nach Member v1
       struct Nachv1: public SortierBedingung
       {
           int bedingung(const Beispiel &t1, const Beispiel &t2)
           {
              if(m_Aufsteigend) return (t1.v1 - t2.v1);
              else return (t2.v1 - t1.v1);
           }
    
           explicit Nachv1(bool aufsteigend = true) : SortierBedingung(aufsteigend) {}
       };
    
       //Sortierung nach Member v2
       struct Nachv2: public SortierBedingung
       {
          int bedingung(const Beispiel &t1, const Beispiel &t2)
          {
              if(m_Aufsteigend) return (t1.v2 - t2.v2);
              else return (t2.v2 - t1.v2);
          }     
    
          explicit Nachv2(bool aufsteigend = true) : SortierBedingung(aufsteigend) {} 
       };
    
       //Sortierung nach Member v3
       struct Nachv3: public SortierBedingung
       {
          int bedingung(const Beispiel &t1, const Beispiel &t2)
          {
              if(m_Aufsteigend) return (t1.v3 - t2.v3);
              else return (t2.v3 - t1.v3);
          }
    
          explicit Nachv3(bool aufsteigend = true) : SortierBedingung(aufsteigend) {}
       };  
    
       vector<SortierBedingung*>  m_SortierBedingung; //Die an die Methode order() übergebenen Zeiger auf die
                                                      //SortierBedingungen werden hier gespeichert.
    
       //Wird von std::sort() aufgerufen
       bool operator()(const Beispiel &t1, const Beispiel &t2)
       {
           if(!m_SortierBedingung.empty())
           {    
               //Gehe alle übergebenen Sortierbedingungen durch
               vector<SortierBedingung*>::iterator i = m_SortierBedingung.begin();
    
               while(i != m_SortierBedingung.end())
               {
                  int ergebnis = (*i)->bedingung(t1,t2);
    
                  if(ergebnis < 0) return true; //tausche
                  else if(!ergebnis) i++;       //überprüfe die nächste Bedingung
                  else return false;            //tausche nicht
               }
           }
    
           return false; //keine SortierBedingung übergeben, tausche nichts
       }
    
       //füge eine weitere Sortierbedingung hinzu
       Sorter& order(SortierBedingung *cmp)
       {
           m_SortierBedingung.push_back(cmp);
    
           return *this;
       }
    
       //setze den Sorter zurück
       void clear()
       {
          for(vector<SortierBedingung*>::iterator i = m_SortierBedingung.begin(); i != m_SortierBedingung.end(); i++) delete (*i);
          m_SortierBedingung.clear();
       }
    };
    
    int main()
    {
    
       //Testdatensätze
    
       Beispiel datensatz;
    
       vector<Beispiel> vBeispiel;
    
       vBeispiel.push_back(datensatz( 999,   1,   0));
       vBeispiel.push_back(datensatz( 999,   2,   8));
       vBeispiel.push_back(datensatz( 999,   2,   3));
       vBeispiel.push_back(datensatz(   1, 212, 654));
       vBeispiel.push_back(datensatz(   1, 321,  85));
       vBeispiel.push_back(datensatz(   0,  12,   1));
       vBeispiel.push_back(datensatz(   0,  32,   1));
       vBeispiel.push_back(datensatz(   0, 800,   1));
       vBeispiel.push_back(datensatz(   3, 999,  44));
       vBeispiel.push_back(datensatz(   3,  56, 254));
    
       cout << "Unsortiert:" << endl << endl;
    
       for(vector<Beispiel>::iterator i = vBeispiel.begin(); i != vBeispiel.end(); i++) cout << *i << endl;
    
       cout << endl << "Sortiert:" << endl << endl;
    
       Sorter test;
    
       /*
          Bevor std::sort() aufgerufen wird, sollte der Sorter mit einer Kette von beliebigen SortierBedingungen 
          "initialisiert" werden. Die Methode order gibt immer eine Referenz auf das Objekt selbst zurück,
          wodurch eine beliebig lange "Initialisierungskette" möglich ist. In diesem Beispiel wird zuerst nach
          dem Member v2 aufsteigend, dann nach v1 absteigend und zuletzt nach v3 aufsteigend sortiert.
       */
       test.order(new Sorter::Nachv2()).order(new Sorter::Nachv1(false)).order(new Sorter::Nachv3());
    
       //sortieren
       sort(vBeispiel.begin(), vBeispiel.end(), test);
    
       //Ausgabe
       for(vector<Beispiel>::iterator i = vBeispiel.begin(); i != vBeispiel.end(); i++) cout << *i << endl;
    
       system("pause");
    
       return 0;
    }
    

    Der Vorteil ist, dass man den Vector nach Art von ORDER BY wie in deinem SQL Beispiel sortieren kann. Der Nachteil ist, dass du für jeden Member deines Typs, den du sortieren lassen willst, eine eigene Klasse schreiben musst, die von SortierBedingung erbt.



  • schorf2k schrieb:

    Ich habe hier eine mögliche Lösung für dein Problem. Ich benutze hierfür Funktionsobjekte in Zusammenhang mit der std::sort() Funktion. Sicherlich lässt sich an dem Code einiges verbessern (...)

    Wau!
    Habe den Code soeben getestet und ihn ein wenig variiert. Läuft einwandfrei.
    Besten Dank schon mal für diese wirklich hilfreiche Antwort!!

    Gruß
    Leo



  • Hallo,

    Nach dem Anpassen auf meinen Datentyp ...

    struct TUebungPruefung{
        TDateTime dtDatum;
        char cBenutzer_VN[BENUTZER_VN_MAX+1];
        char cAufgabenLevel[BENUTZER_NN_MAX+1];
        TDateTime dtPruefungsdauer; // Sekunden
    };
    

    ... erhalte ich beim Kompilieren in nachfolgender Zeile ...

    //Ausgabe von Beispiel an einen Stream
    ostream& operator<<(ostream &Stream, const TUebungPruefung &b)
    {
      // Diese Zeile erzeugt den Fehler
      return Stream << setw(4) << b.dtDatum << " " << setw(4) << b.cBenutzer_VN << " " << setw(4) << b.cAufgabenLevel << " " << setw(4) << b.dtPruefungsdauer;
    }
    

    ... folgende Fehlermeldung:
    [C++ Fehler] Unit_DM.cpp(216): E2015 Mehrdeutigkeit zwischen '_STL::basic_ostream<char,_STL::char_traits<char> >::operator <<(int)' und '_STL::basic_ostream<char,_STL::char_traits<char> >::operator <<(double)'

    Warum?

    Gruß
    Leo



  • Hast du auch für den Datentyp TDateTime eine operator<< Funktion geschrieben? Ich nehme an, dass es ein von dir selbst definierter Datentyp ist.



  • Hallo,

    schorf2k schrieb:

    Hast du auch für den Datentyp TDateTime eine operator<< Funktion geschrieben? Ich nehme an, dass es ein von dir selbst definierter Datentyp ist.

    Nein, ist kein von mir definierter Typ. Aber der Zuweisungsoperator verlangt laut Borland-Hilfe eine besondere Include-Reihenfolge:

    Wenn Sie #include <iostream> oder #define VCL_IOSTREAM angeben, bevor Sie die include-Anweisung für die Datei systdate.h (die normalerweise indirekt durch die Datei vcl.h einbezogen wird) festlegen, können Sie die Streaming-Operatoren (<< und >>) mit TDateTime-Werten als Argument verwenden:
    ostream& operator << (ostream& os, const TDateTime& arg);
    istream& operator >> (istream& is, TDateTime& arg);
    

    Nach der Umstellung dieser Include-Anweisung kompiliert der Code einwandfrei.
    Nur die Sortierung funktioniert nicht wie gewünscht. Je nach Sortieranweisung wird entweder gar nicht sortiert, oder es erscheint die Meldung 'Integer overflow'.

    Kann es evtl. mit den setw()-Anweisungen zusammenhängen. Muss ich die ggf. auf meinen Datentyp anpassen? Ich habe in der Doku nichts aussagekräftiges dazu finden können.

    Ich glaube, einiges könnte man einfacher am Telefon bereden. Falls das Anliegen nicht zu vermessen ist, kannst Du mir Deine Nummer mailen: leofreitag@netcologne.de

    Gruß
    Leo



  • Hallo,

    habe den Fehler gefunden. Siehe Anmerkungen bei 'Original-Version' und 'Angepasste Version'.

    struct Nachv3: public SortierBedingung
       {
          int bedingung(const TUebungPruefung &t1, const TUebungPruefung &t2)
          {
              // Original-Version
              /*
              if(m_Aufsteigend)
                  return (t1.cAufgabenLevel - t2.cAufgabenLevel);
              else
                  return (t2.cAufgabenLevel - t1.cAufgabenLevel);
              */
    
              // Angepasste Version mit strcmp, um char-Arrays vergleichen zu können.
              if(m_Aufsteigend)
                  return strcmp(t1.cAufgabenLevel, t2.cAufgabenLevel);
              else
                  return strcmp(t2.cAufgabenLevel, t1.cAufgabenLevel);
          }
          explicit Nachv3(bool aufsteigend = true) : SortierBedingung(aufsteigend) {}
       };
    
       struct Nachv4: public SortierBedingung
       {
          int bedingung(const TUebungPruefung &t1, const TUebungPruefung &t2)
          {
              // Original-Version
              // if(m_Aufsteigend) return (t1.dtPruefungsdauer - t2.dtPruefungsdauer);
              // else return (t2.dtPruefungsdauer - t1.dtPruefungsdauer);
    
              // Angepasste Version, um auch den Dezimalanteil 'richtig' sortieren zu können
              int iBack = 0;
              if(m_Aufsteigend){
                  if(t1.dtPruefungsdauer < t2.dtPruefungsdauer){
                      iBack = -1;
                  }else if(t1.dtPruefungsdauer > t2.dtPruefungsdauer){
                      iBack = 1;
                  }
              }else{
                  if(t2.dtPruefungsdauer < t1.dtPruefungsdauer){
                      iBack = -1;
                  }else if(t2.dtPruefungsdauer > t1.dtPruefungsdauer){
                      iBack = 1;
                  }
              }
              return iBack;
    
          }
          explicit Nachv4(bool aufsteigend = true) : SortierBedingung(aufsteigend) {}
    };
    

    So läuft's bestens.

    Gruß
    Leo


Anmelden zum Antworten