2008-12-24 6 views
34

Например, файлы в Python являются итерабельными - они перебирают строки в файле. Я хочу подсчитать количество строк.Есть ли какой-либо встроенный способ получить длину итерации в python?

Один быстрый способ сделать это:

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

Однако это загружает весь файл в память (на один раз). Это скорее поражает цель итератора (которому требуется только сохранить текущую строку в памяти).

Это не работает:

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

, как генераторы не имеют длину.

Есть ли способ сделать это, чтобы определить функцию count?

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

EDIT: Чтобы уточнить, я понимаю, что весь файл должен быть прочитан! Я просто не хочу, чтобы это в памяти все сразу =).

+0

, чтобы подсчитать количество строк, которые вы будете загружать в память в любом случае! – hasen

+0

списки (все типы последовательностей) также являются итерабельными.что вы имеете в виду «итератор» – hop

+4

@ хасен: да, но не все сразу. – Claudiu

ответ

53

Короткие итерацию через итерацию и подсчета количества итераций, нет , Это то, что делает его итерабельным, а не списком. На самом деле это не проблема даже для python. Посмотрите на классическую структуру данных связанных списков. Поиск длины - это операция O (n), которая включает в себя повторение всего списка, чтобы найти количество элементов.

Как mcrute упоминалось выше, вы, вероятно, может снизить функцию:

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

Конечно, если вы определяете свой собственный Iterable объект, который вы всегда можете реализовать __len__ себя и держать рассчитывать где-то элемент.

+0

это может быть улучшено с помощью itertools.tee() – hop

+0

@hop: Подумайте, как объяснить? –

+0

@Matt Joiner: вызов 'count_iterable' потребляет итератор, поэтому вы не сможете ничего с ним поделать. Копирование итератора с помощью 'i, i2 = itertools.tee (i)' заранее решит эту проблему, но она не работает внутри функции, потому что 'count_iterable' не может изменить свой аргумент как побочный эффект (но определяя функция для простой 'sum()' кажется мне ненужной в любом случае ...). Я думаю, что это было более или менее моим рассуждением 2 года назад. Подумав об этом дальше, я бы, скорее всего, использовал '.seek (0)' (и переименовал функцию, так как он больше не будет работать для произвольных итераторов). – hop

18

Если вам нужен подсчет строк, вы можете сделать это, я не знаю ни одного лучшего способа сделать это:

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

Если вы думаете об этом, то как вы предлагаете найти количество строк в файле, не читая весь файл для строк новой строки? Конечно, вы можете найти размер файла, и если вы можете гарантировать, что длина строки равна x, вы можете получить количество строк в файле. Но если у вас есть какое-то ограничение, я не вижу, как это может работать вообще. Кроме того, поскольку итерации могут быть бесконечно длинными ...

+3

Я действительно хочу прочитать весь файл, я просто не хочу его в памяти сразу – Claudiu

7

Абсолютно нет, по той простой причине, что итерабельность не гарантирована.

Рассмотрим совершенно правовую функцию генератора:

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

Попытка вычислить длину этой функции len([x for x in forever()]) явно не работает.

Как вы отметили, большая часть целей итераторов/генераторов заключается в том, чтобы работать с большим набором данных, не загружая все это в память. Тот факт, что вы не можете получить немедленную длину, следует рассматривать как компромисс.

+19

Это также верно для sum(), max() и min(), но эти агрегатные функции принимают итерации. – ttepasse

+3

i downvoted это, в основном для «абсолютно», что просто не соответствует действительности. все, что реализует __len __(), имеет длину - бесконечную, или нет. – hop

+0

@hop, вопрос об итерациях в общем случае. итерации, которые реализуют __len__, являются особым случаем. – Triptych

8

Я использовал это переопределение в течение некоторого времени:

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

Он никогда не вернется ... См. Пример Триптиха. – bortzmeyer

+0

Да, используйте с осторожностью – ttepasse

+2

«используйте с осторожностью», иначе «мы все соглашаемся с взрослыми», одним из принципов Python. По крайней мере, один раз. –

5

Пакет cardinality обеспечивает эффективную count() функции и некоторые связанные функции для подсчета и проверить размер любой итерации: http://cardinality.readthedocs.org/

import cardinality 

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

Внутренне он использует enumerate() и collections.deque() для перемещения всей фактической логики цикла и счета до уровня C, что приводит к значительному ускорению по сравнению с for петли в Python.

2

Оказывается, реализовано решение для этого common problem. Рассмотрите возможность использования функции ilen() от more_itertools.

more_itertools.ilen(iterable) 

Пример печать количества строк в файле (мы используем менеджер with контекста для безопасного обращения с закрытием файлов):

# Example 
import more_itertools 

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

# Output: 433 

Этот пример возвращает тот же результат, как и решение, представленное ранее для на общую сумму строки в файле:

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

# Output: 433 
0

Я тест между двумя общими процедурами в некотором коде шахты, который находит, как много графиков на п вершин есть , чтобы увидеть, какой метод подсчета элементов сгенерированного списка идет быстрее. У Sage есть генераторные графики (n), которые генерируют все графики на n вершин. Я создал две функции, которые получают длину списка, полученного итератором, двумя разными способами и приурочен к каждому из них (усредняя более 100 тестовых прогонов), используя функцию time.time(). Функции были следующими:

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

и

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

Теперь я время каждый метод

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 

среднее время метод список = 0,0391882109642

среднее время метод сумма = +0,0418473792076

Таким образом, вычисляя количество графиков на n = 5 вершин, метод списка немного быстрее (хотя 100 тестовых прогонов не являются большим размером выборки). Но когда я увеличил длину списка вычисляемого пробуя графики на п = 7 вершин (т.е. изменение графика (5) на графики (7)), результат был таков:

среднего времени метода списка = +4,14753051996

средняя сумма метод время = 3.96504004002

В этом случае метод суммы был немного быстрее. В общем, эти два метода имеют примерно одинаковую скорость, но разница MIGHT зависит от длины вашего списка (может быть, просто было то, что я только усреднял более 100 тестовых прогонов, что было не очень высоким - навсегда в противном случае).