Grosse Textdateien schnell editieren

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
Siggi2008
User
Beiträge: 6
Registriert: Donnerstag 27. November 2008, 10:13

Hallo zusammen!

Ich muss grosse Textdateien (bis zu 50 MB) auslesen und editieren. Bislang habe ich dazu die 'Split'-Funktion verwendet. Allerdings dauert die Operation relativ lang.

Da ich eine Vielzahl von grossen Textdateien bearbeiten muss, würde ich gern wissen, ob es eine bessere (d.h. vor allen Dingen schnellere) Möglichkeit gibt.

Ich bin Euch sehr dankbar für Eure Hinweise!

Viele Grüße,
Siggi
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Moin,
Siggi2008 hat geschrieben:Ich muss grosse Textdateien (bis zu 50 MB) auslesen und editieren. Bislang habe ich dazu die 'Split'-Funktion verwendet. Allerdings dauert die Operation relativ lang.
Die Dateien sind auch relativ groß ;-) .

Was machst du denn genau? Ums Einlesen und Schreiben der Datei kommst du nicht herum. Wenn das alleine lange dauert, dann kauf dir ne schnellere Platte. Aber vielleicht ist dein "Editieren" ja das Problem? Zeig mal etwas Code, dann kann man dir besser helfen.

Gruß,
Manuel
Siggi2008
User
Beiträge: 6
Registriert: Donnerstag 27. November 2008, 10:13

Hallo helduel,

anbei ein Auszug aus dem Code, den ich bislang verwende:


+++++++++++++++++++++++++++++++++++++++++++++++
CODE:
+++++++++++++++++++++++++++++++++++++++++++++++

def write_beam_elems(filestring, elems, output_filename):
# Generate the correct filename from output_filename
output_path = os.path.dirname(output_filename) # Get the path name
output_fn = os.path.basename(output_filename) # Get the filename
predicate = re.compile('\.')
e = predicate.split(output_fn) # Split the filename by '.'
output_file_extension = e[-1] # Set the extension to the last string after the '.'
output_fn = output_fn[:len(e)+2]

# Split the *.out file
predicate = re.compile('STRESSES IN.*IN ELEMENT\s+\d+, POINT\s+\d+')
elem_list = predicate.split(filestring)

i = 1
j = 1
while i <= len(elem_list)-2:
try:
filename = "%s/%s%i.%s" % (output_path, output_fn, j, output_file_extension)
f = open(filename, "w")
beam_list = sort_fibres(elem_list, elem_list[i+1])
f.write(beam_list2str(beam_list))
f.close
tem_output_filename = "%s/%s%i.%s" % (output_path, output_fn, j, '.tem')
create_tem(tem_output_filename, elems[j-1], beam_list)
i += 2
j += 1
except IOError:
print "Error opening file %s. Does the path '%s/' exist?" % (filename, output_path)
break
+++++++++++++++++++++++++++++++++++++++++++++++


Vielen Dank im Voraus!

Siggi
abgdf

Wenn Dir ".split()"-Operationen zu langsam sind, ist Dir Code in Sprachen wie Python insgesamt zu langsam.
Du müßtest das dann in C auf Byte-Ebene angehen. Ist aber sehr mühsam.

Allein sowas:

Code: Alles auswählen

fh = file("l.txt", "r")
a = fh.readlines()
fh.close()
sieht dann bei mir schon z.B. so aus:

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *readFile(const char *filename);
int writeFile(const char *filename, char *a);

char *strMalloc(unsigned int s);
char *strRealloc(char *a, unsigned int s);

int main(void)
{
    char *a = readFile("l.txt");
    puts(a);
    return 0;
}

char *readFile(const char *filename)
{
    /* Open a text-file mostly regardless of its size,
       store it in memory and return a char-pointer pointing
       to the memory-adress. */

    FILE *fp;

    char *a;

    char buff[1000];

    int i;

    int size;

    size = 0;

    buff[999] = '\0';

    a = strMalloc(1000);

    if ((fp = fopen(filename, "r")) == NULL)
    {
        puts("Error opening file; probably file not found.");
        exit(1);
    }

    while (!feof(fp))
    {
        for (i = 0; i < 999; i++)
        {
            buff[i] = fgetc(fp);
            size++;
            if (buff[i] == EOF)
            {
                buff[i] = '\0';
                break;
            }
        }

        size++;

        a = strRealloc(a, size);

        strcat(a, buff);
    }

    fclose(fp);

    return a;
}


