2015-12-04 13 views
5

lascia supporre Ho un (testo) file con la seguente struttura (nome, punteggio):trattamento dei dati in modo efficiente in file di testo

a   0 
a   1 
b   0 
c   0 
d   3 
b   2 

E così via. Il mio obiettivo è di sommare i punteggi per ogni nome e ordinarli dal punteggio più alto al punteggio più basso. Quindi, in questo caso, desidero il seguente output:

d   3 
b   2 
a   1 
c   0 

In anticipo non so quali nomi saranno nel file.

Mi chiedevo se esiste un modo efficace per farlo. Il mio file di testo può contenere fino a 50.000 voci.

L'unico modo in cui riesco a pensare è solo iniziare dalla riga 1, ricordare quel nome e poi andare su tutto il file per cercare quel nome e la somma. Questo sembra orribilmente inefficiente, quindi mi chiedevo se c'è un modo migliore per farlo.

+2

Qualsiasi tentativo da parte vostra? –

+0

@AvinashRaj Come affermato nella domanda, conosco un modo per farlo, ma ero più alla ricerca di una soluzione migliore. Posso aggiungere qualche pseudocodice nella mia domanda, se vuoi che lo faccia. –

+0

Puoi usare 'ordinato (split)' con lo stesso 'key' (essendo le lettere) – MaTh

risposta

12

leggere tutti i dati in un dizionario:

from collections import defaultdict 
from operator import itemgetter 

scores = defaultdict(int) 
with open('my_file.txt') as fobj: 
    for line in fobj: 
     name, score = line.split() 
     scores[name] += int(score) 

e la selezione:

for name, score in sorted(scores.items(), key=itemgetter(1), reverse=True): 
    print(name, score) 

stampe:

d 3 
b 2 
a 1 
c 0 

prestazioni

per controllare le prestazioni di questa risposta vs. quello di @SvenMarnach, ho inserito entrambi gli approcci in una funzione. Qui fobj è un file aperto per la lettura. Io uso io.StringIO ritardi così IO dovrebbe, si spera, non possono essere misurati:

from collections import Counter 

def counter(fobj): 
    scores = Counter() 
    fobj.seek(0) 
    for line in fobj: 
     key, score = line.split() 
     scores.update({key: int(score)}) 
    return scores.most_common() 

from collections import defaultdict 
from operator import itemgetter 

def default(fobj): 
    scores = defaultdict(int) 
    fobj.seek(0) 
    for line in fobj: 
     name, score = line.split() 
     scores[name] += int(score) 
    return sorted(scores.items(), key=itemgetter(1), reverse=True) 

Risultati per collections.Counter:

%timeit counter(fobj) 
10000 loops, best of 3: 59.1 µs per loop 

Risultati per collections.defaultdict:

%timeit default(fobj) 
10000 loops, best of 3: 15.8 µs per loop 

Sembra defaultdict è quattro volte Più veloce. Non avrei immaginato questo. Ma quando si tratta di prestazioni, è necessario misurare .

+0

@caiohamamura: abbastanza giusto, cancellato il mio commento. Non ho visto che Mike sta usando 'defaultdict (int)' (La mia soluzione è stata messa in una lista e poi usare 'sum()'). –

+0

@Kevin Quella strategia basata su liste sarebbe più lenta e (temporaneamente) consumerebbe più RAM rispetto all'approccio di Mike di accumulare semplicemente i dati come si vede. –

+0

@ PM2Ring: Sì, perché creerà alcuni elenchi. Ho dimenticato che possiamo usare 'defaultdict (int)'. –

7

Questo è un buon caso d'uso per collections.Counter:

from collections import Counter 

scores = Counter() 
with open('my_file') as f: 
    for line in f: 
     key, score = line.split() 
     scores.update({key: int(score)}) 

for key, score in scores.most_common(): 
    print(key, score) 
+0

Immagino che questo sarebbe un po 'più lento di usare 'defaultdict', dal momento che è necessario inserire ciascun elemento di dati in un' dict' per aggiungerlo al 'Counter'. OTOH, rende semplice ottenere l'elenco ordinato dei punteggi accumulati. –

+0

@ PM2Ring: Probabilmente, e 'defaultdict' sarà molto più lento di un semplice' dict'. La velocità conta solo per questo caso d'uso se il file è veramente enorme, e se lo è, il collo di bottiglia sarà I/O, non CPU. –

0

Pandas può fare questo abbastanza facilmente:

import pandas as pd 
data = pd.read_csv('filename.txt', names=['Name','Score']) 
sorted = data.groupby('Name').sum().sort_values('Score', ascending=False) 
print sorted 
Problemi correlati