Есть ли встроенный способ получения длины итерации в 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: Чтобы понять, я понимаю, что весь файл должен быть прочитан! Я просто не хочу его в памяти сразу =).
Ответы
Ответ 1
За исключением повторения итерации и подсчета количества итераций, нет. Это делает его итерируемым, а не списком. На самом деле это не проблема даже для python. Посмотрите на классическую структуру данных связанных списков. Поиск длины - это операция O (n), которая включает в себя повторение всего списка, чтобы найти количество элементов.
Как указано выше, вы можете уменьшить свою функцию до:
def count_iterable(i):
return sum(1 for e in i)
Конечно, если вы определяете свой собственный итерируемый объект, вы всегда можете реализовать __len__
самостоятельно и хранить где-нибудь элемент.
Ответ 2
Если вам нужно количество строк, вы можете сделать это, я не знаю, как лучше это сделать:
line_count = sum(1 for line in open("yourfile.txt"))
Ответ 3
Абсолютно нет, по той простой причине, что итерабельность не гарантируется конечной.
Рассмотрим эту совершенно законную функцию генератора:
def forever():
while True:
yield "I will run forever"
Попытка вычислить длину этой функции с помощью len([x for x in forever()])
явно не сработает.
Как вы заметили, большая часть целей итераторов/генераторов заключается в том, чтобы работать с большим набором данных, не загружая все это в память. Тот факт, что вы не можете получить немедленную длину, следует рассматривать как компромисс.
Ответ 4
Я использовал это переопределение в течение некоторого времени:
def len(thingy):
try:
return thingy.__len__()
except AttributeError:
return sum(1 for item in iter(thingy))
Ответ 5
Пакет cardinality
обеспечивает эффективную функцию count()
и некоторые связанные функции для подсчета и проверки размера любого итерабельного: http://cardinality.readthedocs.org/
import cardinality
it = some_iterable(...)
print(cardinality.count(it))
Внутри он использует enumerate()
и collections.deque()
, чтобы переместить всю реальную логику цикла и подсчета на уровень C, что привело к значительному ускорению над циклами for
в Python.
Ответ 6
Оказывается, есть реализованное решение для этой общей проблемы. Рассмотрите возможность использования функции 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
Ответ 7
Если мы подумаем об этом, как вы предложите найти количество строк в файле, не читая весь файл для строк новой строки? Конечно, вы можете найти размер файла, и если вы можете гарантировать, что длина строки равна x, вы можете получить количество строк в файле. Но если у вас есть какое-то ограничение, я не вижу, как это может работать вообще. Кроме того, поскольку итерации могут быть бесконечно длинными...
Ответ 8
Я проверил между двумя общими процедурами в каком-то моем коде, который находит, сколько графиков на n вершинах есть, чтобы увидеть, какой метод подсчета элементов сгенерированного списка идет быстрее. У 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
средний метод списка time = 0.0391882109642
средняя сумма времени метода = 0,0418473792076
Таким образом, вычисляя количество графиков на n = 5 вершинах таким образом, метод списка немного быстрее (хотя 100 тестовых прогонов не являются большим размером выборки). Но когда я увеличил длину списка, вычисляемого, используя графики на n = 7 вершинах (т.е. Меняя графики (5) на графики (7)), результатом было следующее:
средний метод списка время = 4.14753051996
средняя сумма метод время = 3.96504004002
В этом случае метод суммы был немного быстрее. В общем, эти два метода имеют примерно одинаковую скорость, но разница MIGHT зависит от длины вашего списка (может быть, просто было то, что я только усреднял более 100 тестовых прогонов, что было не очень высоким - навсегда в противном случае).