int writeFile(const char *filename, char *a)
{
    FILE *fp;

    if ((fp = fopen(filename, "w")) == NULL)
    {
        puts("Error writing file.");
        return 1;
    }
        
    fprintf(fp, "%s", a);

    fclose(fp);

    return 0;
}


char *strMalloc(unsigned int s)
{
    /* Allocates memory for a string, testing if allocation has
       been successfull. */

    char *a;
    if ((a = (char *) malloc(s * sizeof(char))) == NULL)
    {
        puts("Error allocating memory.");
        exit(1);
    }

    return a;
}


char *strRealloc(char *a, unsigned int s)
{
    /* Reallocates memory of a string, testing if reallocation has
       been successfull. */

    if ((a = (char *) realloc(a, s * sizeof(char))) == NULL)
    {
        puts("Error reallocating memory.");
        exit(1);
    }

    return a;
}
Wie lang und schwierig der Rest dann wird, kannst Du Dir vielleicht denken. Nicht umsonst hat man diese Ebene in Befehlen wie z.B. "sed" gekapselt.

Gruß
Zuletzt geändert von abgdf am Mittwoch 3. Dezember 2008, 12:42, insgesamt 1-mal geändert.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Reguläre Ausdrücke sind meines Wissens nicht gerade die schnellsten. Du könntest versuchen, dir mit der Standard-`split()`-Methode von Strings zu helfen. Ist vielleicht etwas umständlicher und unschöner, dürfte aber schneller sein.

Weiterhin scheint mir das Argument `filestring` schon das Problem zu verdeutlichen. Zeig' doch auch den Code zum Einlesen. Dabei bietet sich zeilenweises Einlesen und die Verwendung eines Generators sehr an, um nicht *alle* Zeilen zeitgleich im Speicher halten zu müssen. Sofern die zu lesenden Datensätze auf Zeilen aufgeteilt sind, kannst du diese separat verarbeiten.

Python-Code kannst du übrigens in die Tags `[code=py]` und `[/code]` einschließen, damit er formatiert und eingefärbt dargestellt wird.
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Code: Alles auswählen

predicate = re.compile('\.')
e = predicate.split(output_fn) # Split the filename by '.'
Das kannst du durch folgendes ersetzen:

Code: Alles auswählen

e = output_fn.split(".")
Aber das wird durch nicht viel Geschwindigkeit bringen.
Das:

Code: Alles auswählen

predicate = re.compile('STRESSES IN.*IN ELEMENT\s+\d+, POINT\s+\d+')
elem_list = predicate.split(filestring)
ist natürlich ein Brocken. Regexes sind allgemein recht langsam. Keine Ahnung, wie die Dateien sonst so aussehen, aber vielleicht kannst du das ganze ohne Regexes basteln?

Gruß,
Manuel
Siggi2008
User
Beiträge: 6
Registriert: Donnerstag 27. November 2008, 10:13

Hallo!

Vielen Dank schon mal an alle für die schnelle Hilfe! @Y0Gi: Nachstehend habe ich noch mal den Code für das Einlesen der Dateien angegeben. Vielleicht kann man da ja tatsächlich noch die Geschwindigkeit erheblich steigern.

Code: Alles auswählen

import sys

# Open, read and close a file. Return the string, containing the file.
def read_file(filename):
	try:
		fsock = open(filename)
		try:
			string = fsock.read()
		finally:
			fsock.close()
		
	except IOError:
		print "Error opening file %s. Does the file exist?" % filename
		sys.exit(-1)
	
	return string
	
# Print the status of a operation "x of y elements done (x.y%)"
def print_status(current_elem, length):
	status =  "%i of %i elements done (%.1f%%)" % (current_elem, length, (current_elem/float(length))*100.0)
	sys.stdout.write(status+'\r')
	sys.stdout.flush()
	
# Print a status message (msg). Attribute = 'r' means overwrite the last line (replace), 'n' means, use new line
def print_status_string(msg, attribute):
	if attribute == 'r':
		sys.stdout.write('                                                                                   \r')
		sys.stdout.flush()
		sys.stdout.write(msg+'\r')
		sys.stdout.flush()
	elif attribute == 'n':
		print msg
Viele Grüße,
Siggi
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Vielleicht könntest du noch einen Auszug aus einer Datei hier posten? Das Parsen könnte man vielleicht noch optimieren?
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Was

Code: Alles auswählen

output_fn = output_fn[:len(e)+2]
bewirken soll, verstehe ich nicht.
Es existiert os.path.splitext.
MfG
HWK
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

