Vektor sortieren



  • Der äußere Vektor hat keinen Zeitstempel. Wenn du den ersten und letzten Zeitstempel brauchst nimmst du den ersten und letzten inneren Vektor und davon das erste bzw. letzte Element.



  • bekomme noch folgenden Fehler:

    [C++ Fehler] algorith.cc(2361): E2093 'operator<' ist im Typ 'CDataset::SValue' für Argumente desselben Typs nicht implementiert.
    [C++ Fehler] algorith.cc(2362): E2093 'operator<' ist im Typ 'CDataset::SValue' für Argumente desselben Typs nicht implementiert.
    

    Ich nehme an, dass die Operatorüberladungsfunktion an der falschen Stelle steht. Hier mal die Klassendefinition:

    class CDataset {
       private:
          static int countDatasets; //zählt angelegten Datensätze
          int countData; // zählt Messwerte
          struct SValue {
             TDateTime timestamp;
             AnsiString unit;
             double value;
          };
          struct LessSValueTimestamp : public std::binary_function<SValue,SValue,bool> {
             bool operator()(const SValue& s1, const SValue& s2) const {
                return s1.timestamp < s2.timestamp;
             }
          };
          TDateTime creationTime;
          TDateTime periodStart;
          TDateTime periodStop;
          AnsiString identifier;
          AnsiString plantOperator;
          std::vector<AnsiString> VFilenames;
          void SplitString(AnsiString s_, TDateTime* timestamp_, double* value_);
       public:
          CDataset();
          std::vector<std::vector<SValue> >VData;
          int GetCountDatasets() {return countDatasets;}
          TDateTime GetCreationTime() {return creationTime;}
          AnsiString GetIdentifier() {return identifier;}
          int GetCountData() {return countData;}
          void SetIdentifier(AnsiString identifier_) {identifier = identifier_;}
          AnsiString GetPlantOperator() {return plantOperator;}
          void SetPlantOperator(AnsiString plantOperator_) {plantOperator = plantOperator_;}
          void ImportData(TStringList* stlPaths_);
          std::vector<AnsiString> &GetVFilenames();
    };
    


  • Hallo,

    Hast du sort überhaupt korrekt aufgerufen, wie DocShoe dir das gezeigt hat.
    Die Fehlermeldung besagt, dass das nicht der Fall ist. Sonst würde sort nicht nach operator< suchen.



  • Du hast Recht. Ich hab den dritten Parameter vergessen 🙂 Vielen Dank nochmal.

    sort(VData[0].begin(), VData[0].end(), LessSValueTimestamp());
    


  • ich hab nun die Sortierung in die Schleife geschrieben, so dass das nach Datum aller Dateien sortiert wird. Falls nun doch mal Zeitabweichungen auftreten könnte ich ja nun die kleinste und größte Zeit aller Dateien ermitteln. Ich hab dafür 2 Variablen definiert: TDateTime periodStart;
    und TDateTime periodStop;
    Eine Idee wie man das machen könnte?



  • rudpower schrieb:

    ich hab nun die Sortierung in die Schleife geschrieben, so dass das nach Datum aller Dateien sortiert wird. Falls nun doch mal Zeitabweichungen auftreten könnte ich ja nun die kleinste und größte Zeit aller Dateien ermitteln. Ich hab dafür 2 Variablen definiert: TDateTime periodStart;
    und TDateTime periodStop;
    Eine Idee wie man das machen könnte?

    Nicht die geringste... weil ich nicht verstehe, was du machen willst. Was genau hast du eigentlich vor, beschreib das Problem doch mal im größeren Umfang. Warum können Zeitabweichungen auftreten? Wovon weicht die Zeit ab? Was haben die Messdaten damit zu tun?
    Behalte beim Posten bitte im Hinterkopf, dass du im Thema bist und Zusammenhänge kennst, die Leser allerdings nicht. Also versuch dein Problem so umfangreich wie möglich aber auch nur so umfangreich wie nötig zu schildern.



  • Da muss man doch nur alle inneren vectoren sortieren (was du schon machst) und dann in einer Schleife über den äußeren vector das kleinste erste und das größte letzte Element suchen. Dabei hilft vector::front() vector::back() sowie std::min und std::max().



  • so ähnlich hab ich jetzt gemacht, nur ohne die Vektorfunktionen:

    void CDataset::ImportData(TStringList* stlPaths_)
    {
       VFilenames.resize(stlPaths_->Count); //Vektor für Dateinamen
       VData.resize(stlPaths_->Count); //Vektor für Messwerte
       for (int i=0; i<stlPaths_->Count; i++) { //durchläuft alle Dateien in der Stringliste
          std::auto_ptr<TStringList> stlData(new TStringList);
          stlData->LoadFromFile(stlPaths_->Strings[i]);
          VData[i].resize(stlData->Count); // Vektor für einzelne Messpunkte
          VFilenames[i] = stlPaths_->Strings[i];
          for (int j=0; j<stlData->Count; j++) {
             TDateTime temp_timestamp;
             double temp_value;
             SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
             VData[i][j].value = temp_value;
             VData[i][j].timestamp = temp_timestamp;
             countData++; // zählt Messwerte
          }
          sort(VData[i].begin(), VData[i].end(), LessSValueTimestamp()); //sortiert Vektoren nach Datum
       }
       //speichert minimale und maximale Zeit
       periodStart = VData[0][0].timestamp;
       periodStop = VData[0][VData[0].size()-1].timestamp;
       for (int i=0; i<VData.size(); i++) {
          if (VData[i][0].timestamp < periodStart)
             periodStart = VData[i][0].timestamp;
          if (VData[i][VData[i].size()-1].timestamp > periodStop)
             periodStop = VData[i][VData[i].size()-1].timestamp;
       }
    }
    

    werd das gleich mal umschreiben



  • hab gerade gelesen, dass es auch valarray gibt. Hier kann man mit min() das Minimum der Werte des gesamten arrays bestimmen. Könnte ich statt vector einfach valarray nehmen? Dann wäre das ja viel einfacher.



  • rudpower schrieb:

    hab gerade gelesen, dass es auch valarray gibt. Hier kann man mit min() das Minimum der Werte des gesamten arrays bestimmen. Könnte ich statt vector einfach valarray nehmen? Dann wäre das ja viel einfacher.

    Wenn es dir nur um den kleinsten oder größten Zeitstempel geht brauchst die Vektoren überhaupt nicht zu durchsuchen, das kannst du bereits beim Einlesen erledigen. Du hast ja bereits eine Zählvariable, die die Gesamtanzahl der gelesenen Datensätze mitzählt, das kannst du für die Bestimmung der Zeitraums ausnutzen:

    void ImportData()
    {
       TDateTime PeriodStart;
       TDateTime PeriodStop;
    
       unsigned int CountData = 0;
    
       // über die Dateien und die einzelnen Messdatensätze iterieren
       for( int f = 0; f < Files.size(); ++f )
       {
          ...
          for( int r = 0; r < Records.size(); ++r )
          {
             // Eigenschaften des Datensatzes lesen
    
             if( CountData == 0 )
             {
                // 1. Datensatz überhaupt
                PeriodStart = CurrentRecord.Timestamp;
                PeriodStop  = CurrentRecord.Timestamp;
             }
             else
             {
                // ab dem 2. Datensatz minimum/maximum bestimmen
                PeriodStart = min( CurrentRecord.Timestamp, PeriodStart );
                PeriodStop  = max( CurrentRecord.Timestamp, PeriodStop  );
             }
          }
       }
    }
    

    PS:
    Mein std::valarray hat kein min()/max(). Ob´s einfacher wäre wage ich auch zu bezweifeln, da du dem valarray ja auch erst einmal beibringen musst, wie es zwei SValue Objekte vergleichen soll. Für beliebige ranges gibt es allerdings die Funktionen std::min_element() und std::max_element().



  • hatte es mit Braunsteins Tipp geschafft. So funktioniert es. Danke nochmal für die zahlreichen Hilfen.
    Hier nun der fertige Code:

    void CDataset::ImportData(TStringList* stlPaths_)
    {
       VFilenames.resize(stlPaths_->Count); //Vektor für Dateinamen
       VData.resize(stlPaths_->Count); //Vektor für Messwerte
       for (int i=0; i<stlPaths_->Count; i++) { //durchläuft alle Dateien in der Stringliste
          std::auto_ptr<TStringList> stlData(new TStringList);
          stlData->LoadFromFile(stlPaths_->Strings[i]);
          VData[i].resize(stlData->Count); // Vektor für einzelne Messpunkte
          VFilenames[i] = stlPaths_->Strings[i];
          for (int j=0; j<stlData->Count; j++) {
             TDateTime temp_timestamp;
             double temp_value;
             SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
             VData[i][j].value = temp_value;
             VData[i][j].timestamp = temp_timestamp;
             countData++; // zählt Messwerte
          }
          sort(VData[i].begin(), VData[i].end(), LessSValueTimestamp()); //sortiert Vektoren nach Datum
       }
       //speichert minimale und maximale Zeit
       periodStart = VData.front().front().timestamp;
       periodStop = VData.front().back().timestamp;
       for (unsigned int i=1; i<VData.size(); i++) {
          periodStart = min(VData[i].front().timestamp, periodStart);
          periodStop = max(VData[i].back().timestamp, periodStop);
       }
    }
    


  • Wat is wenn keine Daten gelesen wurden? Rumms!

    Überhaupt scheinst du ein sehr optimistischer Programmierer zu sein und gehst davon aus, dass Dinge immer so sind, wie du erwartest.
    Guck dir mal deinen Code an und überleg dir, was passiert, wenn:

    - ein Datensatz nicht das korrekte Format hat (weil ein DAU mit nem Editor dran rumgefuckelt hat)
    - keine Messdatei gelesen werden konnte und damit keine Daten zur Verfügung stehen



  • Fehlerbehandlung für den Fall, dass die Daten das falsche Format haben hab ich drin. In der Methode SplitString habe ich einen try catch. Wird der Fehler ausgelöst gibt SplitString false zurück. Dann gibt auch die aufrufende Methode ImportData false zurück:

    bool CDataset::ImportData(TStringList* stlPaths_)
    {
       bool status = true;
       VFilenames.resize(stlPaths_->Count); //Vektor für Dateinamen
       VData.resize(stlPaths_->Count); //Vektor für Messwerte
       for (int i=0; i<stlPaths_->Count; i++) { //durchläuft alle Dateien in der Stringliste
          std::auto_ptr<TStringList> stlData(new TStringList);
          stlData->LoadFromFile(stlPaths_->Strings[i]);
          VData[i].resize(stlData->Count); // Vektor für einzelne Messpunkte
          VFilenames[i] = stlPaths_->Strings[i];
          for (int j=0; j<stlData->Count; j++) {
             TDateTime temp_timestamp;
             double temp_value;
             status = SplitString(stlData->Strings[j], &temp_timestamp, &temp_value);
             VData[i][j].value = temp_value;
             VData[i][j].timestamp = temp_timestamp;
             countData++; // zählt Messwerte
          }
          sort(VData[i].begin(), VData[i].end(), LessSValueTimestamp()); //sortiert Vektoren nach Datum
       }
       //speichert minimale und maximale Zeit
       periodStart = VData.front().front().timestamp;
       periodStop = VData.front().back().timestamp;
       for (unsigned int i=1; i<VData.size(); i++) {
          periodStart = min(VData[i].front().timestamp, periodStart);
          periodStop = max(VData[i].back().timestamp, periodStop);
       }
       return status;
    }
    //---------------------------------------------------------------------------
    // extrahiert Zeitwert und Messwert aus String
    bool CDataset::SplitString(AnsiString s_, TDateTime* timestamp_, double* value_)
    {
       int laenge = s_.Length();
       int posTab = s_.Pos("\t");
    
       try {
          *value_ = s_.SubString(posTab+1, laenge-posTab).ToDouble();
          *timestamp_ = s_.SubString(1, posTab-1).ToDouble();
          return true;
       }
       catch(...) {
          return false;
       }
    }
    

    Der zuletzt angelegte Datensatz wird daraufhin gelöscht und das modale Fenster zum Auswählen der Dateien wird wieder geschlossen. Außerdem bekommt der benutzer einen entsprechenden Meldungsdialog:

    void __fastcall TfrmNewDataset::btnCreateNewDatasetClick(TObject *Sender)
    {
       std::auto_ptr<TStringList> stlPaths(new TStringList);
    
       //alle markierten Dateien in Stringliste schreiben:
       for (int i=0; i<lviewData->Items->Count; i++) {
          TListItem* item = lviewData->Items->Item[i];
          if (item->Checked)
             stlPaths->Add(lviewData->Items->Item[i]->Caption +  lviewData->Items->Item[i]->SubItems->Strings[0]);
       }
       //neuen Datensatz anlegen und Messdaten importieren:
       VDataset.push_back(CDataset());
       if (VDataset.back().ImportData(stlPaths.get())) {
          VDataset.back().SetPlantOperator(cmbLocation->Text);
          VDataset.back().SetIdentifier(txtIdentifier->Text);
          ModalResult = mrOk;
       }
       else {
          ShowMessage("Einer der Dateien enthält keine Messdaten oder besitzt das falsche Format");
          VDataset.pop_back();
          ModalResult = mrCancel;
       }
    }
    


  • Ich glaube, du hast meinen Post nicht verstanden. Guck dir doch mal an, was dein Code macht und spiel ein paar Szenarios in deinem Kopf durch. Gibt es Fälle, in denen dein Code Probleme hat, und können diese Fälle auftreten?
    Deine ImportData gibt übrigens nur dann false zurück, wenn der letzte Datensatz nicht geparst werden konnte, weil du den Status mit jedem SplitString aktualisierst.

    Im Detail:
    In Zeile 9 setzt du die Vektorgröße auf die Anzahl der erwarteten Datensätze.

    In Zeile 14 wird die Textzeile geparst um deren Daten zu bestimmen
    Du gibst zwar einen Status zurück, wertest ihn aber nicht aus. Es wird also unabhängig davon, ob eine Datenzeile erfolgreich geparst werden konnte, ein Datensatz in den Vektor eingefügt. Hm... ob das so gewollt ist? Wenn in der nächsten Datenzeile gültige Daten stehen wird Status auf true gesetzt und der aufrufende Code weiß nichtmal, dass mindestens ein Datensatz Müll ist.

    In Zeile 22 und 23 greifst du ohne zu prüfen auf front() zweier Vektoren zu. Kann auch schon mal in die Hose gehen, wenn einer der beiden Vektoren keine Elemente besitzt.

    Noch was zur SplitString Methode:
    Warum übergibst du den String per value und den TDateTime und double als Zeiger. Alle Parameter sind doch für den Methodenaufruf zwingend erforderlich, das kann man besser durch (const) Referenzen lösen.

    bool CDataset::SplitString( const AnsiString& Line, TDateTime& Timestamp, double& Value )
    {
       ...
    }
    

Anmelden zum Antworten