Laden...

Daten dynamisch einlesen und bearbeiten

Erstellt von Torni vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.976 Views
T
Torni Themenstarter:in
50 Beiträge seit 2014
vor 3 Jahren
Daten dynamisch einlesen und bearbeiten

Hallo,

ich habe eine Liste mit vielen Zeilen die folgendermaße aufgebaut ist:


001*text1|002*Text2|004*Text4|
001*text11|002*Text22|003*Text3|009*Text9|
...

Pro Zeile gibt es beliebig viele Einträge bis ca. 30 - aber nicht jede Nummer von 001* - 030* muss belegt sein
Ich möchte diese Liste gerne neu schreiben ohne die Marker (001*, 002* usw.. und dazwischen statt dem Trenner "|" ein Semikolon schreiben)

Ich habe mit folgendem Code das Ergebnis aber irgendwie ist das totaler Rübensalat..:


        public int sKFilter = 1;
        public string sKZaehler="001";

        private void RemoveAllUnneededChars(string path)
        {
            StringBuilder sb = new StringBuilder();
            string[] lines = File.ReadAllLines(path, Encoding.UTF8);

                foreach (string line in lines )
                {
                string[] parts = line.Split('|');
                int plen = parts.Count()-1;
                int ie = 1;
                sKFilter = 1;
                
                    foreach (string part in parts)
                    {
                    if (ie > plen)
                        break;

                        sKZaehler = sKFilter.ToString("D3") + "*";

                        string shelp = part.Substring(0, 3);

                        if (part.StartsWith(sKZaehler))
                        {
                            sb.Append(part.Substring(part.IndexOf(sKZaehler) + 4) + ";");
                            sKFilter = sKFilter + 1;
                        }
                        else
                        {
                            while (sKZaehler.Substring(0,3) != shelp)//nxt part ist nicht gleich der Nummer
                            {
                                sb.Append(";");
                                sKFilter = sKFilter + 1;
                                sKZaehler = sKFilter.ToString("D3") + "*";
                            }
                            sb.Append(part.Substring(part.IndexOf(sKZaehler) + 4) + ";");
                            sKFilter = sKFilter + 1;
                        }
                    ie++;
                    }
                sb.Append(Environment.NewLine);
                }

            System.IO.File.WriteAllText("Standorte_bereinigt.csv", sb.ToString());
        }

Wäre echt nett, wenn da mal jmd drüber kucken könnte und mir mal Tipps geben könnten, das bissl zu verschönern..

T
2.219 Beiträge seit 2008
vor 3 Jahren

Der Code ist sehr suboptimal.
Hier hast du zu viele Abläufe in der Methode zusammen gerührt, was den Code unübersichtlich und schlecht verständlich/wartbar macht.
Ebenfalls hast du mit sKZaehler ein public Feld, was von außen zugegriffen werden kann.
Damit gibst du die interna deiner Klasse raus.

Ich würde dir eine Trennung der einzelnen Abläufe in Methoden als ersten Schritt empfehlen.
Hier sollte das einlesen der Zeilen, die Umwandlung der Zeilen und das schreiben in eigene Methode ausgelagert werden.

Auch tust du Dinge die mir nicht klar sind bzw. scheinbar zu einer unbekannten Anforderung gehören.
Deine Prüfung von ie und plen ergibt keinen Sinn, da du durch die foreach Schleife nie über die Länge von Parts laufen kannst.
Auch mit einer for Schleife wäre dies nicht möglich.

Ebenfalls hast du eine Prüfung der Nummern in deinem Code, der scheinbar irgend einem Zweck dient, der mir aber nicht bekannt ist.
Auch könnte man den gesamten Prozess vereinfachen.
Die Ersetzung der Pipes kannst du mit einem Replace im String erledigen.
Die Entfernung der Marker könntest du sogar über ein Split mit * erreichen, sofern diese nicht im Text vorkommen.
Ansonsten könntest du deinen Code generell vereinfachen und deine Schleifen umbauen/teilweise sogar entfernen.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

T
Torni Themenstarter:in
50 Beiträge seit 2014
vor 3 Jahren

Hi T-Virus,

ja bei den ersten beiden Absätzen gebe ich dir vollkommen recht.
Da muss ich dringend Hand anlegen.

Was die Prüfung mit ie und plen angeht ist in der Textdatei wie oben im Beispiel ein abschliessendes Pipe weswegen der Code ohne den break in ner endlosschleife münden würde.

Die Prüfung der Nummern (inkl dem *, weil wie du anmerkst leider auch im Text vorkommen kann) habe ich so, damit die Reihenfolge der Daten für die zu erstellende csv eingehalten wird bzw. die Semikolon bis zur nächsten Nummer (Marker) gesetzt werden.

Das ist ja mein Knackpunkt wie ich das anders umsetzen soll.

Ziel soll ja z.B. eine Zeile so sein:

Text1;Text2;;Text4;;;;Text8;;;;;;;;;;TextXX;;;
usw.

T
2.219 Beiträge seit 2008
vor 3 Jahren

Da du eine foreach Schleife verwendest, kannst du nie eine Endlosschleife haben.
Auch kannst du die Zeilen bei Split mit der StringSplitOption.RemoveEmpty auch durchführen und leere Einträge ignorieren, dann brauchst du auch dafür keine extra Logik/Prüfungen.