Hat zwar nichts mit deinem Problem direkt zu tun, aber für was ist das verschachtelte try-Konstrukt?
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Der gezeigte reguläre Ausdruck mit dem "STRESSES IN" scheint auf einzelnen Zeilen zu arbeiten. Ich sehe daher keine Notwendigkeit, in "filestring" alle 50 MB zu speichern, sondern würde Zeile für Zeile vorgehen. Dateien wie ein Array zu verarbeiten und nicht als Strom von Daten, ist manchmal einfacher, aber selten eine gute Idee.

Stefan
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Wenn die Datei nicht existiert, kann sie in finally auch nicht geschlossen werdn, darum kein flaches try-except-finally.
Allerdings existiert with was doch sauberer ist als der innere try Block.

Code: Alles auswählen

try: 
    with open(filename) as fsock:
        string = fsock.read()      
except IOError:
    print "Error opening file %s. Does the file exist?" % filename
    sys.exit(-1) 
Gibt es für den großen Einsatz von sys.stdout eigtl einen besonderen Grund? Dass print existiert, scheinst du ja zu wissen.

Edit: Snippet ergänzt
Siggi2008
User
Beiträge: 6
Registriert: Donnerstag 27. November 2008, 10:13

Der Großteil der Dateioperation besteht darin, Daten aus einer vorhanden Textdatei auszulesen, neu zu ordnen und in eine neue Textdatei einzutragen. Ein Beispiel für den Auslesevorgang ist der unten dargestellte Block. Dieser muss zunächst in eine neue Form gebracht werden:

Neue Darstellung:
FIB. STRESS
1 -9.68
2 -6.61
3 130.34
etc.

Alte Darstellung:
FIB. STRESS FIB. STRESS FIB. STRESS FIB. STRESS FIB. STRESS
1 -9.68 2 -6.61 3 130.34 4 127.28 5 130.34
6 127.28 7 -9.68 8 -6.61 9 -0.79 10 121.46
11 127.07 12 -6.40 13 124.84 14 126.20 15 -5.53
16 -4.17 17 126.20 18 124.84 19 -5.53 20 -4.17
Der Block ist nur ein Auszug. Tatsächlich gibt es bis zu 5.000 von diesen Wertepaaren pro Datei (und es sind viele Dateien!), wobei ich diese sehr oft auslesen und in einer anderen Datei eintragen muss.

Vielleicht fällt Euch ja noch etwas sein, wie der Code optimiert werden kann. Ich bin im Internet darauf gestossen, dass die Datei in binärer Form wesentlich schneller verarbeitet werden kann.

Könnte das eine Option sein?

Vielen Dank für Eure Hilfe,
Siggi
maxsz
User
Beiträge: 2
Registriert: Mittwoch 3. Dezember 2008, 16:05

Also geschwindigkeitsrelevant ist vorallem folgende Funktion:

Code: Alles auswählen

def create_tem(tem_output_filename, filename, beam_list):
	shutil.copy2(filename, tem_output_filename)
	
	p = re.compile('0\.000000E\+00')
	i = 0
	try:
		for line in fileinput.input(tem_output_filename, inplace=1):
			if p.search(line[-14:]):
				line =  line[:-14] + '%E\n' % (beam_list[i][3] * 10**6)
				i += 1
			print line,
		fileinput.close()
	except IOError:
		print "Error opening file %s. Does the file exist?" % filename
		sys.exit(-1)

Hier ist das Problem, dass so eine Datei gut mal 1.4 mio Zeilen haben kann, relevant sind allerdings grob nur die ersten 2500. Hier muss in jeder Zeile ein Wert durch einen bestimmten anderen Wert (jede Zeile ein anderer Wert) ersetzt werden. Die Frage ist nun, gibt es eine Möglichkeit nur diese Zeilen direkt in der Datei zu ersetzen, ohne den Rest beachten zu müssen?
abgdf

Also, ich hoffe und gehe mal davon aus, Du kommst so zurecht. Ansonsten wäre es auch möglich, das Hauptprogramm in Python zu schreiben und einzelne rechenintensive Funktionen in C:

http://superjared.com/entry/anatomy-python-c-module/
http://www.python.org/doc/2.5.2/ext/intro.html

Gruß
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

maxsz hat geschrieben:Die Frage ist nun, gibt es eine Möglichkeit nur diese Zeilen direkt in der Datei zu ersetzen, ohne den Rest beachten zu müssen?
Ja, in Deinem Fall schon, da immer 13 Zeichen durch 13 andere ersetzt werden. In etwa so:

