Chars zusammenfügen und sortieren
-
Hallo und ein Frohes Neues!
Ich habe mal eine Frage:
Ich hab mir ein programm geschrieben, dass aus 2 verschiedenen Dateien zeilen einliest, splittet vergleicht und zusammenfügt.
Das klappt wunderbar. Allerdings habe ich festgestellt, dass die Anwendung, für die die fertige Datei ist, alle zeilen Alphabetisch und Numerisch sortiert haben will -.-Die Zeilen (~200.000) sehen ungefähr so aus:
5340N;53.000000;40.000000;3
CAMRN;48.515145;-137.638273;5
MUN;34.5826752;123.692649;4Sortiert werden muss nach dem ersten Kürzel.
Intern habe ich 4 chars erstellt, indem die Werte zwischen den ";" schleifenweise für jede Zeile eingelesen werden.
Am Ende der Schleife werden sie momentan direkt in eine Datei geschrieben.Wie geh ich jetzt da am schlausten ran um die Zeilen zu sortieren?
-
Wieso nimmst du nicht
std::string? Da werden dir auch gleich die Operatoren für lexikalische Vergleiche bereitgestellt, die für die Sortierung benutzt werden können.Dann kannst du einen Container mit Elementen vom Typ
std::stringerstellen. Jede Zeile erhält nun so einen Eintrag. Dann machst dustd::sort()darüber, speicherst wieder in die Datei und fertig.Du musst die Trennung des ersten Kürzels nicht mehr beachten, da bei
std::stringzuerst nach dem ersten, dann nach dem zweiten Zeichen, etc. sortiert wird.
-
HI!
Danke für den Tipp schonmal!
Leider habe ich nicht wirklich viel erfahrung mit der Standardbibliothek.
Die Elemente befinden sich momentan in chars, damit ich sie einfach vergleichen kann.Wie muss ich denn dann vorgehen um meine 4 chars inklusive trennzeichen in den string zu bekommen?
und wie erstelle ich dann einen großen string, den ich dann auch sortieren kann?Ist es da nicht einfacher die chars in ein Array zu packen?
*EDIT*
Ich hab's mal manuell probiert:
`strcpy(chDataString, chFix);
ctrcat(chDataString, ";");
strcat(chDataString, chLatitude);
strcat(chDataString, ";");
cout << chDataString << endl;
`
-
Wie ist es genau mit den 4 Chars?
Ich hab es so verstanden, dass du in der Datei (den Dateien) die einzelnen Einträge hast:
5340N;53.000000;40.000000;3 CAMRN;48.515145;-137.638273;5 MUN;34.5826752;123.692649;4Dann willst du sie in den Arbeitsspeicher einlesen und sortieren. Nach dem Sortieren willst du die Werte zwischen den ; auslesen. Hab ich das richtig verstanden?
Zum Sortieren:
std::deque<std::string> Entries; // Container mit allen Einträgen(Zeilen), z.B. deque oder vector std::string Line; // String anlegen, in dem eine Zeile gespeichert wird std::ifstream File("filename.txt"); // Stream anlegen while (std::getline(File, Line)) // Zeilen in einer Schleife einlesen und im String speichern { Entries.push_back(Line); // Zeile in den Container einfügen } std::sort(Entries.begin(), Entries.end()); // Container sortierenWas das Zusammenfügen von Strings angeht, hast du es viel einfacher als mit
char-Arrays:std::string Str1 = "A5634C"; std::string Str2 = "XT522S"; std::string Text = Str1 + ";" + Str2; // zusammenhängenDu kannst auch
operator+=oderappend()benutzen. Schau vielleicht mal unter www.cplusplus.com nach, das ist eine gute Referenz zur Standardbibliothek.
-
Ok, also:
die Datei sieht in etwa so aus:
`ENTRE ENTRE-31.534170 130.949200
PEA83 PEA83-31.533961 115.952714
89W80 89W80-89.000000-180.000000
CUNNC CUNNC-31.532590 117.457100
PEA09 PEA09-31.531264 115.961353
NYNWH NYNWH-31.531130 147.241300
CBAEM CBAEM-31.531110 145.804600`
Hierbei handelt es sich um eine Navigationsdatenbank. Das geht so ungefähr 200.000 eintäge weiter. Daraus lese ich jede Zeile aus und zerschnipsel sie in 3 chars:
chFix[5] = "ENTRE"
chLatitude[10] = "-31.534170"
chLonglitude[11] = "130.949200"und
chNumber[1] = "0"
chNumber kommt aus einer anderen Datei, dort wird abgeglichen, ob derselbe eintrag vorhanden ist, wie in dem char chFix. Wenn nicht, gibt's ne null.
dann wird alles in eine neue Datei geschrieben, die ein anderes Format vorraussetzt:
`ENTRE;-31.534170;130.949200;3
PEA83;-31.533961;115.952714;5
89W80;-89.000000;-180.000000;0
CUNNC;-31.532590;117.457100;0
PEA09;-31.531264;115.961353;3
NYNWH;-31.531130;147.241300;5
CBAEM;-31.531110;145.804600;5`
Allerdings muss die Ausgabe vorher alphabetisch sortiert werden, also:
`89W80;-89.000000;-180.000000;0
CBAEM;-31.531110;145.804600;5
CUNNC;-31.532590;117.457100;0
ENTRE;-31.534170;130.949200;3
PEA09;-31.531264;115.961353;3
PEA83;-31.533961;115.952714;5
NYNWH;-31.531130;147.241300;5`
und da haperts...
Hoffe das reicht ums verständlicher zu machen

