Zeilen aus einer .txt-Datei unter best. Bedingung heraussuchen

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
Rotkehlchen
User
Beiträge: 20
Registriert: Freitag 7. Dezember 2018, 07:39

Hallo liebes Forum,
das ist mein erster Beitrag hier, deshalb bitte ich schon mal im Voraus um Gnade, was Fehler oder Ungenauigkeiten angeht.
Ich bin Python-Anfängerin, und habe folgendes Problem:
Aus einer .txt-Datei (nennen wir sie Quelle) möchte ich mittels Python bestimmte Zeichenketten nach folgendem Schema heraussuchen: die Datei enthält "Überschriften", die immer so anfangen: "AB_CD_...", oder "EF_GH_...", etc.
Im ersten Schritt möchte ich mir alle Überschriften, die mit "AB_CD" anfangen, in eine .txt-Datei namens Ziel schreiben lassen. Das hat soweit geklappt, hier mein Code dazu:

Code: Alles auswählen

def suchisuch(Quelldatei, Zieldatei):       # das ist die Subroutine, die die Überschriften aus der Datei rauskopiert...
    Quelle = open (Quelldatei,'r+')
    Ziel = open (Zieldatei,'w')                       # ...und sie in Ziel schreibt (jede Id in eine einzelne Zeile)

    f = Quelle.readlines()

    i = 0
    while i < len(f):
        if f[i].startswith("AB") == True:       # zum Teil steht vor der eigentlichen Überschrift auch noch eine Art Header, der die gleichen ersten beiden Buchstaben wie die Überschrift hat
            k = f[i]
            l = k.split(' ')
            if l[2].startswith("AB_CD") == True: 
                Id_gefunden = l[2]              
                Ziel.write(Id_gefunden)
                Ziel.write('\n')
        elif f[i].startswith("AB_CD") == True:
            k = f[i]
            l = k.split(' ')
            Id_gefunden = l[0]
            Ziel.write(Id_gefunden)
            Ziel.write('\n')
        i += 1
    Quelle.close()
    Ziel.close()
Nach jeder Überschrift folgen in meiner Quelldatei einige Zeilen Text, die unwichtig für mein Vorhaben sind. Nach einem Stichwort (sagen wir, es heißt "Stichwort_1") folgen allerdings immer eine bis mehrere Zeilen Text, die jedes Mal mit einem weiteren Stichwort (= "Stichwort_2") abgeschlossen werden. Beide Stichwörter sind bei allen Überschriften gleich, d.h. Stichwort_1 folgt auch auf Überschriften, die nicht mit AB_CD anfangen.
Mein Ziel ist es, in meine Ziel.txt-Datei nicht nur die Überschriften zu schreiben, sondern auch - immer jeweils nach der zugehörigen Überschrift - den Text zwischen den beiden Stichwörtern.
Meine bisherigen, leider fehlgeschlagenen Versuche, benutzen mehrere konditionierte Loops, hier ein Beispiel:

Code: Alles auswählen

def suchisuch_2(Quelldatei, Zieldatei): # diese Sub schreibt auch den Teil zwischen der Überschrift und Stichwort_1 in Ziel
    Quelle = open (Quelldatei,'r+')
    Ziel = open (Zieldatei,'w')             

    f = Quelle.readlines()

    i = 0
    while i < len(f):
        if f[i].startswith("AB") == True:
            k = f[i]
            l = k.split(' ')
            if l[2].startswith("AB_CD") == True:
                Id_gefunden = l[2]              
                Ziel.write(Id_gefunden)
                Ziel.write('\n')

                j = 0
                while f[i+j].startswith("Stichwort_2") == False:
                    Ziel.write(f[i+j])
                    Ziel.write('\n')
                    j += 1
        elif f[i].startswith("AB_CD") == True:
            k = f[i]
            l = k.split(' ')
            Id_gefunden = l[0]
            Ziel.write(Id_gefunden)
            Ziel.write('\n')
            j = 0
            while f[i+j].startswith("Stichwort_2") == False:
                Ziel.write(f[i+j])
                Ziel.write('\n')
                j += 1
        i += 1
    Quelle.close()
    Ziel.close()

