Есть ли какой-нибудь встроенный способ получить длину итерируемого в python?
Например, файлы в Python являются итеративными - они перебирают строки в файле. Я хочу посчитать количество строк.
Один быстрый способ сделать это:
lines = len(list(open(fname)))
Тем не менее, это загружает весь файл в память (сразу). Это скорее противоречит цели итератора (которому нужно только сохранить текущую строку в памяти).
Это не работает:
lines = len(line for line in open(fname))
поскольку генераторы не имеют длины.
Есть ли способ сделать это за исключением определения функции счета?
def count(i):
c = 0
for el in i: c += 1
return c
РЕДАКТИРОВАТЬ: Чтобы уточнить, я понимаю, что весь файл должен быть прочитан! Я просто не хочу, чтобы в памяти все сразу =).
10 ответов
Если не считать итераций итераций и подсчета количества итераций, нет. Вот что делает его итеративным, а не списком. На самом деле это даже не проблема для Python. Посмотрите на классическую структуру данных связанного списка. Поиск длины - это операция O(n), которая включает в себя итерацию всего списка, чтобы найти количество элементов.
Как упоминалось выше, вы, вероятно, можете уменьшить свою функцию до:
def count_iterable(i):
return sum(1 for e in i)
Конечно, если вы определяете свой собственный повторяемый объект, вы всегда можете реализовать __len__
себя и где-то хранить количество элементов.
Если вам нужно количество строк, вы можете сделать это, я не знаю лучшего способа сделать это:
line_count = sum(1 for line in open("yourfile.txt"))
cardinality
Пакет обеспечивает эффективное count()
функция и некоторые связанные функции для подсчета и проверки размера любого итерируемого: http://cardinality.readthedocs.org/
import cardinality
it = some_iterable(...)
print(cardinality.count(it))
Внутренне он использует enumerate()
а также collections.deque()
переместить всю реальную логику циклов и счетчиков на уровень C, что значительно ускорит for
петли в Python.
Я использовал это переопределение в течение некоторого времени:
def len(thingy):
try:
return thingy.__len__()
except AttributeError:
return sum(1 for item in iter(thingy))
Оказывается, есть решение для этой распространенной проблемы. Рассмотрите возможность использования 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
Абсолютно нет, по той простой причине, что итерируемые не гарантируются конечными.
Рассмотрим эту совершенно легальную функцию генератора:
def forever():
while True:
yield "I will run forever"
Попытка вычислить длину этой функции с len([x for x in forever()])
явно не сработает.
Как вы заметили, большая часть целей итераторов / генераторов заключается в том, чтобы иметь возможность работать с большим набором данных без загрузки всего этого в память. Тот факт, что вы не можете получить немедленную длину, следует рассматривать как компромисс.
Поскольку, по-видимому, в то время дублирование не было замечено, я также опубликую выписку из своего ответа на дубликат:
Есть способ выполнить значительно быстрее, чем sum(1 for i in it)
когда итерируемое может быть длинным (и не значимо медленнее, когда итерируемое короткое), сохраняя при этом фиксированное поведение памяти (в отличие от len(list(it))
), чтобы избежать перебора и перераспределения накладных расходов для больших входов.
# On Python 2 only, get zip that lazily generates results instead of returning list
from future_builtins import zip
from collections import deque
from itertools import count
def ilen(it):
# Make a stateful counting iterator
cnt = count()
# zip it with the input iterator, then drain until input exhausted at C level
deque(zip(it, cnt), 0) # cnt must be second zip arg to avoid advancing too far
# Since count 0 based, the next value is the count
return next(cnt)
подобно len(list(it))
, ilen(it)
выполняет цикл в коде C на CPython (deque
, count
а также zip
все реализованы в C); избегание выполнения байтового кода в цикле обычно является ключом к производительности в CPython.
Вместо того, чтобы повторять здесь все показатели производительности, я просто укажу на мой ответ с полными деталями.
Для фильтрации этот вариант может быть использован:
sum(is_good(item) for item in iterable)
который может быть естественно прочитан как "считать хорошие предметы" и является более коротким и простым (хотя, возможно, менее идиоматическим), чем:
sum(1 for item in iterable if is_good(item)))
Примечание: тот факт, что True
оценивает 1
в числовом контексте указывается в документах ( https://docs.python.org/3.6/library/stdtypes.html), поэтому это приведение не является взломом (в отличие от некоторых других языков, таких как C/C++).
Если вы подумаете об этом, мы предложим вам найти количество строк в файле, не читая весь файл для новых строк? Конечно, вы можете найти размер файла, и если вы можете гарантировать, что длина строки равна x, вы можете получить количество строк в файле. Но если у вас нет каких-то ограничений, я не вижу, как это вообще может работать. Кроме того, так как итерации могут быть бесконечно длинными...
Я провел тест между двумя общими процедурами в моем коде, который определяет, сколько существует графов по 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
среднее время метода списка = 0.0391882109642
метод расчета средней суммы = 0,0418473792076
Таким образом, вычисляя количество графов по n=5 вершинам таким образом, метод списка немного быстрее (хотя 100 тестовых прогонов не большой размер выборки). Но когда я увеличил длину списка, вычисляемого путем попытки построения графиков на n=7 вершинах (т. Е. Изменив графы (5) на графы (7)), результат был следующим:
среднее время метода списка = 4.14753051996
метод расчета средней суммы = 3,96504004002
В этом случае метод суммы был немного быстрее. В целом, эти два метода примерно одинаковы, но разница МОЖЕТ зависеть от длины вашего списка (возможно, просто я усреднил только 100 тестовых прогонов, что не очень много - это заняло бы вечность иначе).