Extrahieren von Textsegmenten/-blöcken inkl. Leerzeilen aus Datei

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
jedopy
User
Beiträge: 3
Registriert: Sonntag 3. November 2024, 13:23

Hallo zusammen,

ich bin neu hier im Forum und hoffe auf Unterstützung zu einem Thema.

Für ein Feierabendprojekt möchte ich wissenschaftliche Daten, deren Struktur vorgegeben ist, via Python in eine sqlite-Datenbank einlesen. Der Datenbankteil stellt kein Problem dar, die Aufbereitung der Quelldaten hingegen schon. Ich muss dazu sagen, dass ich relativ frisch mit Python begonnen habe und es in erster Linie ein Hobby ist, ich aber einen guten Programmmierstil lernen möchte, in dem unter anderem auch Themen wie Zeichensätze, Performance und Standardisierung berücksichtigt sind.

Meine Quelldatei sieht folgendermaßen aus:

Code: Alles auswählen

Parameter 1 = xxx
Parameter 2 = xxx
Parameter 3 = xxx

Parameter 4 = xxx
Parameter 5 = xxx
Parameter 6 = xxx
Parameter 7 = xxx
Parameter 8 = xxx
Parameter 9 = xxx

Parameter 10 = xxx
Parameter 11 = xxx
Parameter 12 = xxx
Parameter 13 = xxx
Parameter 14 = xxx
Parameter 15 = xxx
Parameter 16 = xxx

Parameter 17 = xxx
Parameter 18 = xxx
Parameter 19 = xxx
Parameter 20 = xxx
Parameter 21 = xxx
Parameter 22 = xxx
Parameter 23 = xxx
Parameter 24 = xxx

Obige Blöcke entsprechen einem Datensatz in der sqlite-DB und können mehrfach hintereinander vorhanden sein (man beachte die Leerzeile am Ende, aber leider auch mittendrin). Nach meinem Verständnis muss ich eine mehrstufige Funktion bauen:

1. Abgrenzen der Datensätze und schreiben in eine Variable
2. Zerlegen des Strings, damit ich ausschließlich den Teil hinter dem Gleichheitszeichen - abzüglich sämtlicher Leerzeichen - erhalte.

Laut meinem Buch kann man "Zeilen zählen". Doch erscheint mir das sehr fehleranfällig, da man hierdurch ja keine Muster oder Strukturen und somit auch kein Anfang und Ende eines Datensatzes erkennen kann. Welche Funktionalität in Python eignet sich dafür am ehesten?

Ich danke vielmals im Vorwege für Ratschläge und sende viele Grüße
Benutzeravatar
sparrow
User
Beiträge: 4536
Registriert: Freitag 17. April 2009, 10:28

Sind die Parameter jedes Satzes immer gleich?
Stehen sie immer in der selben Reihenfolge?
Woran erkennt man, dass ein neuer Datensatz beginnt? 2 Leerzeilen? Sich wiederholender Parameter? Gar nicht?
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Ein Datensatz sollte doch immer die selbe Anzahl an Parametern haben. Ansonsten liest man halt die Datei Zeilenweise und macht immer, wenn man eine Leerzeile trifft, etwas mit den Daten. Mehr kann man aus Deiner vagen Beschreibung nicht herleiten.
jedopy
User
Beiträge: 3
Registriert: Sonntag 3. November 2024, 13:23

Hallo,

oben beschriebener Block kann 1:1 und ohne Abweichung bis zu 20 mal untereinander in einer Textdatei stehen. Wie ich bereits schrieb, ist die letzte Zeile eine Leerzeile, kann aber nicht als Trennzeichen genutzt werden, da es auch zwischendrin Leerzeilen gibt.