def suchFilter(Datei): # soll aus dem von der vorherigen Sub erzeugten Datenwust nur noch die Überschriften und den Text zwischen den Stichwörtern raussuchen
    Ziel = open(Datei, 'r+')
    Zeilen = Ziel.readlines()

    i = 0
    while i < len(Zeilen):
        if Zeilen[i].startswith("AB_") == True:
            j = 1
            while Zeilen[i+j].startswith("Stichwort_1") == False:
                del Zeilen[i+j]
                j += 1
            del Zeilen[i+j+1]
        i += 1
    Ziel.close()
    Superziel = open(Datei, 'w')
    for i in Zeilen:
        Superziel.write(i)

    Superziel.close()
Hat jemand eine Idee, was ich falsch mache bzw. wie ich anders vorgehen sollte? Ich benutze Python 3 und bin für jede Hilfe dankbar! :)

LG
Rotkehlchen
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rotkehlchen: zuerst ein paar Anmerkungen zum Code:
Dateien öffnet man mit dem with-Statement, damit sie automatisch wieder geschlossen werden. Variablennamen schreibt man nach Konvention klein. 'r+' ist für eine Textdatei kein Dateimodus, den man benutzen sollte. Einbuchstabige Namen sind schlecht, weil sie nichts aussagen. Statt einer while-Schleife benutzt man for-Schleifen hier und statt über den Index iteriert man direkt über die Zeilen, die auch nicht alle gleich eingelesen werden, sondern indem man über das File-Objekt iteriert. Prüfe nicht explizit auf True. Der zweite if-Block wird nie erreicht, denn eine Zeile die mit AB_CD anfängt, fängt auch immer mit 'AB' an.

Alles in allem kommt man auf soetwas:

Code: Alles auswählen

def suchisuch(quelldateiname, zieldateiname):
    """das ist die Subroutine, die die Überschriften aus der Datei rauskopiert..."""
    with open(quelldateiname) as lines, open(zieldateiname, 'w') as output:
        for line in lines:
            if line.startswith("AB"):
                ld_gefunden = line.split()[2]
                if ld_gefunden.startswith("AB_CD"):
                    output.write(Id_gefunden + '\n')
            elif line.startswith("AB_CD"):
                ld_gefunden = line.split()[0]
                output.write(Id_gefunden + '\n')
Für Dein eigentliches Problem solltest Du Dir zuerst einen regulären Automaten aufmalen, der alle Zustände enthält, in denen Du Dich befinden kannst, die Übergänge zwischen den Zuständen und was passieren soll, wenn Du in dem Zustand bist. Also [Start_Zustand]: wenn Zeilestart 'AB' mit 'AB_CD' oder Zeilestart 'AB_CD' dann Suche_Stichwort1-Zustand, im [Suche_Stichwort1]: wenn Zeilenstart Stichwort1 dann Kopiere_Zustand. Im Kopiere_Zustand: kopiere Zeile, wenn Stichwort2 dann Start_Zustand.

Diesen regulären Automaten kann man leicht mit Zustands-Variable und ein paar if-Abfragen für die Übergänge implementieren.
Rotkehlchen
User
Beiträge: 20
Registriert: Freitag 7. Dezember 2018, 07:39

Vielen Dank für Deine Hilfe, Sirius3!
Wie so oft ist mir kurz nachdem ich um Hilfe gebeten habe, selbst ein Weg in den Sinn gekommen. Mir ist nämlich aufgefallen, dass der Text, der zwischen den beiden Stichwörtern steht, noch ein weiteres Merkmal hat: die erste Zeile davon beginnt immer mit einer eckigen Klammer (was Stichwort1 und den Filter überflüssig gemacht hat). Und so kam folgender Code zustande:

Code: Alles auswählen

