for-schleife über Zeilen einer Datei: Nächste + übern. Zeile

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
Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

Habe folgendes Problem, an dem ich insgesamt zig Stunden sass. Habe den Beitrag für Studenten übrigens gelesen ;). Ich bin kein Student mehr und programmiere höchst selten. Da meine Aufgabe gerade etwas eilt, würde ich mich sehr über Hilfe freuen. Zu dem folgenden Problem habe ich wirklich ausgiebig gegoogelt, aber konkret nichts gefunden.

Folgendes Setting:
- Eingabedatei (txt)
- Ausgabedatei (txt)

Aus der Eingabedatei sollen bestimmte Informationen gezogen werden und in einer etwas anderen Form in die Ausgabedatei geschrieben werden.

Das habe ich soweit auch schon ziemlich gut hinbekommen, nur stehe ich nun vor folgender Schwierigkeit:
Ich gehe per

Code: Alles auswählen

for line in eingabedatei
und entsprechenden if-Schleifen durch die Datei und ziehe mir die Informationen aus der ersten Zeile.

Die Informationen befinden sich aber nicht alle in der ersten Zeile, sondern in Blocks zu je drei Zeilen oder mehr. Man kann sich die Eingabedatei etwa so vorstellen:

vorlauf... bla
120 Informationszeile A1
500 Informationszeile B1
600 Informationszeile C1
600 Informationszeile C1
600 Informationszeile C1
120 Informationszeile A2
500 Informationszeile B2
600 Informationszeile C2

usw.

D.h. also, ich kann den "Zeilentyp" A, B oder C (die jeweils einen verschiedenen Aufbau haben) leicht anhand des Zeilenbeginns erkennen (und frage dies entsprechend per "if" ab).

Wenn ich nun aber per "for line in eingabedatei" die Aer Zeilen durchgehe, möchte ich dabei auch irgendwie die darunterstehenden Informationen in B und C mitnehmen.

Das klappt teilweise auch, indem ich

Code: Alles auswählen

eingabedatei.next
schreibe und darunter das nächste "if"-Statement schreibe, das sich dann auf Zeile B bezieht und nicht auf A.

Dies klappt aber irgendwie nur teilweise, das Ergebnis gerät irgendwie durcheinander. Problem ist hierbei auch, dass die Informationen, die letztlich in der Ausgabedatei stehen, wild aus den Informationen aus Zeile A, B und C durcheinandergewürfelt sind, d.h. ich muss etwa schreiben (nur Pseudocode:)

Code: Alles auswählen

ausgabedatei = eingabedatei.Informationszeile A[1:3] + eingabedatei.Informationszeile C[2:3] + eingabedatei.Informationszeile B[4:7] + eingabedatei.Informationszeile A[4:5]
D.h. nachdem ich bereits per eingabedatei.next auf Zeile B "gesprungen" bin, muss ich wieder zu A zurückgehen, um weitere Informationen aus A zu bekommen.

Habe leider den Code nicht zuhause vorliegen.... hoffe das war einigermassen verständlich. Ich muss noch dazu sagen, dass es von der Laufzeit her überhaupt nicht perfekt sein muss (da die Dateien eh sehr klein sind) und ich viel mehr nach dem allersimpelstem Weg (falls möglich) ohne Listen etc. suche. Würde mich sehr über eine Antwort freuen...
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!
Remington Steele hat geschrieben:und entsprechenden if-Schleifen durch die Datei und ziehe mir die Informationen aus der ersten Zeile.
Also erstmal...

Lade die ganze Datei und packe die Daten in dafür geeignete Datenstrukturen. Hier bietet sich ggf. auch SQLite an. Dann wird das Problem ein ganzes Stück einfacher.
Das Leben ist wie ein Tennisball.
Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

Hm ok. Habt Ihr zumindest einen Tipp für mich, welche Datenstruktur empfehlenswert - ein kleiner Ansatz?
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Wiederholen sich die Zeilen immer gleich?
Also immer:
120 Informationszeile A1
500 Informationszeile B1
600 Informationszeile C1
600 Informationszeile C1
600 Informationszeile C1
120 Informationszeile A2
500 Informationszeile B2
600 Informationszeile C2
600 Informationszeile C2
600 Informationszeile C2
, oder sind das nicht immer die gleiche Anzahl pro "Datensatz", bzw. kann auch einemal eine fehlen oder mehr als einmal vorkommen?
BlackJack

@Remington Steele: Je nach dem was da genau gemacht werden muss, kann man halt die Grunddatenstrukturen empfehlen. Listen hattest Du ja schon genannt.