Parameter 1 ist immer der erste und Parameter 24 immer der letzte Parameter und auch die Anzahl ist immer gleich. Die Reihenfolge ist immer identisch. Da es bei XML-Dateien die Möglichkeit gibt, Strukturen zu parsen, habe ich gehofft, es gibt auch bei Textdateien etwas Vergleichbares. Wahrscheinlich läuft es dann auf das Zählen von Zeilen hinaus.
Benutzeravatar
noisefloor
User
Beiträge: 4187
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Da es bei XML-Dateien die Möglichkeit gibt, Strukturen zu parsen, habe ich gehofft, es gibt auch bei Textdateien etwas Vergleichbares.
Kommt drauf an. Wenn es in irgendeiner eine Semantik gibt kann man es grundsätzlich parsen. Hast du aber scheinbar nicht, weil z.B. die Leerzeilen lt. deines Aussage keine relevanten Bedeutung haben.
Wahrscheinlich läuft es dann auf das Zählen von Zeilen hinaus.
Genau. Was hier IMHO auch vollkommen ok ist. Wenn du weißt, dass es immer 24 Datenzeilen gibt, die einen zusammenhängenden Datenblock bilden, dann kann man zählen. Dazu würde ich vielleicht auch vorher alle Leerzeilen aus der Datei entfernen, damit man wirklich durchzählen kann, ohne auf Leerzeilen Rücksicht zu nehmen.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 14032
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@jedopy: Wie sieht denn der Teil vor dem Gleichheitszeichen tatsächlich aus? Daran kann man ja auch sehen/überprüfen, ob da tatsächlich innerhalb der 24 Zeilen tatsächlich die erwarteten 24 Namen/Schlüssel enthalten sind, oder welche mehrfach vorkommen oder gar unerwartete.

Zu lösende Probleme teilt man normalerweise in kleinere Teilprobleme auf, und die dann wieder in kleinere Teilprobleme, bis man einzelne Teilprobleme mit einer Funktion mit ein paar Zeilen Code lösen kann. Dann schreibt man eine Funktion nach der anderen, testet die jeweils bevor man mit der nächsten weiter macht. Die Funktionen verwenden dann immer mehr die Funktionen die schon kleinere Teilprobleme gelöst haben, bis man dann am Ende das Gesamtproblem gelöst hat.

Ein typischer Punkt zum Aufteilen der Probleme ist wenn man etwas mehrfach für gleichartige Daten machen muss. Also beispielsweise das verarbeiten *einer* Zeile, was man dann als Funktion auf jede Zeile anwenden kann. Oder das verarbeiten eines Blocks von 24 Zeilen zu einem Datensatz. Was man dann auf Blöcke von 24 Zeilen anwenden kann. Und das selbst wieder die Funktion zum verarbeiten einer einzelnen Zeile verwendet.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from pprint import pprint

from more_itertools import chunked

EXPECTED_KEYS = {f"Parameter {i}" for i in range(1, 25)}


def parse_line(text):
    name, equals, value = text.partition("=")
    if not equals:
        raise ValueError(f"expected '=' in {text!r}")

    return (name.strip(), value.strip())


def parse_record(lines):
    record = dict(map(parse_line, lines))
    if record.keys() != EXPECTED_KEYS:
        raise ValueError(
            f"record {record!r} does not have the expected keys:"
            f" {EXPECTED_KEYS!r}"
        )
    return record


def parse_records(lines):
    non_empty_lines = filter(bool, (line.strip() for line in lines))
    return map(
        parse_record, chunked(non_empty_lines, len(EXPECTED_KEYS), True)
    )

def main():
    with open("test.txt", "r", encoding="ascii") as lines:
        for record in parse_records(lines):
            pprint(record)


if __name__ == "__main__":
    main()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
nezzcarth
User
Beiträge: 1754
Registriert: Samstag 16. April 2011, 12:47