def suchisuch(Quelldatei, Zieldatei, Durchsuchen_ab_Kapitel="1"): # die dritte Variable grenzt den Suchbereich ein, damit es schneller geht
    Quelle = open (Quelldatei,'r')
    Ziel = open (Zieldatei,'w')             

    f = Quelle.readlines()

    p = 0
    while p < len(f):
        if f[p].startswith(Durchsuchen_ab_Kapitel) == True:
            q = 1
            while p+q < len(f):
                if f[p+q].startswith(Durchsuchen_ab_Kapitel) == True: # weiterer Loop, da das erste Vorkommen des Kapitelnamens im Inhaltsverzeichnis ist
                    
                    i = p+q
                    while i < len(f):
                        if f[i].startswith("AB") == True:
                            k = f[i]
                            l = k.split(' ')
                            if l[2].startswith("AB_CD") == True: 
                                Id_gefunden = l[2]              
                                Ziel.write(Id_gefunden + '\n')
                                j = 0
                                while f[i+j].startswith("Stichwort2") == False:
                                    if f[i+j].startswith("[") == True: 
                                        m = 0
                                        while f[i+j+m].startswith("Stichwort2") == False:
                                            Ziel.write(f[i+j+m])
                                            m += 1
                                    j += 1
                        elif f[i].startswith("AB_CD") == True:
                            k = f[i]
                            l = k.split(' ')
                            Id_gefunden = l[0]
                            Ziel.write(Id_gefunden + '\n')
                            j = 0
                            while f[i+j].startswith("Stichwort2") == False:
                                if f[i+j].startswith("[") == True:
                                    m = 0
                                    while f[i+j+m].startswith("Stichwort2") == False:
                                        Ziel.write(f[i+j+m])
                                        m += 1
                                j += 1
                        i += 1
                q += 1
        p += 1
    Quelle.close()
    Ziel.close()
Das ignoriert jetzt leider einige Deiner Anmerkungen (und ich bin überrascht, dass ich mit so vielen Laufindizes den Überblick bei den Einrückungen behalten konnte)... aber er tut was er soll.
Eine Frage habe ich interesseshalber dennoch, und zwar habe ich auch ein wenig mit Deinem Vorschlag herumgespielt, wobei das ld_gefunden = line.split()[2] Probleme gemacht hat, von wegen index out of range. Mir ist unklar, wieso man hier überhaupt ohne den readlines()-Befehl auskommt, für mich ist das immer noch die nachvollziehbarste Variante, um die Zeilen als Listeneinträge anzusprechen.

LG
Rotkehlchen
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Rotkehlchen hat geschrieben: Montag 10. Dezember 2018, 08:14 Mir ist unklar, wieso man hier überhaupt ohne den readlines()-Befehl auskommt, für mich ist das immer noch die nachvollziehbarste Variante, um die Zeilen als Listeneinträge anzusprechen.
readlines() liest alle Teilen aus der Datei auf einmal ein und stellt diese in einer Liste zur Verfügung. Ein Dateiobjekt ist aber auch ein Generator und über beide Objekte kannst Du iterieren – das ist das entscheidende. Stell Dir vor, Du hast sehr großen Hunger und möchtest zehn Pizzen nacheinander essen. Dafür rufst Du beim Lieferservice an und der nette Pizzabote bringt einenStapel von zehn Pizzen auf einmal. Das ist eine Liste, bzw. so funktioniert readlines(). Nun möchtest Du aber gerne jede Pizza warm essen und hast zudem im Backofen nicht genügend Speicherplatz für einen großen Stapel Pizzen zum warmhalten. So bestellst Du jede Pizza einzeln und der Pizzabote liefert Dir diese sobald Du sie brauchst. Das ist ein Generator. Der Vorteil: immer warme Pizzen und der Backofen bleibt frei ;)
Rotkehlchen
User
Beiträge: 20
Registriert: Freitag 7. Dezember 2018, 07:39

Danke kbr für diese anschauliche Erklärung, das werde ich in meinem nächsten Programm berücksichtigen!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Rotkehlchen: Ich würde Dir dringen raten, das nochmal zu überarbeiten. Schnell gemacht ist das `== True` überall rauszulöschen und `xy == False` durch `not xy` zu ersetzen.
Der gedoppelte Code im Fall `AB` und `AB_CD` ist unschön. `f` oder `l` als Variablenname ist schlecht und die vielen Indizes verwirren. Die Einrücktiefe ist viel zu tief. Einige Deiner while-Schleifen sind sequenziell und nicht verschachtelt.
Antworten