Code: Alles auswählen

test = open("/tmp/foo", "r+")
SEARCH = '0.000000E+00'
line = test.readline()
while line:
    if line[-13:].startswith(SEARCH):
        test.seek(test.tell() - 13)
        test.write('1.234567E+00\n')
    line = test.readline()
test.close()
Gruß,
Manuel
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Siggi2008 hat geschrieben:Der Block ist nur ein Auszug.
Der Block hat aber nicht viel mit deinem Code zu tun. Du splitest nach "STRESSES IN.*IN ELEMENT\s+\d+, POINT\s+\d+", aber in deinem Beispiel kommt sowas gar nicht vor.

So wie ich das sehe, reicht es, wenn du schaust, ob die Zeile mit "FIB. STRESS" beginnt (line.startswith("FIB")), oder auch nur mit "FIB". Dann kannst du entsprechend reagieren.

Aber durch die Datei durchjagen und eine andere schreiben - da musst du durch ;-). Interessant wäre jetzt, wie du die Zeilen parst und neu sortierst. Da ist, denke ich, mehr Spielraum für Optimierungen.
maxsz
User
Beiträge: 2
Registriert: Mittwoch 3. Dezember 2008, 16:05

Also das hat schon einiges gebracht. Vielen Dank!

Also immer zwei Blöcke zwischen "STRESSES..." gehören zusammen. Aber vlt sagt der Code mehr als tausend Worte. Eventuell sind ja noch weitere Optimierungen möglich...

Code: Alles auswählen

def sort_fibres(string1, string2):
	predicate = re.compile('-?\d+\.\d+')
	elem_list1 = predicate.findall(string1)
	elem_list2 = predicate.findall(string2)
	
	# Create new list
	beam_list = []
	
	for i in range(0, len(elem_list1)):
		beam_list.append([i+1, 
						 elem_list1[i], 
						 elem_list2[i], 
						 calculate_mean(float(elem_list1[i]), float(elem_list2[i]))])
		
	return beam_list


def write_beam_elems(filestring, elems, output_filename):
	# Generate the correct filename from output_filename
	output_fn = os.path.splitext(output_filename)[0]
	output_file_extension = os.path.splitext(output_filename)[1]
	
	# Split the *.out file 
	predicate = re.compile('STRESSES IN.*IN BEAM ELEMENT\s+\d+, POINT\s+\d+')
	elem_list = predicate.split(filestring)
	
	i = 1
	j = 1
	length = (len(elem_list)-1)/2
	while i <= len(elem_list)-2:	
		try:
			beam_list = sort_fibres(elem_list[i], elem_list[i+1])
			filename = "%s%i%s" % (output_fn, j, output_file_extension)
			f = open(filename, "w")
			f.write(beam_list2str(beam_list))
			f.close()
			tem_output_filename = "%s%i.%s" % (output_fn, j, 'tem')
			create_tem(tem_output_filename, elems[j-1], beam_list)
			
			print_status(j, length)
			
			i += 2
			j += 1
		except IOError:
			print "Error opening file %s. Does the path '%s/' exist?" % (filename, output_fn)
			break

derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Aus Zeile 7-16 kannst du eine Zeile machen (List Comprehension). Aus

Code: Alles auswählen

    # Create new list
    beam_list = []
    
    for i in range(0, len(elem_list1)):
        beam_list.append([i+1, 
                         elem_list1[i], 
                         elem_list2[i], 
                         calculate_mean(float(elem_list1[i]), float(elem_list2[i]))])
        
    return beam_list
wird (ungetestet)

Code: Alles auswählen

beam_list = [[index+1,
              elem_list1[index],
              elem_list2[index],
              calculate_mean(float(elem_list1[i]),
                             float(elem_list2[i]))] for index, value in  enumerate(elem_list1)]
  • with-statement einsetzen
  • sinnvolle Variablen benutzen
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

SchneiderWeisse hat geschrieben:Hat zwar nichts mit deinem Problem direkt zu tun, aber für was ist das verschachtelte try-Konstrukt?
FYI: Die gemeinsame Benutzung von `except` und `finally` ist IIRC erst ab einer bestimmten Python-Version (2.4? 2.5?) möglich.

Im vorliegenden Fall hätte ich es anders herum verschachtelt, *wenn* ich nicht sowieso `with` benutzen würde.
Antworten