Lange Zeile nach x Zeichen umbrechen

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
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

Hallo,

ich habe zwei Dateien mit jeweils nur einer Zeile Inhalt. Diese Zeile ist sehr lang und beinhaltet keine Leerzeichen.
Die Dateien unterscheiden sich in einigen Bytes.
Da mein Diff-Programm zeilenbasiert arbeitet, möchte ich gerne die Zeile nach jeweils x Zeichen umbrechen, um so im Viewer den Unterschied besser sehen zu können.

Ich habe das jetzt folgendermaßen gelöst, weiß aber nicht, ob das die optimale Lösung ist und würde Euch bitten, mal einen Blick darauf zu werfen.

Code: Alles auswählen

def crlf(src, target, count):
    with open(src) as src_file:
        new = ''
        for line in src_file:
            counter = 0
            for char in line:
                new += char
                counter += 1
                if counter % count == 0:
                    new += '\n'

    with open(target, 'w') as target_file:
        target_file.write(new)
Besten Dank.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du könntest einfach das ``textwrap``-Modul nutzen :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

Ich werde dann auch gleich noch mal einen Hinweis auf das `difflib`-Modul aus der Standardbibliothek los. :-)
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

Vielen Dank für Eure Antworten :)
Hyperion hat geschrieben:Du könntest einfach das ``textwrap``-Modul nutzen :-)
Interessantes Modul :)
Allerdings braucht die Wrap-Routine für die Anzeige meines umgebrochenen Strings auf der Console 25 Sekunden, während meine Routine 4 Sekunden benötigt :shock:
Hast Du eine Idee, wie das sein kann? (Stringlänge 700000 Byte)
BlackJack hat geschrieben:Ich werde dann auch gleich noch mal einen Hinweis auf das `difflib`-Modul aus der Standardbibliothek los. :-)
Ok, das schaue ich mir später mal an :)
BlackJack

@sfx2k: Das `textwrap`-Modul kann mehr und macht eigentlich ja auch ein bisschen etwas anderes als Du da brauchst. Das ist der Sonderfall den man in den Eingabedaten eigentlich nicht haben möchte, weil die ”Lösung” so hässlich ist Worte mittendrin einfach umzubrechen.

Allerdings ist Deine Lösung auch nicht wirklich gut weil da wenn's blöd läuft so an die 228 GiB an Daten im Arbeitsspeicher herumkopiert werden und Du wahrscheinlich einfach nur Glück hast, dass dieser Sonderfall in CPython in der Regel optimiert abläuft. Verlassen darf man sich da aber nicht drauf. Zeichenketten sind unveränderlich. Auch ein ``+=`` erzeugt eine neue Zeichenkette mit der Länge der alten beiden und die werden dann in diesen Speicherbereich kopiert. Wenn man das also in einer Schleife macht, in der diese Zeichenkette immer länger und länger wird, werden immer mehr und mehr Daten in jedem Durchlauf kopiert. Da Du das Zeichenweise machst, im ersten Durchlauf 1 Zeichen, im zweiten 2, im dritten 3, und im 700tausendsten 700.000, und da sind noch nicht mal die '\n' mitgezählt. Gesamt macht dass dann also mindestens die Summe von 1 bis 700.000:

Code: Alles auswählen

In [2]: sum(xrange(700000))
Out[2]: 244999650000L
Was Dich hier rettet ist das CPython bei Zeichenketten auf die es nur eine Referenz gibt beim ``+=`` schummelt und die Zeichenkette doch veränderbar macht. Sieht ja ausser dem einzigen Besitzer der Referenz niemand, und der bekommt das erwartete Ergebnis. Das geht so aber nur bei einer Speicherverwaltung die auf Referenzzähler setzt. Bei alternativen Python-Implementierungen wie IronPython oder Jython, die den jeweiligen Garbage Collector von .NET bzw. der JVM verwenden, geht das nicht, und auch bei CPython ist nicht garantiert das die Speicherverwaltung nicht irgendwann einmal geändert wird.

Es sieht auch recht kompliziert aus. Ich würde einfach die Eingabedatei in Blöcken zu x Bytes laden und dann jeweils mit einem '\n' in die Zieldatei schreiben.
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

