Programm zur Auslesen und Kopieren von Informationen aus einer Textdatei

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
maxwer
User
Beiträge: 16
Registriert: Mittwoch 20. Januar 2021, 11:46

Guten Tag,

ich befasse mich noch nicht lang mit der Programmiersprache Python und habe erst die grundlegenden Kenntnisse gelernt. Aktuell stehe ich vor einer Aufgabe bei der ich nicht weiter komme.
Diese besteht darin, dass ich ein Programm schreiben möchte welches mich bei der automatisierten Datenübermittlung unterstützt. Die Daten liegen in Form von Zahlen, Text, Leerzeichen, Tabulatoren und Zeilenumbrüchen vor. Als Ausgangsbasis habe ich eine Datei im .inp-Format. Diese Datei enthält Informationen welche ich auf vier Textdokumente (.txt) aufteilen möchte.

Die Aufgabe des Programms besteht darin, die Master-Datei (.inp-Format) auszulesen und dabei nach Schlagworten zu suchen. Die Schlagworte werden dabei natürlich von mir vorgegeben. In meiner Ausgangsdatei beginnt und endet der notwendige Bereich immer mit einem [*]. Demnach müsste das Programm bei [*Schlagwort] mit dem auslesen beginnen und bei [*] mit dem auslesen enden. Die von mir benötigten Auszüge haben alle unterschiedliche Zeilenlänge und sind immer unter einem anderen Schlagwort zu finden. Ich habe einen Auszug des Codes angehängt.

*Conductivity
123.5, 20.
132.3, 50.
141.1,100.
149.7,150.
158.4,200.
*Density
2.66e-09, 20.
2.65e-09, 50.
2.64e-09,100.
2.63e-09,150.
2.62e-09,200.
*Depvar
10,
*

Um meine Datei einzulesen habe ich folgenden Befehl verwendet: [Masterfile = open('Job2D.inp','r')]. Die .inp-Datei und das Python Programm befinden sich im selben Verzeichnis.
Im Vorhinein möchte ich mich schonmal für Tipps und Unterstützung von der Community bedanken.
MfG
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@maxwer: Vorweg ein Hinweis auf den Style Guide for Python Code, nach dem `Masterfile` klein geschrieben wird. Und es sind nach meinem Sprachgefühl auch zwei Worte, also mit Unterstrich: `master_file`.

Dateien öffnet man am besten mit der ``with``-Anweisung, damit die auch garantiert am Ende des Blocks geschlossen werden, egal warum der Block verlassen wird.

Das Aufteilen der Bereiche geht ganz gut mit `split_after()` aus dem externen `more_itertools`:

Code: Alles auswählen

#!/usr/bin/env python3
from operator import methodcaller

from more_itertools import split_before

FILENAME = "Job2D.inp"


def main():
    with open(FILENAME, encoding="ascii") as input_file:
        for block in split_before(input_file, methodcaller("startswith", "*")):
            print(block)


if __name__ == "__main__":
    main()
Ausgabe für Deine Beispieldaten:

Code: Alles auswählen

['*Conductivity\n', '123.5, 20.\n', '132.3, 50.\n', '141.1,100.\n', '149.7,150.\n', '158.4,200.\n']
['*Density\n', '2.66e-09, 20.\n', '2.65e-09, 50.\n', '2.64e-09,100.\n', '2.63e-09,150.\n', '2.62e-09,200.\n']
['*Depvar\n', '10,\n']
['*\n']
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
maxwer
User
Beiträge: 16
Registriert: Mittwoch 20. Januar 2021, 11:46

@__blackjack__: Vielen Dank für die schnelle Antwort und den Tipp mit dem Style Guide.

Haben Sie Quellen die Sie weiterempfehlen können um sich in diese Thematik selbstständig einzuarbeiten?
Da mein master_file deutlich umfangreicher ist und ich tatsächlich nur einige Ausschnitte übernehmen möchte, wäre es gut wenn ich ein Start-Schlüsselwort und End-Schlüsselwort definieren könnte.

Bspw. möchte ich mir nur [*Density] und die dazugehörigen Werte anzeigen lassen, den Rest jedoch nicht.
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Eingelesen werden muss ja trotzdem alles. Dann iterierst du über alle Blöcke und nur wenn das erste Element des Blocks mit deinem vorher eingegebenen Schlagwort übereinstimmt, gibst du den Block aus. Das lässt sich aus dem Programm von __blackjack__ mit zwei zusätzlichen Zeilen lösen. Die eine Zeile ist die Eingabe des Schlagwortes und die andere Zeile die nötige if-Bedingung.
maxwer
User
Beiträge: 16
Registriert: Mittwoch 20. Januar 2021, 11:46

