split() Funktion re-implementieren, dass sie iterable ist.

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.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Montag 18. Februar 2008, 15:26

Hi,

ist es denkbar, dass man die split() Funktion re-implementiert, dass sie iterable ist, statt immer eine komplette Liste zurückgibt?

Hintergrund der Frage ist die Problematik rund um grosse (oder grössere) Eingabedaten, wo es zu bevorzugen wäre, wenn da keine komplette monster-mörder Liste zurückgegeben wird, stattdessen man die einzelnen split() Resultate "häppchenweise" bekommt.

Quasi eine Funktion, welche die Elemente einer split() Funktion einzeln zurück-yieldet.

Ist split() eigentlich in Python implementiert? Wenn ja, wo kann man den source-code davon angucken?
BlackJack

Montag 18. Februar 2008, 15:50

Klar kann man das machen. In einer Schleife mit `index()`-Methode die Trennzeichenfolge suchen und dann die enstprechenden "slices" ``yield``\en. Man kann `index()` auch einen Startoffset mitgeben. `str.split()` dürfte in C implementiert sein. Quelltext gibt's natürlich auf www.python.org :-)
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Montag 18. Februar 2008, 16:01

Und dann nach split.c suchen?????
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Montag 18. Februar 2008, 16:07

PS: BITTE KEINE LÖSUNG POSTEN!!!

Ich komme selbst drauf, brauche nur bissle Zeit.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Montag 18. Februar 2008, 16:15

akis.kapo hat geschrieben:PS: BITTE KEINE LÖSUNG POSTEN!!!
...diese Aussage ist hier eher selten. 8)

.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Montag 18. Februar 2008, 16:22

Ich bin nicht doof. Nur zu beschäftigt. Nach Feierabend nehme ich mir das Problem vor. Hab schon ne wage Idee wieviele Indizes man mindestens unterscheiden können muss und wie man sie am logischten benennt...
BlackJack

Montag 18. Februar 2008, 16:22

@akis.kapo: Eher nach `stringobject.c` und da dann nach der Funktion `string_split()`. Ist jedenfalls bei der 3.0er Beta dort zu finden.
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Montag 18. Februar 2008, 17:05

Das Modul 'itertools' könnte dir helfen.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Montag 18. Februar 2008, 18:09

Hi

Also ich würde das so machen:

Code: Alles auswählen

token, rest = data.split(trennzeichen, 1)
Dass in einer Schleife bis rest = '' ist und jedesmal das token yielden.

Gruss

*edit* ups split gibt ja nur 1 Element zurück wenns fertig ist, also halt vorher schauen ob len(res) == 2 ist und daraus entscheiden ob fertig oder noch nicht
BlackJack

Montag 18. Februar 2008, 18:49

Das ist aber mit sehr viel umkopieren von Daten verbunden. Gerade bei grossen Datenmengen und vielen Elementen hat das eine furchtbare Laufzeit.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Dienstag 19. Februar 2008, 23:24

@BlackJack

Hier meine erste Version:
Bitte mal durchchecken und kritisieren.
(Aber nicht zu stark kritisieren, sonst guck ich nicht mehr ins Forum!) :evil:

Dateien:

test1.txt:

Code: Alles auswählen

aaa//bbb//ccc//ddd//eee
test2.txt:

Code: Alles auswählen

Dies ist ein wunderschöner Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline Zeichen '\n\n'.

Dies ist ein bescheuerter Abschnitt,
der durch andere Abschnitte durch einen
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline Zeichen '\n\n'.

Dies ist ein überflüssiger Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline Zeichen '\n\n'.

Dies ist ein bomchickawahwah Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline Zeichen '\n\n'.
itersplit.py:

Code: Alles auswählen

#!/usr/bin/env python

def iterable_split(filename, sep=None, blocksize=None):

        eingabe_band = open(filename,'r') # hier lesen wir
        arbeits_band = '' # hier suchen wir

        if sep is None:
                sep = '\n' # default setzen

        if blocksize is None:
                blocksize = 512 # default setzen
        else:
                blocksize = max(blocksize,1) # bs muss > 0 sein

        current_block = blocksize * '?' # nur spielerei ...

        while not len(current_block) < blocksize: # solange wir "ganze" blöcke lesen ...
                current_block = eingabe_band.read(blocksize) # lese genau einen block
                arbeits_band += current_block # füge ihn ans ende der arbeitsbands
                find_index = 0 # neue suche, neues glück
                while find_index > -1: # solange wir was finden ...
                                find_index = arbeits_band.find(sep) # markiere trefferstartpunkt
                                if find_index > -1: # wenn wir überhaupt treffen ...
                                                treffer = arbeits_band[:find_index] # merke treffer
                                                arbeits_band = arbeits_band[find_index+len(sep):] # lösche treffer+sep vom anfang des arbeitsbands
                                                yield treffer # gib treffer zurück
        if len(arbeits_band) > 0: # falls die dateigrösse in bytes nicht = vielfaches der blockgrösse, gebe "rattenschwanz" zurück
                yield arbeits_band # restgeld zurückgeben

