Textdatei formatieren / sortieren

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
Markus_1985
User
Beiträge: 6
Registriert: Donnerstag 13. Januar 2011, 19:18
Wohnort: Mannheim

Hey Leute!

Ich freue mich im Forum aktiv sein zu dürfen :) !

Ich sitze schon ziemlich lange an einer Funktion und sehe den Wald wohl vor lauter Bäumen nicht mehr, deswegen poste ich das hier mal.

Es geht um folgendes: Es existiert eine Textdatei. Den Inhalt dieser Datei möchte ich in die richtige Reihenfolge bringen. In der Textdatei beginnen Befehle mit * und Kommentare mit **. Alles andere sind dann die zu den Befehlen gehörenden Werte.

Also könnte die Textdatei so aussehen:

**Ich bin ein Kommentar
**der über mehrere Zeilen
**geht.
**Ich muss hier bleiben.
*Baum
200, 500, 600
800, 80 , 58
**Funktion Baum
*Haus
50, 58 , 70
87, 12, 17
900, 50
**Funktion Haus
...

Für das Beispiel hier gibt es jetzt zwei Befehle. Die eigentliche Reihenfolge müsste aber so aussehen:


**Ich bin ein Kommentar
**der über mehrere Zeilen
**geht.
**Ich muss hier bleiben.
*Haus
50, 58 , 70
87, 12, 17
900, 50
**Funktion Haus
*Baum
200, 500, 600
800, 80 , 58
**Funktion Baum
...

Die Reihenfolge ist also immer die selbe, auf Haus folgt Baum usw. Es kann auch vorkommen, dass es mehrere Baum & Haus Funktionen gibt, jedoch ist es immer die selbe Anzahl.

Ich hoffe es ist klar geworden um was es geht. Wichtig ist, dass die Anzahl der Werte nach den Befehlen unterschiedlich sein kann.

Ich bin so vorgegangen: die Textdatei wird als Liste eingelesen (readlines), die Zeilennummer werden mit einer RegEx gesucht (über eine andere Methode). Die erhaltene Liste habe ich mit sort() sortiert und mit der eigentlichen Liste verglichen (Liste sortiert und Liste unsortiert). Wenn es eine abweichung gibt, lösche ich die beteffenden Zeilen. Schließlich versuche ich die Zeilen wieder an die richtige Stelle einzufügen mit insert().
Jedoch ist der Ansatz ziemlich kompliziert wie ich finde. Außerdem habe ich bis jetzt noch das Problem, dass die eingefügten Befehle mit ihren Werten in der falschen Reihenfolge erscheinen.

Code: Alles auswählen

## Achtung, es handelt sich hier nicht um echten Code
baumEx = Zeilennummer(n) der RegEx von Baum z.B. [0, 5]
hausEx = Zeilenummer(n) der RegEx von Haus z.B. [3, 8]
inhalt = Inhalt der Datei als Liste 
inhaltKopie = eine Kopie von inhalt
## Achtung, es handelt sich hier nicht um echten Code

for j in range(len(unsortierteListe)):
	if (unsortierteListe[j] != sortierteListe[j]):
		for c in range(unsortierteListe[j]+1, len(inhaltKopie)):
			if inhalt[c].startswith('*') == True and inhalt[c].startswith('**') == False:
				break
            for a in range(unsortierteListe[j], c):
                del inhalt[unsortierteListe[j]]
Fast das gleiche habe ich nun nochmal um die Listenelemente wieder einzufügen. Aber leider scheint es doch schwieriger als gedacht und funktioniert auch nicht.

Hat jemand eine bessere Idee?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Markus_1985 hat geschrieben: Es geht um folgendes: Es existiert eine Textdatei. Den Inhalt dieser Datei möchte ich in die richtige Reihenfolge bringen. In der Textdatei beginnen Befehle mit * und Kommentare mit **. Alles andere sind dann die zu den Befehlen gehörenden Werte.
Wer hat sich so ein Format ausgedacht? Ist das wirklich gegeben oder hast Du Dir das selber ausgedacht bzw, kannst an dem Format was ändern?

Also ganz trivial ist das Format nicht umzuändern; ein wenig Aufwand macht das sicherlich.

Wozu Du da etwas sortierst ist mir nicht klar! Inwiefern spielt denn die Lexikografische Ordnung eine Rolle in Bezug auf die Reihenfolge der Befehle?

Diese komplette Indexzugreiferei sieht zumindest ein wenig umständlich aus. Ich denke man sollte das Parsing nicht unbedingt Zeilenweise durchführen, da die Blöcke ja über mehrere Zeilen gehen können. Hat man die Blöcke identifiziert, so könnte man diese leichter in der richtigen Reihenfolge ausgeben.