@Jankie: vielen Danke für die schnelle Antwort. Ich tue mir aktuell noch sehr schwer damit die Befehle in die richtige Struktur zu bringen, da ich mit der Strukturierung in Python nicht vertraut bin. In anderen Sprachen funktioniert dies auf eine andere Weise. Wäre es möglich mir das am Beispiel von ___blackjack__'s Code zu zeigen?
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@maxwer: Welche anderen Programmiersprachen kennst Du denn? Python ist ja nicht wirklich exotisch. Objektorientierung die auf eine klassische imperative Grundlage aufsetzt. Und dann kommt noch ein bisschen funktionales dazu. Die „comprehension“-Syntax ist in der Sprachfamilie noch ein bisschen was besonderes, aber davon wurde in diesem Thema noch kein Gebrauch gemacht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Du musst bei dem Code von __blackjack__ über das print(block) noch eine If-Bedingung einbauen, dass nur wenn dein vorher eingegebenes Wort in dem ersten Element der Liste vorkommt, dass print(block) ausgeführt wird.

Also wie greift man auf das erste Element einer Liste zu und wie schaut man, ob ein vorher eingegebener String IN genau diesem ersten Element vorkommt?
maxwer
User
Beiträge: 16
Registriert: Mittwoch 20. Januar 2021, 11:46

@__blackjack__ @Jankie: Vielen Dank nochmals für die Tipps. Im Folgenden habe ich ein Programm angehängt welches auf der von euch beschriebenen Funktionsweise basiert.

Code: Alles auswählen

def main():
    for i in range(len(index_list)) and range(len(end_list)):
        with open(FILENAME, encoding="ascii") as input_file:
            lines = input_file.readlines()
            for line in lines:
                if line.startswith(index_list[i]):
                    start_line = lines.index(line)
                elif line.startswith(end_list[i]):
                    end_line = lines.index(line)
            Resulting_list = lines[start_line:end_line+1]
            print(Resulting_list)
        
            with open('Result.txt', 'w') as file:
                file.write(''.join(Resulting_list))
            
            
            

if __name__ == "__main__":
    main()
Ich bekomme über die Ausgabe "print" die korrekten Blöcke in der Konsole meiner Entwicklungsumgebung angezeigt.
Wenn ich jedoch die Blöcke in eine Datei schreiben möchte, wird immer nur der letzte Block gedruckt. Weshalb ist das so?
Wie kann ich das umgehen, da ich in der .txt-Datei den gleichen Inhalt brauche der in die Konsole ausgegeben wird?
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

Hast Du Dir mal angeschaut, was der Ausdruck `range(len(index_list)) and range(len(end_list))` bedeutet? Das was Du denkst, was es macht, kann man nicht einmal sinnvoll in Umgangssprache übersetzen: i soll sowohl über die Indizes von index_list als auch end_list laufen. Wie soll denn das gehen? Tatsächlich passiert ja etwas ganz anderes.
Für jeden Index liest Du dann einmal komplett den Inhalt von FILENAME und überschreibst jeweils die Datei `Result.txt` in jedem Durchgang mit neuem Inhalt.
Und wenn das funktionieren würde, dann hättest Du die Blöcke ja doch wieder nur in einer neuen Datei untereinander geschrieben, also nichts gewonnen.

Wo hast Du denn konkret Probleme mit den Lösungen von meinen Vorpostern?
maxwer
User
Beiträge: 16
Registriert: Mittwoch 20. Januar 2021, 11:46

@Sirius3: Ich möchte folgendes vermeiden: Die Inhalte von 'Result.txt' sollen eben nicht nach jedem Vorgang überschrieben werden. SOndern es sollen drei Blöcke in der Datei 'Result.txt' untereinander geschrieben werden.
Mein aktuelles Problem steht nicht im zusammenhang mit dem bisher geposteten. Ich habe nur den Forumsbeitrag eröffnet und wollte in diesem weiter meine Fragen stellen.
Mit dem Ausdruck `range(len(index_list)) and range(len(end_list))` möchte ich alle Inhalte der Liste aufgreifen um nach den entsprechenden Inhalten im 'FILENAME' zu suchen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@maxwer: ``for … in range(len(sequence)):`` ist in Python grundsätzlich ein „anti pattern“. Man kann *direkt* über die Elemente iterieren, ohne den Umweg über einen Laufindex.

Das ``and`` funktioniert so nicht. Das liefert in diesem Falle immer das erste `range()`-Objekt und niemals das zweite, das ``and`` kann man sich also sparen.

Wenn die Werte aus den beiden Listen am gleichen Index zusammengehören, dann sollten diese Werte auch gar nicht in zwei Listen stehen sondern in einer Liste, beispielsweise als Tupel.