-
Okay. Das Zerschnipseln kannst du mit
std::stringeinfach lösen, indem du die Memberfunktionsubstr()benutzt (siehe mein geposteter Link zur C++-Referenz). Oder du benutzt gleichstd::replace(), um die Leerschläge durch Strichpunkte zu ersetzen.Also würde ich zuerst zeilenweise von der ersten Datei einlesen, die Formatänderung und Vergleiche mit der anderen Datei vornehmen und dann die aktuelle Zeile jeweils in einem Container speichern. Wenn du diesen Container gefüllt hast, kannst du ihn mit
std::sort()alphabetisch sortieren und wieder zeilenweise in die neue Datei schreiben.Was mir allerdings etwas problematisch erscheint, sind die Prüfungen, ob der Eintrag in der anderen Datei vorhanden ist. Gehst du da für jeden Eintrag der ersten Datei alle Einträge der anderen Datei durch? Eventuell lohnt es sich, die Einträge der anderen Datei auch in einem Container zu speichern, vielleicht einem assoziativen wie
std::set, damit du schneller suchen kannst.Ich hoffe, das war jetzt einigermassen verständlich, ansonsten einfach nachfragen.

-
supi, das ging schnell.
An genau das hab ich auch gerade gedacht und habs mal drauf losprobiert:
error C2039 'deque' is not a member of 'std' ...was das abgleichen angeht: ja, für jeden eintrag der ersten list muss ich die komplette 2. Liste durchlaufen, bis ich meine variable gefunden habe - oder eben nicht. Der Erste Testlauf hat gleub ich fast 4 Stunden gedauert...
also, nur um zu gucken ob ich's richtig verstanden habe:
Ich erstelle mir 2 container.
In den ersten kopiere ich die komplette 2. Liste
aus der ersten liste hole und verarbeite ich die variablen zeilenweise
(btw: leider hat er's hier nicht richtig angezeigt. in der ersten Liste habe ich zwischen dem ersten kürzel und dem wiederholten kürzel ca. 13 leerstellen, std_replace scheint hier schlecht zu sein...)
dann pushe ich alles in den 2. container.
nach durchlauf der schleife wird der sortiert und in die Datei geschrieben.richtig?
--btw2: ist es möglich einen string in den container zu pushen?
den hätte ich nämlich schon parat...
*EDIT* Fehlermeldung schon begraben und die Strings scheine ich auch pushen zu können.
-
error C2039 'deque' is not a member of 'std' ...
#include <deque> ...ist es möglich einen string in den container zu pushen?
std::vector<std::string> v; v.push_back ("hmm"); std::string str = "ak"; v.push_back (str); ...
-
super, hat wunderbar geklappt. die chars kann ich alle pushen. morgen bau ich das dann nochmal komplett um.
allerdings hab ich noch 3 Fragen:
1. woher kommt std::sort. ist mal wieder "no member of 'std'"
2. wie speichere ich den container: nur std::ofstream FILE "filename.txt"); ?
3. wie vergleiche ich mein chFix mit dem containerinhalt der 2. Liste und hole mir die Variable?
-
So, ich hatte gerade ein wenig Zeit...

