txt Datei Zeilen einlesen, wenn sie bestimmtes Format haben

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ossihmz
User
Beiträge: 12
Registriert: Donnerstag 20. Dezember 2018, 06:44

Hallo zusammen,

bei der Suche kamen leider nur verwandte Themen, aber nichts, was ich brauchen könnte.


Die Daten übertrage ich seriell vom Arduino zum Raspberry und speichere sie als txt.
Ich habe nun eine Datei mit knapp einer Million Wertepaaren "x : y : z". (jeweils 3stellige Zahlen)

Durch Baudratenfehler von 2-5% kommt hin und wieder Murks raus.
Nun will ich nur die Wertepaare einlesen, die den Vorgaben entsprechen und in Arrays speichern zum späteren plotten und auch eine vernünftige Datei ausgeben.

Wahrscheinlich ein eher kleines Problem.
Habe ich nur noch nie zuvor benötigt.

Ich lese also die Datei zeilenweise ein und benötige nur den Befehl, der die Auswahl trifft. Rest ist kein Thema. :)

Für Hilfe bin ich dankbar.

Schönes Wochenende 8)
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

Das kann man zum Beispiel mit regulären Ausdrücken lösen: https://docs.python.org/3/library/re.html

Du schreibst einen regulären Ausdrück, der auf die Zeile passt (z.B.^\d{3}(?:\s:\s\d{3}){2}$) und prüfst das mit re.match.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@nezzcarth: da man mit split und map gleich die Zahlen bekommt und nur eine Exception abfangen müßte, sind hier reguläre Ausdrücke nicht nötig.

@ossihmz: wie willst Du erkennen, wenn nur eine Ziffer falsch übertragen wurde? Normalerweise überträgt mein zusätzlich zu den Daten auch noch Checksummen um Übertragungsfehler gleich zu erkennen. Wie sieht denn Dein Protokoll zwischen Arduino und Raspberry aus?
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

Sirius3 hat geschrieben: Samstag 19. Januar 2019, 14:26 @nezzcarth: da man mit split und map gleich die Zahlen bekommt und nur eine Exception abfangen müßte, sind hier reguläre Ausdrücke nicht nötig.
Ich hatte es so verstanden, dass die Zeilen dabei auch auf formale Richtigkeit geprüft werden sollten. Mit split und map kann man den (zugegeben vielleicht etwas unwahrscheinlichen) Fall, dass die Anzahl der Stellen nicht stimmt ja nicht prüfen. Dafür müsste man dann in einem weiteren Schritt für die Zahlen auf 99 < x < 1000 testen.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@nezzcarth: ich habe die Stellenzahl eher als Hinweis auf die Größenordnung gesehen und nicht auf eine feste Ziffernzahl.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@nezzcarth: 99 als Untergrenze ist keine gute Idee wenn auch 000 oder 042 gültige Zahlen sein können. :-)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
ossihmz
User
Beiträge: 12
Registriert: Donnerstag 20. Dezember 2018, 06:44

Ich schreibe mal 6 Zeilen und baue alle Fehler ein, die vorkommen:
315 315 330 (diese Zeilen brauche ich)
315 315 330315 315 330 (Fehler -> rausschmeißen)
3 (Fehler -> rausschmeißen)
15 315 330 (Fehler -> rausschmeißen)
315 315 330 (diese Zeile brauche ich)

Die ":" habe ich einfachheitshalber mal rausgelassen. In die Datei muss letztlich sowieso niemand schauen.
Formal kann ich zur Not am Ende noch ansetzen.

Hier geht es also nur darum die richtigen Zeilen in eine neue Datei zu überführen und in Python weiterverarbeiten zu können.
Habe mich nun auf den ersten Vorschlag hin mit den regulären Ausdrücke beschäftigt, da ich diese zum ersten Mal nutzen würde.

Die Vorgehensweise klang gar nicht schlecht denke ich?!
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ossihmz: Den regulären Ausdruck mit `re.compile()` in ein Objekt übersetzen und dann dessen `match()`-Methode mit `filter()` auf die geöffnete Ausgangsdatei loslassen und das Ergebnis der `writelines()`-Methode der geöffneten Zieldatei übergeben.

Wobei in Deinen Beispieldaten jetzt gar nicht vorkommt, dass einzelne Bytes korrupt sind, also irgendwelche zufälligen Bitmuster enthalten oder sich zumindest durch ein Bit oder so vom Originalbyte unterscheiden. Da muss man dann nämlich sicherstellen das man die Aktion auf Byteebene macht und nicht versucht die Ausgangsdatei als Textdatei zu behandeln, weil durch korrupte Bytes im Grunde mit jeder Textkodierung Probleme beim Dekodieren auftreten könnten.

Edit: Komplett ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import re


def main():
    is_valid_line = re.compile(rb'\d{3}(?:\s:\s\d{3}){2}$').match
    with open('input.txt', 'rb') as in_file:
        with open('output.txt', 'wb') as out_file:
            out_file.writelines(filter(is_valid_line, in_file))

    
if __name__ == '__main__':
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
ossihmz
User
Beiträge: 12
Registriert: Donnerstag 20. Dezember 2018, 06:44

