2008-12-24 13 views
34

Zum Beispiel sind Dateien in Python iterierbar - sie durchlaufen die Zeilen in der Datei. Ich möchte die Anzahl der Zeilen zählen.Gibt es einen eingebauten Weg, um die Länge eines Iterablen in Python zu erhalten?

Eine schnelle Möglichkeit, dies zu tun:

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

Dies ist jedoch lädt die gesamte Datei in dem Speicher (auf einmal). Dies verhindert eher den Zweck eines Iterators (der nur die aktuelle Zeile im Speicher behalten muss).

funktioniert das nicht:

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

als Generatoren keine Länge haben.

Gibt es eine Möglichkeit, eine Zählfunktion zu definieren?

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

EDIT: Um zu klären, verstehe ich, dass die ganze Datei gelesen werden muss! Ich will es nur nicht in Erinnerung auf einmal =).

+0

um die Anzahl der Zeilen zu zählen, wird die Datei trotzdem in den Speicher geladen! – hasen

+0

Listen (alle Sequenztypen) sind ebenfalls iterierbar.was meinst du ist "iterator" – hop

+4

@hasen: ja, aber nicht alle auf einmal. – Claudiu

Antwort

53

Kurzlaufen durch die iterable und Zählen der Anzahl der Iterationen, kein . Das macht es zu einem iterierbaren und nicht zu einer Liste. Dies ist nicht wirklich ein Python-spezifisches Problem. Betrachten Sie die klassische Linked-List-Datenstruktur. Das Finden der Länge ist eine O (n) -Operation, bei der die gesamte Liste durchlaufen wird, um die Anzahl der Elemente zu finden.

Wie mcrute oben erwähnt, können Sie wahrscheinlich Ihre Funktion reduzieren:

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

Natürlich, wenn Sie Ihre eigenen iterable Objekt sind definieren Sie immer __len__ selbst implementieren können und halten ein Element irgendwo zählen.

+0

dies könnte mit einem itertools.tee verbessert werden() – hop

+0

@hop: Pflege, um zu erklären, wie? –

+0

@Matt Joiner: Wenn Sie 'count_iterable' aufrufen, wird der Iterator verbraucht, so dass Sie damit nichts weiter machen könnten. Das vorherige Kopieren des Iterators mit 'i, i2 = itertools.tee (i)' würde dieses Problem lösen, aber es funktioniert nicht innerhalb der Funktion, weil 'count_iterable' sein Argument nicht als Nebeneffekt ändern kann (aber a Funktion für eine einfache 'sum()' scheint mir sowieso überflüssig ...). Ich denke, das war mehr oder weniger meine Argumentation vor zwei Jahren. Wenn ich weiter darüber nachdenke, würde ich wahrscheinlich '.seek (0)' stattdessen verwenden (und die Funktion umbenennen, da sie für beliebige Iteratoren nicht mehr funktionieren würde). – hop

18

Wenn Sie eine Anzahl von Linien müssen Sie dies tun können, ich weiß nicht jeden besseren Weg, es zu tun:

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

Wir werden, wenn Sie darüber nachdenken, wie schlagen Sie vor, die Anzahl der Zeilen in einer Datei zu finden, ohne die ganze Datei für Zeilenumbrüche zu lesen? Sicher, Sie können die Größe der Datei finden, und wenn Sie sicherstellen können, dass die Länge einer Zeile x ist, können Sie die Anzahl der Zeilen in einer Datei erhalten. Aber es sei denn, Sie haben irgendeine Art von Zwang, ich sehe nicht, wie das überhaupt funktionieren kann. Auch weil Iterables unendlich lang sein können ...

+3

Ich möchte die ganze Datei lesen, ich will es nicht im Speicher auf einmal – Claudiu

7

Absolut nicht, aus dem einfachen Grund, dass Iterables nicht garantiert werden, endlich zu sein.

Betrachten Sie diese völlig legal Generatorfunktion:

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

Der Versuch, die Länge dieser Funktion mit len([x for x in forever()]) zur Berechnung funktioniert eindeutig nicht.