for i in iterable_split('test1.txt', '//', 8):
        print i

for i in iterable_split('test2.txt', '\n\n'):
        print i
EDIT: Ausgabe vergessen, nachgeliefert:

Code: Alles auswählen

$ ./itersplit.py
aaa
bbb
ccc
ddd
eee
Dies ist ein wunderschöner Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline zeichen '\n\n'.
Dies ist ein bescheuerter Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline zeichen '\n\n'.
Dies ist ein überflüssiger Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline zeichen '\n\n'.
Dies ist ein bomchickawahwah Abschnitt,
der durch andere Abschnitte mit einem
Absatz getrennt wird. Einen Absatz
erkennt man durch zwei aufeinander
folgende Newline zeichen '\n\n'.
Irgendwie erinnert mich der Stil aber an C statt Python. :?
Zuletzt geändert von akis.kapo am Mittwoch 20. Februar 2008, 00:03, insgesamt 2-mal geändert.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Dienstag 19. Februar 2008, 23:43

Mein erster Gedanke war: igitt, Inline-Kommentare!

Der zweite war, dass das ja etwas ganz anderes macht, als ``split()``. Deines splittet eine Datei die es auch selbst öffnet (ohne Fehlerbehandlung). Also übergibtst du split_iterable() gar kein Iterable sondern einen Dateinamen (der zwar auch iterabel ist, aber der wird nicht gesplittet). Worum gehts jetzt? Einen Split-Generator oder eine Datei via Generator zu splitten?
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Dienstag 19. Februar 2008, 23:48

Es geht darum, split()-Verhalten nachzubauen, aber statt der Rückgabe einer kompletten Liste, die einzelnen Elemente zu yielden.

Ich habe einfach Dateien gewählt in diesem Prototyp, weil ich mir verschiedene Testdateien erstellt habe.
Man hätte auch Strings definieren können statt mit Dateien zu arbeiten.

Es geht ja ums Prinzip. Das mit dem Dateien kann man ja umbauen.

EDIT:
PS: Mein Programm hier hat keine Kommentare. (Ich hab eh alles im Kopf).
Die Kommentare seht nur ihr im Forum in diesem Post.
BlackJack

Mittwoch 20. Februar 2008, 00:16

Die Funktion sähe massiv anders aus, wenn sie sich nicht um die Datei kümmern würde, sondern wirklich nur eine Zeichenkette splitten würde.

Default-Werte sollte man, wenn das möglich ist, gleich in der Argumentliste angeben. Bei `sep` und `blocksize` hätte man das problemlos tun können, weil sowohl Zeichenketten als auch Zahlen "immutable" sind. Da kann es also nicht zu unerwünschten Nebeneffekten kommen. Und eine Blockgrösse kleiner 1 würde ich nicht "heimlich" korrigieren, sondern höchstens dem Aufrufer als Ausnahme um die virtuellen Ohren hauen. Aus dem Zen: "Errors should never pass silently."

Das blockweise "addieren" und dann das Löschen von Treffer und Separator vom `arbeits_band` ist wieder mit sehr viel unnötigem Umkopieren von Zeichenkettendaten verbunden. Du solltest das `iter_split()` (der Name passt besser zu der Funktion) nur auf einer Zeichenkette definieren und wirklich nur die Treffer dort heraus kopieren und ``yield``\en. Wie gesagt, die `find()`-Methode kennt noch mehr Argumente als nur die gesuchte Zeichenkette!

Und wenn man dass dann auf Dateien anwenden will, die richtig gross sind, so bis 2 GiB auf 32-Bit-Systemen, dann kann man anstatt einer Zeichenkette immer noch ein `mmap.mmap()`-Objekt an die Funktion übergeben und braucht sich nicht selbst um so kleinliches blockweises Einlesen kümmern.
Benutzeravatar
akis.kapo
User
Beiträge: 127
Registriert: Freitag 1. September 2006, 12:58

Mittwoch 20. Februar 2008, 13:54

@BJ

Ok, dann hier der Gegenvorschlag:

Code: Alles auswählen

#!/usr/bin/env python

def iter_split(instring, sep='\n'):
    len_sep = len(sep)
    find_index = 0
    pivot = 0
    while find_index > -1:
        find_index = instring.find(sep, pivot)
        if find_index > -1:
            result = instring[pivot:find_index]
            pivot = find_index + len_sep
            yield result

mystring = 'aaa//bbb//ccc//ddd//eee'

for i in iter_split(mystring, '//'):
    print i
Allerdings ist mir nicht 100%ig klar, wie ich das mache,
dass es per mystring.iter_split(foo, bar) geht, statt als iter_split(string, ...).
Antworten