J'ai donc deux fichiers CSV que j'essaie de comparer et d'obtenir les résultats des éléments similaires. Le premier fichier, hosts.csv est illustré ci-dessous:
Path Filename Size Signature
C:\ a.txt 14kb 012345
D:\ b.txt 99kb 678910
C:\ c.txt 44kb 111213
Le deuxième fichier, masterlist.csv est illustré ci-dessous:
Filename Signature
b.txt 678910
x.txt 111213
b.txt 777777
c.txt 999999
Comme vous pouvez le voir, les lignes ne correspondent pas et le masterlist.csv est toujours plus grand que le fichier hosts.csv. La seule partie que j'aimerais rechercher est la partie Signature. Je sais que cela ressemblerait à quelque chose comme:
hosts[3] == masterlist[1]
Je recherche une solution qui me donnera quelque chose comme ceci (essentiellement le fichier hosts.csv avec une nouvelle colonne RESULTS):
Path Filename Size Signature RESULTS
C:\ a.txt 14kb 012345 NOT FOUND in masterlist
D:\ b.txt 99kb 678910 FOUND in masterlist (row 1)
C:\ c.txt 44kb 111213 FOUND in masterlist (row 2)
J'ai cherché dans les messages et trouvé quelque chose de similaire à ceci ici mais je ne le comprends pas très bien car j'apprends toujours le python.
Modifier en utilisant Python 2.6
Edit: Alors que ma solution fonctionne correctement, consultez la réponse de Martijn ci-dessous pour une solution plus efficace.
Vous pouvez trouver la documentation du module python CSV ici .
Ce que vous recherchez est quelque chose comme ceci:
import csv
f1 = file('hosts.csv', 'r')
f2 = file('masterlist.csv', 'r')
f3 = file('results.csv', 'w')
c1 = csv.reader(f1)
c2 = csv.reader(f2)
c3 = csv.writer(f3)
masterlist = list(c2)
for hosts_row in c1:
row = 1
found = False
for master_row in masterlist:
results_row = hosts_row
if hosts_row[3] == master_row[1]:
results_row.append('FOUND in master list (row ' + str(row) + ')')
found = True
break
row = row + 1
if not found:
results_row.append('NOT FOUND in master list')
c3.writerow(results_row)
f1.close()
f2.close()
f3.close()
La réponse de srgerg est terriblement inefficace, car elle fonctionne en temps quadratique; voici une solution de temps linéaire à la place, en utilisant Python 2.6 syntaxe compatible:
import csv
with open('masterlist.csv', 'rb') as master:
master_indices = dict((r[1], i) for i, r in enumerate(csv.reader(master)))
with open('hosts.csv', 'rb') as hosts:
with open('results.csv', 'wb') as results:
reader = csv.reader(hosts)
writer = csv.writer(results)
writer.writerow(next(reader, []) + ['RESULTS'])
for row in reader:
index = master_indices.get(row[3])
if index is not None:
message = 'FOUND in master list (row {})'.format(index)
else:
message = 'NOT FOUND in master list'
writer.writerow(row + [message])
Cela produit un dictionnaire, mappant les signatures de masterlist.csv
À un numéro de ligne en premier. Les recherches dans un dictionnaire prennent un temps constant, ce qui rend la deuxième boucle sur hosts.csv
Lignes indépendante du nombre de lignes dans masterlist.csv
. Sans parler du code qui est beaucoup plus simple.
Pour ceux qui utilisent Python 3, ce qui précède a seulement besoin que les appels open()
soient ajustés pour s'ouvrir en mode texte (supprimez le b
du mode fichier) , et vous souhaitez ajouter new line=''
pour que le lecteur CSV puisse prendre le contrôle des séparateurs de lignes. Vous pouvez indiquer le codage à utiliser explicitement plutôt que de vous fier à la valeur par défaut de votre système (utilisez encoding=...
). Le mappage master_indices
Peut être construit avec une compréhension de dictionnaire ({r[1]: i for i, r in enumerate(csv.reader(master))}
).
Le module Python CSV et les collections, en particulier OrderedDict , sont vraiment utiles ici. Vous voulez utiliser OrderedDict pour conserver l'ordre des clés, etc. Ce n'est pas obligatoire, mais c'est utile!
import csv
from collections import OrderedDict
signature_row_map = OrderedDict()
with open('hosts.csv') as file_object:
for line in csv.DictReader(file_object, delimiter='\t'):
signature_row_map[line['Signature']] = {'line': line, 'found_at': None}
with open('masterlist.csv') as file_object:
for i, line in enumerate(csv.DictReader(file_object, delimiter='\t'), 1):
if line['Signature'] in signature_row_map:
signature_row_map[line['Signature']]['found_at'] = i
with open('newhosts.csv', 'w') as file_object:
fieldnames = ['Path', 'Filename', 'Size', 'Signature', 'RESULTS']
writer = csv.DictWriter(file_object, fieldnames, delimiter='\t')
writer.writer.writerow(fieldnames)
for signature_info in signature_row_map.itervalues():
result = '{0} FOUND in masterlist {1}'
# explicit check for sentinel
if signature_info['found_at'] is not None:
result = result.format('', '(row %s)' % signature_info['found_at'])
else:
result = result.format('NOT', '')
payload = signature_info['line']
payload['RESULTS'] = result
writer.writerow(payload)
Voici la sortie à l'aide de vos fichiers CSV de test:
Path Filename Size Signature RESULTS
C:\ a.txt 14kb 012345 NOT FOUND in masterlist
D:\ b.txt 99kb 678910 FOUND in masterlist (row 1)
C:\ c.txt 44kb 111213 FOUND in masterlist (row 2)
Veuillez excuser le désalignement, ils sont séparés par des tabulations :)
Le module csv
est très pratique pour analyser les fichiers csv. Mais juste pour le plaisir, je divise simplement l'entrée sur les espaces blancs pour obtenir les données.
Il suffit d'analyser les données, de créer un dict
pour les données dans masterlist.csv avec la signature comme clé et le numéro de ligne comme valeur. Maintenant, pour chaque ligne de hosts.csv, nous pouvons simplement interroger le dict
et savoir s'il existe ou non une entrée correspondante dans masterlist.csv et si oui à quelle ligne.
#! /usr/bin/env python
def read_data(filename):
input_source=open(filename,'r')
input_source.readline()
return [line.split() for line in input_source]
if __name__=='__main__':
hosts=read_data('hosts.csv')
masterlist=read_data('masterlist.csv')
master=dict()
for index,data in enumerate(masterlist):
master[data[-1]]=index+1
for row in hosts:
try:
found="FOUND in masterlist (row %s)"%master[row[-1]]
except KeyError:
found="NOT FOUND in masterlist"
line=row+[found]
print "%s %s %s %s %s"%Tuple(line)