Allerdings beschreib doch mal bitte exakt, wie die Reihenfolge definiert ist! Immer Block Haus gefolgt von einem Block Baum? Oder alle Blöcke Haus und dann alle Blöcke Baum? Oder alternierend? Wenn ja, wie werden die richtigen Pärchen gefunden? Viele offene Fragen...

Die Kommentare bezüglich der Funktionen sind übrigens eher verwirrend - in beiden Fällen (stehen ja unter der Definition; ich hätte sie drüber erwartet).
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Markus_1985
User
Beiträge: 6
Registriert: Donnerstag 13. Januar 2011, 19:18
Wohnort: Mannheim

Danke für deine Antwort Hyperion!

Nein, an dem Format lässt sich leider nichts ändern, dies ist so gegeben :(.

Eine Sortierung macht durchaus Sinn, da bei großen Textdateien mit vielen Elementen schnell die Übersichtlichkeit leidet.

Ich möchte Dir mal eine etwas größere "Textdatei" vorstellen, vielleicht wird es dann einfacher zu verstehen.
Der Anfang der Datei ist immer mit einem Mehrzeiligen Kommentar versehen. Dieser spielt bei der Sortierung keine, da er einfach an Ort und Stelle gelassen wird.

Jetzt soll auf jeden Baum ein Haus und ein Auto folgen: Baum --> Haus --> Auto ---> Baum --> Haus --> Auto ...
Die Kommentare sind (leider und auch zu meiner Verwirrung) immer unter den Funktionen und können z.B. auch über zwei Zeilen gehen. Diese sollen aber dort bleiben.

**Ich bin ein Kommentar
**der über mehrere Zeilen
**geht.
**Ich muss hier bleiben.
*Baum
200, 500, 600
800, 80, 58
**Funktion Baum
*Haus
50, 58, 70
87, 12, 17
900, 50
**Funktion Haus
*Auto
46, 12, 17
600, 700, 500
**Funktion Auto
*Haus
50, 58, 70
87, 12, 17
900, 50
**Funktion Haus
*Auto
46, 12, 17
600, 700, 500
**Funktion Auto
*Baum
200, 500, 600
800, 80, 58
**Funktion Baum

Hier wäre es also falsch und müsste sortiert werden zu Baum --> Haus --> Auto. Ich hoffe ich konnte nun alle Unklarheiten beseitigen. Wenn ich an dem Format etwas ändern könnte, hätte ich das schon längst gemacht und mich nicht die letzten Tage damit beschäftigt 8) .

Nun nochmal zu meinem Ansatz:

ich wollte mit Hilfe der Regulären Ausdrücke die Zeilen finden wo Baum, Haus und Auto stehen. :
Als Beispiel:

Baum: [5, 15]
Haus: [8, 20]
Auto: [10, 18]

Ich erhalte also im ersten Schleifendurchlauf (For-Schleife, siehe Code) folgendes:

unsortierteListe = [5, 8, 10]
sortierteListe = [5, 8, 10]

Es ist also alles Ok.

Im zweiten Schleifendurchlauf

unsortierteListe = [15, 20, 18]
sortierteListe = [15, 18, 20]

Man sieht, es muss ab 20 bis x mit 18 bis x ersetzt werden und umgekehrt.

Ich hoffe das war verständlich genug?
BlackJack

@Markus_1985: Also ich finde die Zeilenindexerei auch nicht schön. Ich würde da auch eher mit Strukturen arbeiten, die die "Blöcke" wiederspiegeln. Also zum Beispiel das ganze in Teillisten mit Funktionen aufbrechen.

Ist denn garantiert, dass es immer die drei Komponenten Baum, Haus, Auto gibt, nur eben nicht die Reihenfolge in der Datei innerhalb so eines Blocks? Also kann man davon ausgehen, dass nach dem Anfangskommentar immer Gruppen von drei Funktionen kommen, die zusammengehören? Dann könnte man dass doch einfach als Ansatz nehmen und: ersten Kommentarblock in Zieldatei kopieren. Und dann jeweils drei Funktionen lesen und die in der richtigen Reihenfolge rausschreiben. Die kann man ja in ein Dictionary stecken, das 'haus', 'baum', und 'auto' als Schlüssel hat.
Markus_1985
User
Beiträge: 6
Registriert: Donnerstag 13. Januar 2011, 19:18
Wohnort: Mannheim

