web-dev-qa-db-fra.com

Comparez deux fichiers CSV et recherchez des éléments similaires

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

14
serk

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()
11
srgerg

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))}).

21
Martijn Pieters

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 :)

4
Mahmoud Abdelkader

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)
0
MAK