Üblicherweise zerlegt man Probleme in kleinere Teilprobleme. Du könntest Dir zum Beispiel im ersten Schritt eine Funktion schreiben, die nur die relevanten Zeilen liefert. Also ohne den Vorlauf.

Als nächstes eine welche die resultierende flache Zeilenstruktur in eine überführt, die eine Liste pro Block enthält.

Dann kannst Du eine Funktion schreiben die nur noch einen Zeilenblock verarbeitet.
Vielleicht in dem der wieder unterteilt wird in Unterlisten mit zusammengehörigen Zeilentypen. Und so weiter. Oder falls es nicht so kompliziert sein muss, kann die Funktion auch schon alle Informationen aus dem Zeilenblock extrahieren und zurück geben.

Diese Funktion kann man dann auf alle Elemente mit der Liste mit den Zeilenblöcken anwenden, und die Ergebnisse in eine Ausgabedatei schreiben.

Wie auch immer Du das unterteilst, Du kannst nicht wirklich eine Zeile zurück springen. Das zu ermöglichen würde das Programm letztendlich eher komplizierter als einfacher machen. Einfacher ist es alle Informationen aus einem Block zu sammeln und dann am Ende des Blocks zu schreiben. Also immer dann wenn ein neuer Block anfängt und das nicht der erste Block ist. Und am Ende der Daten darf man nicht vergessen die Daten aus dem letzten Block zu schreiben. Diese Randfälle sind der Grund warum ich lieber auf eine Verarbeitung des Datenstroms mit Iteratoren und den `itertools` setze, als den Zustandsautomaten für die Verarbeitung selbst zu implementieren. Das mag auf den ersten Blick etwas schwieriger aussehen als selber mit Listen zu hantieren, letztendlich wird der Quelltext aber einfacher und das Programm robuster, wenn man sich um die Randfälle zum Beispiel bei der Gruppenbildung nicht mehr selber kümmern muss, sondern das `itertools.goupby()` überlassen kann.

Beispielhaft könnte das so aussehen:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from itertools import chain, dropwhile, groupby, imap
from operator import itemgetter

A_LINE_PREFIX = '120 '
B_LINE_PREFIX = '500 '
C_LINE_PREFIX = '600 '


def is_block_start(line):
    """Checks if given `line` is the start of a new block."""
    return line.startswith(A_LINE_PREFIX)


def skip_prefix(lines):
    """Skip all lines until the first block starts."""
    return dropwhile(lambda s: not is_block_start(s), lines)


def iter_blocks(lines):
    """Iterates over the blocks.
    
    Returns iterators over the lines of each block.
    """
    def stamp(lines):
        i = -1
        for line in lines:
            if is_block_start(line):
                i += 1
            yield (i, line)
    return (
        imap(itemgetter(1), group)
        for i, group in groupby(stamp(lines), itemgetter(0))
    )


def process_block(lines):
    """Extracts the data from the lines of a block.
    
    Each block should start with an A line, followed by a B line, followed
    by one or more C lines.
    """
    lines = iter(lines)
    a_line = next(lines)
    assert is_block_start(a_line)
    b_line = next(lines)
    assert b_line.startswith(B_LINE_PREFIX)
    c_lines = list(lines)
    assert c_lines and all(s.startswith(C_LINE_PREFIX) for s in c_lines)
    
    return [
        a_line[1:3],
        '_'.join(s[2:3] for s in c_lines),
        b_line[4:7],
        a_line[4:5],
    ]


def main():
    with open('test.txt') as lines:
        with open('test2.txt', 'w') as out_file:
            out_file.writelines(
                '-'.join(xs) + '\n'
                for xs in imap(process_block, iter_blocks(skip_prefix(lines)))
            )


if __name__ == '__main__':
    main()
Das komplizierteste ist wohl die `iter_blocks()`-Funktion. Aber wenn man die erst einmal verstanden hat, kann man das Muster in solchen Situationen immer wieder anwenden um zusammengehörende Elemente zu gruppieren.
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: aber gerade für Gelegenheitsprogrammierer ist eine zustandsbasierte Lösung glaube ich
intuitiver verständlich als eine zustandslose.

Eine Funktion (besser Iterator), die die Eingabe in Blöcke zerlegt und eine Funktion die diese verarbeitet
bietet sich allein schon der Testbarkeit wegen an.

Hier mein Ansatz:

Code: Alles auswählen

