2008-12-24 10 views
34

Ad esempio, i file, in Python, sono iterabili - iterano sulle linee nel file. Voglio contare il numero di linee.Esiste un modo integrato per ottenere la lunghezza di un iterabile in python?

Un modo rapido è quello di fare questo:

lines = len(list(open(fname))) 

Tuttavia, questo carica l'intero file in memoria (in una sola volta). Questo piuttosto vanifica lo scopo di un iteratore (che ha solo bisogno di mantenere la riga corrente in memoria).

Questo non funziona:

lines = len(line for line in open(fname)) 

come generatori non hanno una lunghezza.

C'è un modo per fare questo breve di definire una funzione di conteggio?

def count(i): 
    c = 0 
    for el in i: c += 1 
    return c 

MODIFICA: per chiarire, ho capito che l'intero file dovrà essere letto! Io non lo voglio in memoria tutto in una volta =).

+0

per contare il numero di righe in cui caricare il file in memoria comunque! Gli elenchi – hasen

+0

(tutti i tipi di sequenza) sono anch'essi iterabili.cosa intendi è "iteratore" – hop

+4

@hasen: sì, ma non tutto in una volta. – Claudiu

risposta

53

Breve iterazione attraverso il iterabile e contando il numero di iterazioni, senza . Questo è ciò che lo rende un iterable e non una lista. Questo non è nemmeno un problema specifico per Python. Guarda la classica struttura dei dati della lista collegata. Trovare la lunghezza è un'operazione O (n) che coinvolge l'iterazione dell'intero elenco per trovare il numero di elementi.

Come mcrute accennato in precedenza, probabilmente si può ridurre la funzione:

def count_iterable(i): 
    return sum(1 for e in i) 

Naturalmente, se si sta definendo il proprio oggetto iterabile si può sempre implementare __len__ se stessi e mantenere conta un elemento da qualche parte.

+0

questo potrebbe essere migliorato con un itertools.tee() – hop

+0

@hop: cura di spiegare come? –

+0

@Matt Joiner: chiamando 'count_iterable' si consuma l'iteratore, quindi non si sarebbe in grado di fare qualcosa di più con esso. Copiare l'iteratore con 'i, i2 = itertools.tee (i)' in anticipo risolverà il problema, ma non funziona all'interno della funzione, perché 'count_iterable' non può cambiare il suo argomento come un effetto collaterale (ma definendo un la funzione per un semplice 'sum()' mi sembra comunque inutile ...). Penso che sia stato più o meno il mio ragionamento 2 anni fa. Pensandoci meglio, probabilmente userò '.seek (0)' invece (e rinominerò la funzione, dato che non funzionerebbe più per gli iteratori arbitrari). – hop

18

Se avete bisogno di un conteggio di linee si può fare questo, non so di un modo migliore per farlo:

line_count = sum(1 for line in open("yourfile.txt")) 
0

Se ci pensate, come vi proponete di trovare il numero di righe in un file senza leggere l'intero file per le newline? Certo, puoi trovare la dimensione del file, e se puoi garantire che la lunghezza di una linea è x, puoi ottenere il numero di linee in un file. Ma a meno che tu non abbia qualche tipo di vincolo, non riesco a vedere come possa funzionare. Inoltre, dal momento che i iterables possono essere infinitamente lunghi ...

+3

Voglio leggere l'intero file, non lo voglio semplicemente in memoria tutto in una volta – Claudiu

7

Assolutamente no, per la semplice ragione che non si garantisce che i iterables siano finiti.

considerare questa funzione di generatore perfettamente legale:

def forever(): 
    while True: 
     yield "I will run forever" 

Cercando di calcolare la lunghezza di questa funzione con len([x for x in forever()]) sarà chiaramente non funziona.

Come è stato notato, gran parte dello scopo degli iteratori/generatori è di essere in grado di lavorare su un grande set di dati senza caricarli tutti in memoria. Il fatto che non si possa ottenere una lunghezza immediata dovrebbe essere considerato un compromesso.

+19

È anche vero per sum(), max() e min() ma queste funzioni di aggregazione sono iterabili. – ttepasse