Da ich mich erst seit relativ kurzer Zeit mit dem ganzen Thema und bzgl einer Bachelorarbeit damit beschäftige verstehe ich noch nicht alles:
Aber ich nehme an mit den korrupten Bytes meinst du Zeichen, wie sie auch entstehen, wenn ich mit 9600 baud sende und zb 115200 empfange?
Sowas kommt nicht vor. Es werden höchstens einzelne Wertepaare übersprungen, zerstückelt oder nur Teile davon ausgegeben.

Ist bzgl re dieses Format richtig?
(^(\d{3}\s){2}\d3}$)

Sprich nochmal für mich. ^Zeilenanfang, \d{3} neue Gruppe mit 3 zahlen und das ganze 2 mal. jeweils mit einem Leerzeichen \s davor. Und das letzte Mal die dritte Gruppe \d mit {3} Zahlen und $ für ne neue Zeile?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ossihmz: Auch wenn Du das bisher noch nicht in den Daten gesehen hast, würde ich den Code, soweit das möglich ist, trotzdem gegen gekippte Bits absichern. Ist ja nicht weiter schwer: einfach die Daten nicht als Text sondern als Bytes behandeln. Also die Dateien im Binärmodus öffnen und für den regulären Ausdruck ein `bytes`-Literal statt einer Zeichenkette verwenden. Siehe mein (ungetestetes) Beispiel im letzten Beitrag.

Wenn Du `match()` verwendest brauchst Du das ^ am Anfang nicht, weil die Funktion/Methode immer am Anfang anfängt. '\d' ist eine Ziffer, '\d{3}' sind genau drei Ziffern, '\d{3}\s' sind drei Ziffern und ein Whitespace-Zeichen *danach*. Das Whitespace-Zeichen kann zum Beispiel auch ein Tabulator sein. Wenn da tatsächlich genau ein Leerzeichen stehen soll, dann nimm einfach genau ein Leerzeichen stattdessen, also '\d{3} '. Das dann als Gruppe geklammert und mit '{2}' dahinter heisst diese Gruppe genau zwei mal. Und dann hast Du einen Fehler: \d3 steht für eine beliebige Ziffer gefolgt von der Ziffer '3'. Da hast Du die geschweiften Klammern um die 3 vergessen damit das mit dem \d davor für genau drei Ziffern steht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
ossihmz
User
Beiträge: 12
Registriert: Donnerstag 20. Dezember 2018, 06:44

__blackjack__ hat geschrieben: Samstag 19. Januar 2019, 16:58 @ossihmz: Auch wenn Du das bisher noch nicht in den Daten gesehen hast, würde ich den Code, soweit das möglich ist, trotzdem gegen gekippte Bits absichern. Ist ja nicht weiter schwer: einfach die Daten nicht als Text sondern als Bytes behandeln. Also die Dateien im Binärmodus öffnen und für den regulären Ausdruck ein `bytes`-Literal statt einer Zeichenkette verwenden. Siehe mein (ungetestetes) Beispiel im letzten Beitrag.

Wenn Du `match()` verwendest brauchst Du das ^ am Anfang nicht, weil die Funktion/Methode immer am Anfang anfängt. '\d' ist eine Ziffer, '\d{3}' sind genau drei Ziffern, '\d{3}\s' sind drei Ziffern und ein Whitespace-Zeichen *danach*. Das Whitespace-Zeichen kann zum Beispiel auch ein Tabulator sein. Wenn da tatsächlich genau ein Leerzeichen stehen soll, dann nimm einfach genau ein Leerzeichen stattdessen, also '\d{3} '. Das dann als Gruppe geklammert und mit '{2}' dahinter heisst diese Gruppe genau zwei mal. Und dann hast Du einen Fehler: \d3 steht für eine beliebige Ziffer gefolgt von der Ziffer '3'. Da hast Du die geschweiften Klammern um die 3 vergessen damit das mit dem \d davor für genau drei Ziffern steht.
Klasse! Vielen Dank nochmal für die Erklärung.
Ich probiere jetzt mal ein wenig aus. Mal schauen obs klappt. Habe mir vorerst eine kleinere Datei fertig gemacht zum Testen.

Also dann schon mal besten Dank für die Hilfe. :)
ossihmz
User
Beiträge: 12
Registriert: Donnerstag 20. Dezember 2018, 06:44

edit. Kommando zurück. Jetzt läuft es. Ich muss irgendeinen Fehler drin gehabt haben.

Ich bedanke mich bei euch und wünsche noch ein schönes Wochenende.

Kann ich den Titel ändern und [gelöst] davor schreiben? Oder können das nur Mods+?


Code: War keine Änderung nötig, außer dass ich meine eigenen Fehler wieder rausholen musste. :mrgreen:

Code: Alles auswählen

import re

def accel_raw_edit():
    is_valid_line = re.compile(rb"\d{3} \d{3} \d{3}$").match
    with open('input.txt', 'rb') as in_file:
        with open('output.txt', 'wb') as out_file:
            out_file.writelines(filter(is_valid_line, in_file))

    
main()
Antworten