Vektor sortieren
-
ich habe folgenden 2-dimensionalen Vektor:
std::vector<std::vector<SValue> >VData;
SValue ist ein struct:
struct SValue { TDateTime timestamp; AnsiString unit; double value; };
Nun möchte ich, dass die Elemente des Vektors nach Zeit sortiert werden, so das ich den kleinsten und größten Zeitwert ermitteln kann (1. und letztes Element). Wie mach ich das?
Wie ich erfahren hab gibt es die Methode sort. Das hab ich mal so ausprobiert:
sort(VData.begin(), VData.end());
Da kommt gleich ein Fehler, dass der Operator < im Typ Dataset::SValue für Argumente desselben Typs nicht implementiert ist. Also müsste ich doch den Operator < in SValue überladen? Mein Versuch mit
inline bool operator< (const SValue& x, const SVAlue& y) { return (x.timestamp < y.timestamp); }
scheitert mit der Meldung, dass ) erwartet wird.
Ich kenn mich leider mit Operatorüberladung noch nicht aus.
-
Hallo,
Dein Operator sieht gut aus. Evtl. kennt er SValue nicht?
Du kannst sort aber nicht so einfach auf einen 2D-vector anwenden. Was soll er da auch sortieren?
-
SValue sollte er kennen, da ich die Operatorüberladung ja in der Struktur definiert habe.
Wie sortiere ich den einen 2dimensionalen Vektor am besten?
-
Ich würde keinen < Operator definieren, sondern ein Funktionsobjekt zum Sortieren des Vektors benutzen. SValue sieht irgendwie nicht so aus, als sei der Zeitstempel ein Indikator für die Wertigkeit.
#include <functional> struct LessSValueTimestamp : public std::binary_function<SValue,SValue,bool> { bool operator()( const SValue& s1, const SValue& s2 ) const { return s1.Timestamp < s2.Timestamp; } }; int main() { std::vector<SValue> v; ... sort( v.begin(), v.end(), LessSValueTimestamp () ); }
Edit:
Sehe gerade erst, dass es ein verschachtelter Vektor ist. Nach welchen Kriterien soll den sortiert werden? Bzw.: Wie soll denn die Sortierung zum Schluss aussehen, wenn du mehrere innere Vektoren hast?
-
es soll nach Datum sortiert werden. Das letzte Element des Vektors (innerer Vektor) soll auch die aktuellste Zeit enthalten. Hier mal die Methode wo die Sortierfunktion stehen soll:
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 } } }
Ziel ist die erste und letzte Zeit zu ermitteln. Dazu wollt ich den inneren Vektor (hier j) aufsteigend nach Datum sortieren. Dann ist das erste Element des Vektors die älteste Zeit und das Letzte Element die aktuellste Zeit.
-
Ah, du hast also pro Datei einen Vektor, der die in der Datei enthaltenen Messdaten enthält. Der äußere Vektor enthält also die Vektoren für einzelne Messdateien. Überlappen sich die Intervalle einzelner Messdateien? Wenn sie sich nicht überlappen kannst du die inneren Vektoren wie oben vorgeschlagen sortieren und anschließend den äußeren Vektor nach dem Zeitstempel des ersten Elements der inneren Vektoren sortieren.
-
ja genau. Die Zeitwerte sowie Intervalle in den einzelnen Dateien sind immer gleich. Also könnte ich theoretisch alle inneren Vektoren sortieren und dann vom 1. äußeren Vektor den ersten und letzten Zeitwert nehmen?
-
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; } }