TAGS = ('120 ','500 ','600 ')
def iter_blocks(lines):
    lines = iter(lines)
    line = next(lines)
    # Zeilen am Anfang ignorieren
    while line[:4]!=TAGS[0]:
        line = next(lines)
    # Bloecke lesen
    stop = False
    while not stop:
        result = []
        nxt = 0
        try:
            while line[:4]==TAGS[nxt]:
                result.append(line)
                line = next(lines)
                nxt = min(nxt+1,len(TAGS)-1)
        except StopIteration:
            stop = True
        if len(result)<len(TAGS):
            raise ValueError('%s erwartet!'%TAGS[len(result)])
        yield result

with open('test.txt') as lines:
    for bl in iter_blocks(lines):
        a_line = bl[0]
        b_line = bl[1]
        c_lines = bl[2:]
        ...
Grüße
Sirius
Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

Vielen Dank Euch beiden, das hat mir sehr geholfen! Bin gerade dabei, es umzusetzen.
Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

Habe noch eine Frage (habe hierzu auch nach ausgiebigem googlen nichts gefunden):

Es ist ja möglich, mit input bzw. raw_input Eingaben einzulesen. Leider benötige ich aber mehr als eine Zeile, z.B. benötige ich vier oder mehr Zeilen mit verschiedenen Nummern, etwa

44444444332
11111111332
43452454232
54421343554

die ich dann zeilenweise verarbeiten kann. Geht solch eine Eingabe gesamt, so dass ich mir die einzelne Eingabe jeder Zeile sparen kann?
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

danke, leider klappt das aber bei mir nicht (siehe Dein zitierter Beitrag). Kannst Du mir dort weiterhelfen?
Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

edit
cmax
User
Beiträge: 14
Registriert: Dienstag 22. Januar 2013, 21:36

Remington Steele hat geschrieben:[...] Geht solch eine Eingabe gesamt, so dass ich mir die einzelne Eingabe jeder Zeile sparen kann?
Du könntest bei der Eingabe ein Trennzeichen verwenden. Und dann je nach Zielstruktur:

Zeilenliste:

Code: Alles auswählen

lines = raw_input(u'Zeilenliste eingeben:').split(u',')
Text mit Zeilenumbruch

Code: Alles auswählen

text = raw_input(u'Zeilenliste eingeben:').replace(u',',u'\n')
Eingabe: 44444444332,11111111332,43452454232,54421343554

Oder, falls alle Zeilen nicht leer sind, einfach zeilenweise mit leerer Zeile zum Beenden:

Code: Alles auswählen

lines = []

i = 0
while True:
    i += 1
    line = raw_input(u'%3d. Zeile: '%i)
    if len(line)>0:
        lines.append(line)
    else:
        break

#ggf.
text = u'\n'.join(lines)
Remington Steele
User
Beiträge: 51
Registriert: Donnerstag 22. November 2012, 21:50

Spitze, danke...
BlackJack

@cmax: Die ``while``-Schleife ist ein wenig umständlich formuliert. Wenn man die ``if``-Bedingung umdreht und abbricht wenn sie Negation zutrifft, kann man sich den ``else``-Zweig sparen:

Code: Alles auswählen

    lines = list()
    i = 0
    while True:
        i += 1
        line = raw_input('{0:3d}. Zeile: '.format(i))
        if not line:
            break
        lines.append(line)
Mit `itertools.count()` wird man das manuelle hochzählen von `i` los:

Code: Alles auswählen

    lines = list()
    for i in count(1):
        line = raw_input('{0:3d}. Zeile: '.format(i))
        if not line:
            break
        lines.append(line)
Das `i` kann man mit einem Generatorausdruck aus dem Schleifenkörper holen und hat dann eine Schleife über die eingegebenen Zeilen:

Code: Alles auswählen

    lines = list()
    for line in (raw_input('{0:3d}. Zeile: '.format(i)) for i in count(1)):
        if not line:
            break
        lines.append(line)
Das was nun von der Schleife übrig bleibt, wird von `itertools.takewhile()` schon implementiert:

Code: Alles auswählen

    input_lines = (raw_input('{0:3d}. Zeile: '.format(i)) for i in count(1))
    lines = list(takewhile(bool, input_lines))
cmax
User
Beiträge: 14
Registriert: Dienstag 22. Januar 2013, 21:36

@BlackJack:

Das hätte sich aber noch um 50% der Zeilen reduzieren lassen. :wink:

Da sind ein paar schöne neue Funktionen für mich drin. Außerdem hab ich in itertools auch noch izip() kennen gelernt. Das hat mir schon oft gefehlt. :idea:

Dank dir. :D
Antworten