Das einlesen ist maximal ineffizient gelöst. Das kann man kaum noch ungünstiger machen. Da wird mehrfach die komplette Datei eingelesen. Warum? Und wenn die Startzeile oder Endzeile eines Blocks gefunden wird, dann wird mit `index()` die gesamte Zeilenliste noch mal von vorne durchsucht um an den Index zu kommen. Und warum wird weitergesucht wenn man die beiden Werte bereits gefunden hat? Kann ein Block mehrfach vorkommen und Du willst nur das letzte Vorkommen finden? Und geht der Code so er da steht korrekt mit der Situation um wenn die Endzeile vor der Anfangszeile gefunden wird? Und es wird auch gar nicht berücksichtigt wenn nur eine oder gar keine der beiden Zeilen gefunden werden. Im ersten Durchlauf der äusseren Schleife fiele dass noch auf, weil man dann einen `NameError` bekommt. In weiteren Durchläufen werden dann im ungünstigen Fall einfach falsche Daten geschrieben.

Wenn Du die Ausgabedatei nicht immer überschreiben willst, dann tu das halt nicht. Statt die für jeden Block zu öffnen, öffnet man die einfach einmal am Anfang und schon wird die nicht jedes mal überschrieben.

Grunddatentypen haben in Namen nichts zu suchen. Man ändert das gar nicht so selten während der Programmentwicklung und dann hat man falsche, weil irreführende Namen im Programm oder muss alle betroffenen Namen anpassen.

Die Eingabedatei braucht man für diese Aufgabe nicht komplett in den Speicher laden. Selbst wenn die Reihenfolge der Blöcke bei der Ausgabe wichtig ist, braucht man nur die Zeilen dieser Blöcke im Speicher sammeln. Wenn die Ausgabereihenfolge der Eingabereihenfolge entsprechen kann/darf, dann braucht man eigentlich nie mehr als eine Zeile im Speicher halten.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
maxwer
User
Beiträge: 16
Registriert: Mittwoch 20. Januar 2021, 11:46

@__blackjack__: Vielen dank für dein ausführliches Feedback.
Die Werte aus beiden Listen gehören am gleichen Index zusammen. Ich lade hier den Auszug der Listen hoch um sich ein besseres Bild zu machen.

Code: Alles auswählen

index_list = ['** Interaction: ELEKTRODE_1','** Interaction: BLECH_1','** Interaction: ELEKTRODE_2']
end_list = ['SURF_BL1_EL1','SURF_BL2_BL1','SURF_BL2_EL2']
Die relevanten Blöcke kommen jeweils nur einmal vor. Die Ausgabe in der Datei 'Result.txt' für den einzelnen Block ist korrekt. Die Situation das nur eine oder gar keine Zeile mit dem Stichwort gefunden wird tritt nicht ein. Mein 'FILENAME'-Dokument enthält auf jeden Fall und 100%-ig die von mir gesuchten Einträge. Die Ausgabereihenfolge soll der Eingabereihenfolge entsprechen.
Was genau meinst du mit Grunddatentypen im Namen?

Wie das mit dem 'Tupel' funktioniert oder was es damit auf sich hat muss ich zuerst selbst recherchieren.
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

@maxwer: Anstatt 2 parallele Listen zu führen, führt man eine Liste, deren elemente List oder Tupel mit 2 Werten sind.

Code: Alles auswählen

# falsch
index_list = ['** Interaction: ELEKTRODE_1','** Interaction: BLECH_1','** Interaction: ELEKTRODE_2']
end_list = ['SURF_BL1_EL1','SURF_BL2_BL1','SURF_BL2_EL2']

# besser
values = [
    ('** Interaction: ELEKTRODE_1', 'SURF_BL1_EL1'), 
    ('** Interaction: BLECH_1', 'SURF_BL2_BL1'),
    ('** Interaction: ELEKTRODE_2', 'SURF_BL2_EL2')
]
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@maxwer: Das keine oder nur eine Zeile gefunden wird sollte vielleicht nicht auftreten können, aber dass das tatsächlich nicht passieren kann ist nicht wirklich garantiert. Simpelster Fall ist das die Eingabedatei aus irgendwelchen Gründen nicht komplett ist. Sei es weil das erzeugende Programm abgebrochen wurde, der Datenträger voll war, die Datei aus einem beschädigten Archiv kommt, weil ein Benutzer die Datei bearbeitet hat — es gibt da viele Möglichkeiten. Bei Eingabedaten sollte man immer ein bisschen vorsichtig sein und auch mit Fehlern rechnen. Man muss da nicht total paranoid sein, aber Fehler die man leicht erkennen kann, sollte man IMHO berücksichtigen und nicht ”lautlos” falsche Ergebnisse erzeugen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten