2010-10-14 15 views
0

En ce qui concerne a previous question, j'essaye de faire des remplacements sur un certain nombre de grands fichiers CSV.Rechercher et remplacer dans les fichiers CSV avec Python

L'ordre (et le contenu) des colonnes changent entre les fichiers, mais pour chaque fichier, il y a environ 10 colonnes que je veux et que je peux identifier par les noms d'en-tête de colonne. J'ai aussi 1-2 dictionnaires pour chaque colonne que je veux. Donc, pour les colonnes que je veux, je veux utiliser seulement les dictionnaires corrects et je veux les implémenter séquentiellement.

Un exemple de la façon dont je l'ai essayé de résoudre ce:

# -*- coding: utf-8 -*- 
import re 

# imaginary csv file. pretend that we do not know the column order. 
Header = [u'col1', u'col2'] 
Line1 = [u'A',u'X'] 
Line2 = [u'B',u'Y'] 
fileLines = [Line1,Line2] 

# dicts to translate lines 
D1a = {u'A':u'a'} 
D1b = {u'B':u'b'} 
D2 = {u'X':u'x',u'Y':u'y'} 

# dict to correspond header names with the correct dictionary. 
# i would like the dictionaries to be read sequentially in col1. 
refD = {u'col1':[D1a,D1b],u'col2':[D2]} 

# clunky replace function 
def freplace(str, dict): 
    rc = re.compile('|'.join(re.escape(k) for k in dict)) 
    def trans(m): 
     return dict[m.group(0)] 
    return rc.sub(trans, str) 

# get correspondence between dictionary and column 
C = [] 
for i in range(len(Header)): 
    if Header[i] in refD: 
     C.append([refD[Header[i]],i]) 

# loop through lines and make replacements 
for line in fileLines: 
    for i in range(len(line)): 
     for j in range(len(C)): 
      if C[j][1] == i: 
       for dict in C[j][0]: 
        line[i] = freplace(line[i], dict) 

Mon problème est que ce code est assez lent, et je ne peux pas comprendre comment l'accélérer. Je suis un débutant, et je suppose que ma fonction freplace est en grande partie ce qui ralentit les choses, car il doit compiler pour chaque colonne dans chaque rangée. Je voudrais prendre la ligne rc = re.compile('|'.join(re.escape(k) for k in dict)) de cette fonction, mais je ne sais pas comment faire cela et encore préserver ce que le reste de mon code fait.

+0

Donc, pour chaque colonne, il y a une regex unique dont vous avez besoin? Pourquoi ne pas simplement créer 10 regex, et pointer vers la droite, avec un décalage déterminé par la colonne que les en-têtes déterminent? De cette façon, c'est une compilation unique pour 10 colonnes, alors vous êtes prêt? (Je suis désolé, je ne connais pas Python du tout et je suppose que ce serait ma première supposition). – onaclov2000

+0

@ onaclov2000: En effet, c'est ce que j'espérais faire, mais je ne suis pas sûr de savoir comment pointer vers la bonne regex. – rallen

Répondre

1

Vous n'avez pas besoin re:

# -*- coding: utf-8 -*- 

# imaginary csv file. pretend that we do not know the column order. 
Header = [u'col1', u'col2'] 
Line1 = [u'A',u'X'] 
Line2 = [u'B',u'Y'] 
fileLines = [Line1,Line2] 

# dicts to translate lines 
D1a = {u'A':u'a'} 
D1b = {u'B':u'b'} 
D2 = {u'X':u'x',u'Y':u'y'} 

# dict to correspond header names with the correct dictionary 
refD = {u'col1':[D1a,D1b],u'col2':[D2]} 

# now let's have some fun... 

for line in fileLines: 
    for i, (param, word) in enumerate(zip(Header, line)): 
     for minitranslator in refD[param]: 
      if word in minitranslator: 
       line[i] = minitranslator[word] 

retours:

[[u'a', u'x'], [u'b', u'y']] 
+0

J'avais l'impression, à la suite de ma question précédente, que re serait beaucoup plus rapide que de faire défiler individuellement chaque élément du dictionnaire. Aussi, je veux faire une boucle dans les dictionnaires séquentiellement quand il y a plusieurs dictionnaires pour une colonne donnée, c'est pourquoi j'ai utilisé des listes dans refD. – rallen

+0

@rallen - si vous avez de gros fichiers csv, il est préférable de préparer correctement le dictionnaire refD/translator. Si vous souhaitez appliquer plusieurs dictionnaires (D1a, D1b) les uns après les autres, fusionnez-les en un seul dictionnaire (translator [param]) - leurs clés doivent être uniques, n'est-ce pas? Dans mon dernier for-loop, je n'itère que sur les lignes et les colonnes de votre grand fichier csv, pas sur les dictionnaires - je n'y accède que par les touches, ce qui est ** fast **. – eumiro

+0

En raison de la nature de mes données, je souhaite que les dictionnaires soient conservés. Si quelque chose est remplacé par D1a, il me donne plus d'informations que s'il passe par D1b (qui consiste en grande partie en entrées raccourcies de D1a). – rallen

0

Donc, si tel est le cas, et les 10 colonnes ont les mêmes noms à chaque fois, mais hors d'usage, (Je ne suis pas sûr si c'est ce que vous faites là-haut, mais ici) gardez un tableau pour les noms des titres, et un pour chaque colonne divisée en éléments (devrait être 10 éléments chaque ligne), maintenant juste compenser quelle regex en faisant un cas/select combo, comparez le numéro d'élément de votre tableau d'en-tête, puis à l'intérieur du cas, refere Comme le tableau de données est au même offset, puisque le nom est ce qui arrivera au bon cas, vous devriez pouvoir utiliser les mêmes 10 regex à plusieurs reprises, et ne pas avoir à recompiler une nouvelle "commande" à chaque fois.

J'espère que cela a du sens. Je suis désolé je ne connais pas la syntaxe pour vous aider, mais j'espère que mon idée est ce que vous cherchez EDIT: I.E.

initialise toutes les expressions rationnelles avant de démarrer vos boucles.

puis après avoir lu une ligne (et après la ligne d'en-tête)

tableau sélectionnez [n]

cas "colonne1"

regex (données [0]);

cas "column2"

regex (données [1]); . . . . end select

Cela devrait appeler le droit regex pour les colonnes de droite

3

Il y a une tonne de choses que vous pouvez faire pour accélérer ce:

d'abord, utilisez le module csv. Il fournit des méthodes efficaces et sans bug pour lire et écrire des fichiers CSV.L'objet DictReader en particulier est ce qui vous intéresse: il présentera chaque ligne lue dans le fichier comme un dictionnaire clé par son nom de colonne. Deuxièmement, compilez vos expressions régulières une fois, pas à chaque fois que vous les utilisez. Enregistrez les expressions rationnelles compilées dans un dictionnaire codé par la colonne à laquelle vous allez les appliquer. Troisièmement, considérez que si vous appliquez une centaine d'expressions régulières à une longue chaîne, vous allez scanner la chaîne du début à la fin une centaine de fois. Ce n'est peut-être pas la meilleure approche à votre problème. vous feriez peut-être mieux d'investir du temps dans une approche qui vous permet de lire la chaîne du début à la fin une fois.

+0

+1 pour le module 'csv'. Regexen ne sont pas le bon outil ici. – Daenyth

+0

@Robert: Merci. 1.) Dans mon fichier, j'utilise le module csv. Je vais regarder dans DictReader pour faire correspondre avec les colonnes; auparavant je ne l'utilisais pas parce que je devais traduire la première ligne de mon texte avant de faire correspondre les colonnes avec leurs dictionnaires correspondants, ce que j'ai fait avec "pour ligne dans les lignes: if firstline == 1: [translate, match traduit cols en ligne avec dictionnaire, etc.] ". 2.) En effet, j'espérais les compiler d'abord, mais je ne savais pas comment les référencer. . . Je vais essayer ce que tu as suggéré. – rallen

+0

@Robert: 3.) Je ne veux utiliser que des dictionnaires spécifiques avec des colonnes spécifiques, donc je ne vois pas comment implémenter ce que vous avez suggéré. Tout compte fait je n'ai que 10 dictionnaires alors j'espère que ce n'est pas trop une perte de vitesse. Et si je parcours les colonnes dans les lignes de toute façon, je ne vois pas en quoi j'appelle des regexes différentes à part l'étape pour assigner les bonnes expressions rationnelles à la colonne correcte, comme vous l'avez dit dans votre deuxième point. – rallen