@BlackJack
Ja die Zeilenindexerei ist wirklich nicht schön und macht das ganze auch ziemlich "Fehleranfällig".
Es ist garantiert, dass es immer die drei Komponenten gibt und die Reihenfolge manchmal falsch ist.

Nur verstehe ich jetzt nicht ganz wie du es meinst. Du willst die Datei also so aufspalten:

**Anfangskommentar (wird einfach reingeschrieben, klar)

<---Split-->

*Baum
200
**Funktion Baum
*Haus
50 ...
**Funktion Haus
*Auto
46...
**Funktion Auto

<---Split-->

*Haus
50...
**Funktion Haus
*Auto
46...
**Funktion Auto
**weiterer Kommentar!
*Baum
200
**Funktion Baum

Und wie trenne ich das ganze jetzt auf? Woher weiß ich, wie lang die einzelnen Funktionen sind und wie bringe ich sie mit Hilfe des Dict. in die richtige Reihenfolge? :)

Was ich halt bei der Indexierung schön fand, war die einfache Art es Zeile für Zeile in die Zieldatei zu schreiben. Das verstehe ich nicht so ganz, wie du das verwirklichen willst.

Vielen Danke für eure Hilfe :)!
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ohne jetzt genau verstanden zu haben, wo das Problem ist, würde ich so vorgehen: Alles einlesen und daraus Objekte machen. Dabei Zeilennummern zu Referenzen auf die jeweiligen Objekte machen. Dann diesen entstanden Baum (oder gerichteten Graf) passend sortieren, dann das ganze wieder in eine Textdatei serialisieren. Der Vorteil: Man hat drei unabhängige Teilaufgaben, die für sich einfacher zu realisieren sind.

Stefan
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also ich habs mal so versucht:
http://paste.pocoo.org/show/320238/

Allerdings ist der Kommentar zu einem Funktionsblock im Moment zwingend erforderlich. sma kann das sicherlich smarter lösen ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Markus_1985: Die Dreier-Funktionsblöcke würde ich noch weiter aufteilen in Listen mit allen Zeilen die zu einer Funktion gehören. Beziehungsweise wäre mein erster Schritt den Strom von Zeilen in Blöcke, also Listen von Zeilen aufzusplitten, die zu einer Funktion gehören. Diese Listen kann man dann zu Listen mit je drei Funktionen zusammenfassen. Und die lassen sich sortieren. Und dann kann man sie raussschreiben. Mal in Code gegossen und mit Deinen letzten Beispieldaten getestet:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from itertools import count, groupby, islice
from operator import itemgetter

FUNC_NAME2ORDER = dict((n, i) for i, n in enumerate(['Baum', 'Haus', 'Auto']))


get_first, get_second = map(itemgetter, xrange(2))


def is_function(line):
    return line.startswith('*') and not line.startswith('**')


def iter_blocks(lines):
    def stamp():
        counter = count()
        i = counter.next()
        for line in lines:
            if is_function(line):
                i = counter.next()
            yield (i, line)
    return (map(get_second, g) for _, g in groupby(stamp(), get_first))


def grouper(item_count, iterable):
    while True:
        result = list(islice(iterable, item_count))
        if not result:
            break
        yield result


def get_function_key(block):
    return FUNC_NAME2ORDER[block[0][1:].strip()]


def main():
    result = list()
    with open('test.txt') as lines:
        blocks = iter_blocks(lines)
        result = blocks.next()  # Initial comments.
        for function_group in grouper(3, blocks):
            function_group.sort(key=get_function_key)
            for block in function_group:
                result.extend(block)
    print ''.join(result)


if __name__ == "__main__":
    main()
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Oh mann BlackJack, wie Du mich deprimierst mit Deinem Code ;-)

Muss ich mir gleich mal en detail angucken, einige Sachen durchschaue ich auf Anhieb noch nicht. Generell sieht das aber richtig elegant aus :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Markus_1985
User
Beiträge: 6
Registriert: Donnerstag 13. Januar 2011, 19:18
Wohnort: Mannheim

Danke Hyperion und BlackJack für euren Code! Echt super!
Manchmal ist es einfach besser jemanden mit mehr Erfahrung zu fragen, sonst kommt man wohl nie zu einem Ergebnis :).

Eine Frage an BlackJack:

Wie schon gesagt habe ich mehr als die drei Funktionen. Und da diese immer mit dem Baum anfangen, heißt es eigentlich immer z.B.

*Baum, Name=#Wert
200
**Funktion Baum
...