Sind die Marker nicht schon pro Zeile sortiert?
Falls nicht, würde dein Ansatz mit der Prüfung stimmen.
Ansonsten wäre auch dieser überflüssig und könnte entfallen.

Ich würde auch überlegen, die Daten in einer eigenen Datenstruktur zu parken.
Z.B. als Standort mit Nummer und Text.
Dann kannst du diese in einer Liste speichern und einfach per Sort selbst über die Nummern sortieren.
Dann brauchst du am Ende keine extra Prüfung machen sondern pro Zeile die Daten einmal sortieren und neu schreiben.

Eigentlich sollte sich damit und einer foreach für die Zeilen alles in einer Methode abdecken lassen.
Damit machst du dann deinen Code les- und für andere und dich selbst auch erst wartbar.
Wenn du in 1-2 Wochen den Code wieder überarbeiten müsstest, müsstest du dich erst einmal in den Prozess einarbeiten.
Und bei dem Ausgangscode müsste jeder erst mal nachdenken was da überhaupt wie und warum passiert.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

T
Torni Themenstarter:in
50 Beiträge seit 2014
vor 3 Jahren

arghh StringSplitOption.RemoveEmpty ... Stirnklatsch. Danke

...
Sind die Marker nicht schon pro Zeile sortiert?
Falls nicht, würde dein Ansatz mit der Prüfung stimmen.
Ansonsten wäre auch dieser überflüssig und könnte entfallen.

...
T-Virus

An für sich sortiert aber eben nicht zwingend fortlaufend, deswegen die Prüfung auf die Zahl, damit ich fehlende Marker als Semikolon in der CSV ersetze und den Inhalt des nächsten vorhandenen Markers an die richtige Position schreibe.

T
2.219 Beiträge seit 2008
vor 3 Jahren

Ist dann auch sicher gestellt, dass alle Zeilen die gleiche Spaltenanzahl haben?
Wenn du weißt, dass es max. 30 Einträge pro Zeile geben kann, dann solltest auch jede Zeile 30 Spalten haben.
Sonst hast du eine CSV Datei mit unterschiedlicher Anzahl an Spalten je Zeile, solltest du auch beachten und abfangen.

Bei dem WriteAllText gibst du auch kein Encoding an obwohl die die Ursprüngliche Datei mit UTF-8 liest.
Bei .NET Framework würde er die Datei mit dem System Encoding schreiben wollen.
Bei Deutschen Systemen dann z.B. mit Latin-1 (Codepage 1252).
Solltest du auf ein explizites Encoding mit UTF-8 stellen, sonst bekommst du bei Zeichen außerhalb von Latin-1 Datenmüll, der dann nicht mehr verarbeitet werden kann.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

T
Torni Themenstarter:in
50 Beiträge seit 2014
vor 3 Jahren

Grds. haben alle diese Zeilen max. 30 Spalten bzw. Marker von 001* bis 030* aber eben nicht immer alle vorhanden.

UTF8 - ok:

System.IO.File.WriteAllText("Standorte_bereinigt.csv", sb.ToString(), Encoding.UTF8);
T
2.219 Beiträge seit 2008
vor 3 Jahren

Dann passt zwar die Reihenfolge.
Dann musst du aber sicherstellen, dass eben jede Zeile auch 29 Semikolon hat, damit du auch 30 Spalten pro Zeile hast.
Aktuell füllt deine Logik aber nur Lücken auf ohne aber auch genau 30 Spalten zu erzeugen.

Wenn du also wie im Ausgangspost keinen Eintrag mit Marker 030 hast, dann hast fehlen am Ende auch ein paar Spalten.
Dadurch haben einige Zeilen dann nicht genau 30 Spalten sondern je nach Datenbestand aus der original Datei nur die aufgefüllten Lücken.

Du musst also beim schreiben nicht nur die Lücken zwischen den Einträgen füllen sondern auch nach dem letzten Eintrag noch dafür sorgen, dass dort bis zu den 30 Spalten alles aufgefüllt wird.

Hier würde ich aber eine strikte Trennung zwischen dem einlesen und parsen der Daten sowie dem schreiben der geparsten Daten in unterschiedliche Klassen packen.
Z.B. eine Parser Klasse, die in einem Dictionary<int, List<Standort>> dir die Zeilennummer bei 0 beginnende mit der Liste der geparsten Standorte liefert.
Und eine Writer Klasse zum Schreiben der Standorte im neuen CSV Format.

Dadurch hat jede Klasse eine gewisse Zuständigkeit und dein Coe trennt diese durch die Klassen auch sauber ab.
Dies macht das verstehen, warten und erweitern deines Codes auch um längen einfacher als die bisherige Methode.

Nachtrag:
Deine Zeile mit sb.Append(Environment.NewLine); kannst du auch ersetzen.
Der StringBuilder bietet hierfür bereits AppendLine() ohne Parameter an.
Intern wird dann ein Append mit Environment.NewLine gemacht.
Macht dann auch durch den expliziten Methoden Namen klar, dass hier die Zeile umgebrochen wird.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.