BlackJack hat geschrieben:@sfx2k: Das `textwrap`-Modul kann mehr und macht eigentlich ja auch ein bisschen etwas anderes als Du da brauchst. Das ist der Sonderfall den man in den Eingabedaten eigentlich nicht haben möchte, weil die ”Lösung” so hässlich ist Worte mittendrin einfach umzubrechen.
Ja, sicher ein Sonderfall; in der Regel möchte man schon logisch umbrechen.
Das dachte ich mir auch, dass das Textwrap-Modul dafür nicht geschaffen worden ist :)
BlackJack hat geschrieben: Allerdings ist Deine Lösung auch nicht wirklich gut
Daher fragte ich nach einer guten Lösung ;)
BlackJack hat geschrieben: weil da wenn's blöd läuft so an die 228 GiB an Daten im Arbeitsspeicher herumkopiert werden und [...]
Danke für diese ausführliche Erläuterung!
BlackJack hat geschrieben: Ich würde einfach die Eingabedatei in Blöcken zu x Bytes laden und dann jeweils mit einem '\n' in die Zieldatei schreiben.
Hört sich gut an; vielen Dank :)

Habe es jetzt mal so umgesetzt:

Code: Alles auswählen

def crlf(src, target, count):
    with open(src) as src_file:
        with open(target, 'w') as target_file:
            while True:
                temp = src_file.read(10)
                if not temp:
                    break
                target_file.write(temp + '\n')
BlackJack

@sfx2k: Der Funktionsname ist sehr kryptisch und auch nur unter Windows richtig. Und anstelle der 10 sollte wohl ``count`` stehen. :-)

Ungetestete Alternative:

Code: Alles auswählen

from functools import partial


def insert_newlines(source_filename, target_filename, count):
    with open(source_filename) as source_file:
        with open(target_filename, 'w') as target_file:
            target_file.writelines(
                block + '\n'
                for block in iter(partial(source_file.read, count), '')
            )
Oder falls Dir das zu verschachtelt ist mit einem zusätzlichen benannten Zwischenergebnis:

Code: Alles auswählen

def insert_newlines(source_filename, target_filename, count):
    with open(source_filename) as source_file:
        with open(target_filename, 'w') as target_file:
            blocks = iter(partial(source_file.read, count), '')
            target_file.writelines(block + '\n' for block in blocks)
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

BlackJack hat geschrieben:@sfx2k: Der Funktionsname ist sehr kryptisch und auch nur unter Windows richtig.
Ja, ich benutze es ja auch nur unter Windows :)
Aber hast recht; bei einer Sprache, die unter mehreren Systemen läuft, sollte man das berücksichtigen.
Ich tue mich nur immer so schwer, geeignete Bezeichner zu finden :oops:
BlackJack hat geschrieben:Und anstelle der 10 sollte wohl ``count`` stehen. :-)
Upsi :mrgreen:
BlackJack hat geschrieben: Ungetestete Alternative:

Code: Alles auswählen

from functools import partial

def insert_newlines(source_filename, target_filename, count):
    with open(source_filename) as source_file:
        with open(target_filename, 'w') as target_file:
            blocks = iter(partial(source_file.read, count), '')
            target_file.writelines(block + '\n' for block in blocks)
Das sieht doch mal schick aus :)
Danke!
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

BlackJack hat geschrieben:

Code: Alles auswählen

def insert_newlines(source_filename, target_filename, count):
    with open(source_filename) as source_file:
        with open(target_filename, 'w') as target_file:
            blocks = iter(partial(source_file.read, count), '')
            target_file.writelines(block + '\n' for block in blocks)
Mein lieber Herr Gesangsverein.
Ich habe diese Routine jetzt mal verinnerlicht - da wäre ich nie so drauf gekommen (Partial) :shock:
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

sfx2k hat geschrieben:Ich habe diese Routine jetzt mal verinnerlicht - da wäre ich nie so drauf gekommen (Partial) :shock:
Das ist doch aber nicht mehr als ein Funktionsobjekt (wie es auch `lambda` erstellt), `iter` und sein 2. Parameter sind da doch interessanter ;)
Antworten