Da ich deinen Code nocht nicht zu 100% nachvollziehen konnte (liegt wohl an meinem Können), wollte ich Dich fragen, wo ich die Baum *Funktion abschneiden kann?
Da ich ja weiß, wann das Gleichheitszeichen kommte, dachte ich mir ich mache das mit [:12]?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Markus_1985 hat geschrieben: Wie schon gesagt habe ich mehr als die drei Funktionen. Und da diese immer mit dem Baum anfangen, heißt es eigentlich immer z.B.

*Baum, Name=#Wert
200
**Funktion Baum
...
Es ist natürlich schlecht, wenn sich die Grammatik deines Formats ständig ändert oder Du uns wichtige Aspekte vorenthältst ;-) Ich würde für das Aufsplitten von solchen Wertpaaren dann doch auf einen RegExp zurückgreifen. Alternativ wenigstens mit find("=") arbeiten oder sogar mit split("="). Einen festen Index halte ich dann doch für zu starr, gerade wenn das Format dann doch nicht so fest gezurrt ist?

Mich würde aber doch mal interessieren, was es mit diesem Format auf sich hat?

Edit: Ich habe BlackJacks Code jetzt komplett nachvollzogen. Die Funktionen werden in der iter_blocks() Funktion herausgefiltert. Allerdings auch der einleitende Kommentar. Jeder Block wird dann in eine Liste verpackt, in der alle zu einem Block gehörigen Zeilen enthalten sind. Die Zeile

Code: Alles auswählen

*Baum, Name=#Wert
wäre in einem Block also immer der 0. Indexeintrag.

Ich frage mich nur gerade, was Du überhaupt mit dem Wert anfangen willst? Hier ging es ja um das Umsortieren und nicht um einen "echten" Parser, mit dem Du an Werte rankommst, um damit später zu hantieren. Wenn doch, müßte man das ganze dann doch anders aufziehen und einen AST basteln.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Markus_1985
User
Beiträge: 6
Registriert: Donnerstag 13. Januar 2011, 19:18
Wohnort: Mannheim

Entschuldigt, dass ich dieses doch wichtige Detail doch vergessen habe!

Es handelt sich um eine Textdatei worin die Daten für die (Preis)kalkulation von Bilderrahmen gespeichert sind.

Da das Programmschon ein wenig älter (läuft unter Win 95), aber es läuft und macht seine Sache anscheinend gut (Never change a running system ;) ). Da es sich um mein Onkel handelt, konnte ich schlecht nein sagen ;(.

Da natürlich in dieser Datei schon X mal etwas hin und her kopiert/verändert wurde und es einfach nicht schön formatiert ist,wurde ich gebeten wenig Struktur rein zu bringen. Also ein guter Weg um mich mal wieder in Python einzuarbeiten. Außerdem führt sowas sehr schnell zu Fehlern, was natürlich zu vermeiden ist, da sonst falsche Rechnung raus gehen.

Leider kann ich euch natürlich nicht die Datei hier rein stellen.

Das erste Element ist dann natürlich der Rahmen:

*RAHMEN, ARTIKELNUMMER=#Wert
#Wert
**Letzte Änderung 05.10.2008
*PREIS, EINKAUF
#Wert, #Wert, #Wert
**Letzte Änderung 18.11.2005
**Preis pro laufender Meter!
*PREIS, VERKAUF
#Wert, #Wert, #Wert
**Letzte Änderung 18.11.2005
*HAENDLER
#Wert, #Wert, #Wert
**Letzte Änderung 18.11.2005
**Preis pro laufender Meter!
*FARBE
#Wert, #Wert, #Wert
**Letzte Änderung 13.09.2010
*BREITE
#Wert, #Wert, #Wert
**Letzte Änderung 27.01.2010
*VERSCHNITT, KALKULIERT=#Wert
#Wert, #Wert, #Wert
**Letzte Änderung 13.09.2010
...

Insgesammt also 9 Elemente, welche wie schon beschrieben in die richtige Reihenfolge zu bringen sind.


Ich denke es ist also am besten wenn ich die einzelnen Funktionen per RegEx suche, da Preis z.B. zwei mal vorhanden ist :/.

Die ganze Sache bereitet mir ziemliches Kopfzerbrechen :K

Edit:
Das ganze wird also nicht von mir geparst, sondern von dem Programm. Mir geht es nur ums sortieren.
BlackJack

@Markus_1985: Also wenn das Muster so stimmt, und ich richtig interpretiere was feste Zeichenketten und was variable Teile sind, könntest Du die erste Zeile von jedem "Funktionsblock" bis zum ersten '=' oder komplett, falls kein '=' enthalten ist, als eindeutigen Wert nehmen, oder!?
Antworten