Da du auch viel Eigeninitiative zeigst, hier mal ein ungefährer Ansatz.std::setist ein assoziativer Container, bei dem man in logarithmischer Zeit suchen kann.#include <fstream> // std::ifstream, std::ofstream #include <string> // std::string #include <deque> // std::deque #include <set> // std::set #include <algorithm> // std::sort() void Format(const std::string& Line, bool EntryFound) { // hier formatierst du den String um. Abhängig davon, // ob der Eintrag in der zweiten Datei gefunden wurde. } int main() { std::ifstream InFile1; // Stream für erste Datei std::string Line; // String für jeweilige Zeile // Einträge der zweiten Datei in List2 speichern std::set<std::string> List2; while (std::getline(File1, Line)) { List2.insert(Line); // Zeile in List2 einfügen } // Einträge der ersten Datei in List1 speichern und // prüfen, ob Eintrag jeweils in List2 vorhanden ist std::deque<std::string> List1; while (std::getline(File1, Line)) { bool EntryFound = false if (List2.find(Line) != List2.end()) // wenn Eintrag in List2 vorhanden ist { EntryFound = true; // kann man auch gleich mit ?: machen, aber hier übersichtlicher } Format(Line, EntryFound); // String formatieren List1.push_back(Line); // formatierten String in List1 einfügen } // Einträge aus List1 sortieren std::sort(List1.begin(), List1.end()); // Stream für Ausgabedatei erstellen std::ofstream OutFile("OutFile.txt"); // Alle Elemente aus List1 durchgehen for (std::deque<std::string>::iterator i = List1.begin(); i != List1.end(); ++i) { OutFile << (*i) << std::endl; // Einträge wieder in Datei schreiben } }Vielleicht klären sich einige Fragen, wahrscheinlich entstehen aber wieder viele neue... Frag einfach.

Falls du dich mit der STL noch nicht so auskennst, kannst du dir ja mal diese Artikel anschauen (vor allem 1 und 2 sind hierbei wichtig):
1) Container
2) Iteratoren und Algorithmen
3) Hilfsklassen und Erweiterungen
-
wow, vielen dank!!!!!

dann studier ich das mal fleißig.
Vielen, vielen Dank nochmal für die Mühe!
-
ok, genial.
Ich habe es geschafft alles zu pushen, zu sortieren und zu schreiben.Jetzt habe ich nurnoch eine Frage:
if (List2.find(Line) != List2.end()) // wenn Eintrag in List2 vorhanden istvergleicht doch die kompletten Zeilen miteinander, korrekt?
Da ich ja aus jeder Zeile nur das Erste Wort vergleichen kann (die sehen nämlich genau so aus wie das Endergebnis) muss ich die dann erst zerschneiden.
Wie schneide ich denn von einem String das erste Wort ab?
-
campinge schrieb:
if (List2.find(Line) != List2.end()) // wenn Eintrag in List2 vorhanden istvergleicht doch die kompletten Zeilen miteinander, korrekt?
Ja.
campinge schrieb:
Da ich ja aus jeder Zeile nur das Erste Wort vergleichen kann (die sehen nämlich genau so aus wie das Endergebnis) muss ich die dann erst zerschneiden.
Wie schneide ich denn von einem String das erste Wort ab?Mit der Memberfunktion
std::string::substr(). Wenn das Wort immer gleich lang ist, kannst dusubstr()einen fixen Parameter übergeben. Ansonsten kannst du mitstd::string::find()die Position des Trennzeichens (Abstand, Strichpunkt oder was auch immer) herausfinden und dann den Teilstring entsprechend lange machen.
-
So, vielen lieben Dank nochmal!
Ich habs doch tatsächlich noch gestern abend geschafft alles zum laufen zu bekommen. Die Idee mit den containern war wirklöich klasse, jetzt ist die Ablaufzeit von ~4 Stunden auf ca 2 Minuten verkürzt worden. Anscheinend war mein code doch etwas zu umständlich