Wie Sie bereits angemerkt haben, besteht ein großer Teil des Zwecks von Iteratoren/Generatoren darin, in der Lage zu sein, an einem großen Datenbestand zu arbeiten, ohne alle Daten in den Speicher zu laden. Die Tatsache, dass Sie keine unmittelbare Länge erhalten können, sollte als Kompromiss betrachtet werden.

+19

Es gilt auch für sum(), max() und min() aber diese Aggregatfunktionen nehmen iterables. – ttepasse

+3

ich habe das abgelehnt, hauptsächlich für das "absolut", das ist einfach nicht wahr. alles, was __len __() implementiert, hat eine Länge - unendlich oder nicht. – hop

+0

@hop, die Frage ist über iterables im allgemeinen Fall. iterables, die __len__ implementieren, sind ein Sonderfall. – Triptych

8

Ich habe diese Neudefinition seit einiger Zeit verwendet:

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

Es kann nie zurückkehren ... Sieh Triptychs Beispiel. – bortzmeyer

+0

Yep, mit Vorsicht zu verwenden – ttepasse

+2

"mit Vorsicht zu verwenden" aka "wir sind alle Einwilligung Erwachsene", einer der Grundsätze von Python. Zumindest war es eins. –

5

Das cardinality Paket bietet eine effiziente count() Funktion und einige verwandte Funktionen die Größe jeder iterable zu zählen und überprüfen: http://cardinality.readthedocs.org/

import cardinality 

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

Intern verwendet es enumerate() und collections.deque(), um alle tatsächlichen Schleifen und Zählen Logik auf den C-Pegel zu bewegen, was zu einer erheblichen Beschleunigung über for Schleifen in Python.

2

Es stellt sich heraus, es gibt eine implementierte Lösung für diese common problem. Erwägen Sie die Verwendung der ilen()-Funktion von more_itertools.

more_itertools.ilen(iterable) 

Ein Beispiel eine Anzahl von Zeilen in einer Datei zu drucken (wir verwenden den with Kontext-Manager Schließen von Dateien sicher zu handhaben):

# Example 
import more_itertools 

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

# Output: 433 

Dieses Beispiel das gleiche Ergebnis wie Lösungen liefert weiter vorne für in Höhe von Zeilen in einer Datei:

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

# Output: 433 
0

ich habe ein Test zwischen den beiden gängigen Verfahren in einigen Code von mir, das feststellt, wie viele Graphen auf n Ecken gibt es , um zu sehen, welche Methode zum Zählen von Elementen einer generierten Liste schneller ist. Sage hat einen Generatorgraphen (n), der alle Graphen auf n Knoten erzeugt. Ich habe zwei Funktionen erstellt, die die Länge einer Liste erhalten, die von einem Iterator auf zwei verschiedene Arten erhalten wurde, und die jeweils mit der Funktion time.time() gemittelt wurden (durchschnittlich über 100 Testläufe). Die Funktionen sind wie folgt:

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

und

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

Nun, wenn ich jede Methode

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 

Durchschnitt Liste Methode Zeit = 0,0391882109642

durchschnittliche Summe Methode Zeit = ,0418473792076

Um die Anzahl der Graphen auf n = 5 Ecken zu berechnen, ist die Listenmethode etwas schneller (obwohl 100 Testläufe keine große Stichprobengröße sind). Aber wenn ich die Länge der Liste erhöht durch Graphen auf n = 7 Ecken (dh Ändern Graphen (5) auf Graphen (7)) versucht, berechnet wird, war das Ergebnis folgendermaßen aus:

Durchschnitt Liste Methode Zeit = 4,14753051996

durchschnittliche Summe Methode Zeit = 3.96504004002

In diesem Fall war die Summenmethode etwas schneller. Alles in allem sind die beiden Methoden ungefähr die gleiche Geschwindigkeit, aber der Unterschied könnte von der Länge Ihrer Liste abhängen (es könnte auch sein, dass ich nur über 100 Testläufe gemittelt habe, was nicht sehr hoch ist - hätte ewig gedauert Andernfalls).

Verwandte Themen