+3

ho downvoted questo, principalmente per "assolutamente", che non è vero. tutto ciò che implementa __len __() ha una lunghezza - infinita o no. – hop

+0

@hop, la domanda riguarda le iterabili nel caso generale. iterables che implementano __len__ sono un caso speciale. – Triptych

8

Ho usato questo ridefinizione da qualche tempo:

def len(thingy): 
    try: 
     return thingy.__len__() 
    except AttributeError: 
     return sum(1 for item in iter(thingy)) 
+0

Non può mai tornare ... Vedi l'esempio di Triptych. – bortzmeyer

+0

Sì, usare con cautela – ttepasse

+2

"use with care" ovvero "siamo tutti adulti consenzienti", uno dei principi di Python. Almeno era uno, una volta. –

5

Il pacchetto cardinality fornisce un efficiente count() funzione e alcune funzioni correlate a contare e controllare la dimensione di qualsiasi iterabile: http://cardinality.readthedocs.org/

import cardinality 

it = some_iterable(...) 
print(cardinality.count(it)) 

Internamente utilizza enumerate() e collections.deque() per spostare tutta la logica di looping e di conteggio effettiva sul livello C, determinando una notevole accelerazione rispetto a for loop in Python.

2

Si scopre che esiste una soluzione implementata per questo common problem. Prendi in considerazione l'utilizzo della funzione ilen() da more_itertools.

more_itertools.ilen(iterable) 

Un esempio di stampa di un numero di righe in un file (usiamo il manager with contesto per gestire in modo sicuro i file di chiusura):

# Example 
import more_itertools 

with open("foo.py", "r+") as f: 
    print(more_itertools.ilen(f)) 

# Output: 433 

Questo esempio restituisce lo stesso risultato di soluzioni presentate in precedenza per linee per un totale di un file:

# Equivalent code 
with open("foo.py", "r+") as f: 
    print(sum(1 for line in f)) 

# Output: 433 
0

ho fatto un test fra le due procedure comuni in un certo codice di mine, che trova il numero di grafici su n vertici ci sono , per vedere quale metodo di conteggio degli elementi di una lista generata va più veloce. Sage ha un generatore di grafici (n) che genera tutti i grafici su n vertici. Ho creato due funzioni che ottengono la lunghezza di una lista ottenuta da un iteratore in due modi diversi e cronometrata ognuna di esse (calcolando la media su oltre 100 esecuzioni di test) utilizzando la funzione time.time(). Le funzioni sono state le seguenti:

def test_code_list(n): 
    l = graphs(n) 
    return len(list(l)) 

e

def test_code_sum(n): 
    S = sum(1 for _ in graphs(n)) 
    return S 

Ora ho tempo ogni metodo

import time 

t0 = time.time() 
for i in range(100): 
    test_code_list(5) 
t1 = time.time() 

avg_time = (t1-t0)/10 

print 'average list method time = %s' % avg_time 


t0 = time.time() 
for i in range(100): 
    test_code_sum(5) 
t1 = time.time() 

avg_time = (t1-t0)/100 

print "average sum method time = %s" % avg_time 

tempo medio di metodo list = 0,0391882109642

tempo medio di metodo sum = ,0418473792076

Quindi, calcolando il numero di grafici su n = 5 vertici in questo modo, il metodo lista è leggermente più veloce (sebbene 100 test eseguiti non siano una grande dimensione del campione). Ma quando ho aumentato la lunghezza della lista viene calcolato provando grafici su n = 7 vertici (cioè cambiano grafici (5) a grafici (7)), il risultato è stato questo:

media temporale metodo list = 4,14753051996

tempo medio metodo somma = 3.96504004002

In questo caso il metodo della somma era leggermente più veloce. Tutto sommato, i due metodi hanno all'incirca la stessa velocità, ma la differenza potrebbe dipendere dalla lunghezza della tua lista (potrebbe anche essere che ho solo una media di oltre 100 test eseguiti, il che non è molto alto - avrebbe preso per sempre altrimenti).

Problemi correlati