jedopy hat geschrieben: Samstag 16. November 2024, 14:49 Da es bei XML-Dateien die Möglichkeit gibt, Strukturen zu parsen, habe ich gehofft, es gibt auch bei Textdateien etwas Vergleichbares. Wahrscheinlich läuft es dann auf das Zählen von Zeilen hinaus.
Da du – glaube ich – nur beispielhafte und nicht die Original-Daten gezeigt hast, ist das etwas schwierig zu beantworten. Wir wissen ja auch nicht, wie die Daten zustande und wo sie herkommen; möglicherweise könnte man auch dort schon ansetzen. In Anfängerfragen hier im Forum ist häufiger zu sehen, dass nur unvollständige Informationen gegeben werden und die Fragestellenden ggf. schon gewisse Vorannahmen zur Lösung treffen, die aber vielleicht gar nicht so ideal sind. Nach meiner Erfahrung mit dem Verarbeiten von seltsamen Datenformaten findet sich jedenfalls oft schon ein Ansatz, diese halbwegs sauber zu verarbeiten. Dazu ist allerdings eine eingehende Analyse aller Aspekte notwendig. Wenn du diese nicht preisgeben kannst oder möchtest, musst du dich selbst daran versuchen, bzw. dich an den allgemeinen Vorschlägen, die bereits gegeben worden sind, orientieren.
jedopy
User
Beiträge: 3
Registriert: Sonntag 3. November 2024, 13:23

Guten Morgen,

vorab: Ich muss auf Geschäftsreise und kann erst in etwa einer Woche mit dem Code experimentieren und mich melden. Um es nicht zu verkomplizieren, habe ich im Eingangspost die Anforderung vereinfacht. Ihr habt aber wahrscheinlich Recht, dass mehr Hintergründe erforderlich sind um das Gesamtbild zu verstehen.

Details:
Es gibt öffentlich zugängliche Ressourcen, wie z.B. celestrak, die via API und Dateidownload in diversen Formaten Flugbahninformationen zu Himmelskörpern wie z.B. Satelliten bereitstellen. Obige Anfrage zu meinem Problem bezieht sich auf das standardisierte Format OMM KVN und sieht im Endeffekt folgendermaßen aus (Struktur & Inhalt):
https://celestrak.org/NORAD/elements/gp ... FORMAT=kvn

Das Programm welches ich schreibe, ist in erster Linie ein Formatkonverter und Datensammler mit sqlite im Unterbau, soll sich im Laufe der Zeit aber zu einem privaten Framework weiterentwickeln, das ich in mein Hobby als Funkamateur einbringen kann (u.a. Rotorsteuerung für Satellitenfunk).

Viele Grüße
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Es handelt sich also um ein Format, bei dem die Schlüssel immer gleich sind.
Also kann man anhand der Schlüssel einen Record erkennen.

Ungetestet:

Code: Alles auswählen

import datetime

KEY_TO_VALUE = {
    "CCSDS_OMM_VERS": str,
    "CREATION_DATE": str,
    "ORIGINATOR": str,

    "OBJECT_NAME": str,
    "OBJECT_ID": str,
    "CENTER_NAME": str,
    "REF_FRAME": str,
    "TIME_SYSTEM": str,
    "MEAN_ELEMENT_THEORY": str,

    "EPOCH": datetime.datetime.fromisoformat,
    "MEAN_MOTION": float,
    "ECCENTRICITY": float,
    "INCLINATION": float,
    "RA_OF_ASC_NODE": float,
    "ARG_OF_PERICENTER": float,
    "MEAN_ANOMALY": float,

    "EPHEMERIS_TYPE": int,
    "CLASSIFICATION_TYPE": str,
    "NORAD_CAT_ID": int,
    "ELEMENT_SET_NO": int,
    "REV_AT_EPOCH": int,
    "BSTAR": float,
    "MEAN_MOTION_DOT": float,
    "MEAN_MOTION_DDOT": float,
}

def parse_lines(lines):
    for line in lines:
        if line.strip():
            key, equals, value = line.partition("=")
            if not equals:
                raise ValueError(f"expected '=' in {line!r}")
            yield key.strip(), value.strip()

def parse_records(lines):
    record = {}
    for key, value in parse_lines(lines):
        if key in record:
            raise ValueError(f"duplicate key {key!r}")
        record[key] = KEY_TO_VALUE[key](value)
        if len(record) == len(KEY_TO_VALUE):
            yield record
            record = {}
    if record:
        raise ValueError("partial record")

def main():
    with open("test.txt", "r", encoding="ascii") as lines:
        for record in parse_records(lines):
            print(record)


if __name__ == "__main__":
    main()
Antworten