Есть ли какой-нибудь встроенный способ получить длину итерируемого в 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 тестовых прогонов, что не очень много - это заняло бы вечность иначе).

Другие вопросы по тегам