Und mit der Kompatibilität klappst auch!
-
campinge schrieb:
jetzt ist die Ablaufzeit von ~4 Stunden auf ca 2 Minuten verkürzt worden.
Wow, damit hätte ich jetzt nicht gerechnet.

Teuer sind hierbei vor allem die Zugriffe auf die Festplatte, also per Streams. Die STL-Container sind extrem praktisch und auch weitgehend auf Performance optimiert. Ich habe jetzt einmal
std::dequeempfohlen, da da die Speicherverwaltung günstig ist (Array von Arrays). Du könntest auchstd::vectornehmen, allerdings könnte es sein, dass es dann länger dauert, weil jeweils die ganze Sequenz reallokiert werden muss, wenn die Kapazität erschöpft ist. Oderstd::list, wobei das sehrwahrscheinlich noch langsamer ist, weil jedes Element einzeln allokiert wird. Achte darauf, dass du bei der Liste nicht die globale Funktionstd::sort(), sondern die Memberfunktionstd::list::sort()aufrufst. Du kannst ja ein wenig experimentieren, aber ich denke, mitstd::dequebist du gut beraten.
-
campinge schrieb:
So, vielen lieben Dank nochmal!
Ich habs doch tatsächlich noch gestern abend geschafft alles zum laufen zu bekommen. Die Idee mit den containern war wirklöich klasse, jetzt ist die Ablaufzeit von ~4 Stunden auf ca 2 Minuten verkürzt worden. Anscheinend war mein code doch etwas zu umständlich

Und mit der Kompatibilität klappst auch!Schalt mal noch auf Release. Das sollte dir auch noch sehr viel bringen. Dann bist du wahrscheinlich im Sekunden - Bereich.

-
drakon schrieb:
Schalt mal noch auf Release. Das sollte dir auch noch sehr viel bringen. Dann bist du wahrscheinlich im Sekunden - Bereich.

Er sprach ja von 200'000 Einträgen und zwei Dateien. Allein das Streamen dürfte einige Zeit brauchen, und dann noch das Füllen zweier Container und Sortieren...
Aber genau weiss ich es auch nicht, da ich praktisch nie in dieser Grössenordnung arbeite. Von daher ist es schon möglich...

-
ok, danke für den Tipp!
Ich habe gerade festgestellt warum der durchlauf so schnell geht.
die Schleife für die 2. Datei läuft nur einmal durch, danach wird die einfach ignoriert. Somit wird halt nur für den 1. Navigationspunkt überprüft, ob dieser vorhanden ist. Der rest wird übersprungen.
gibt's so ne art fgetpos / fsetpos auch für die stl-container?
-
campinge schrieb:
gibt's so ne art fgetpos / fsetpos auch für die stl-container?
Hm, die Funktionen kenne ich nicht gut, da die eher C sind. Meinst du etwas wie
tellp()bzw.seekp()für Streams? Auf den Containern (std::dequeundstd::vector) kann man einfach perat()oderoperator[]die einzelnen Elemente manipulieren.Ich kann dir nur nochmals raten, www.cplusplus.com anzuschauen. Dort sind sowohl die Streams als auch die STL mit ihren Containern und Algorithmen sehr ausführlich erklärt.
-
Die generische Variante für für set wäre std::advance. Das abfragen müsstest du selbst mitzählen.