Есть ли какой-нибудь встроенный способ получить длину итерируемого в python? - PullRequest
43 голосов
/ 24 декабря 2008

Например, файлы в 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 ]

62 голосов
/ 24 декабря 2008

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

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

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

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

19 голосов
/ 24 декабря 2008

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

line_count = sum(1 for line in open("yourfile.txt"))
10 голосов
/ 07 февраля 2015

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

import cardinality

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

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

10 голосов
/ 24 декабря 2008

Я уже некоторое время использую это переопределение:

def len(thingy):
    try:
        return thingy.__len__()
    except AttributeError:
        return sum(1 for item in iter(thingy))
8 голосов
/ 24 декабря 2008

Абсолютно нет, по той простой причине, что итерации не гарантируются конечными.

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

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

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

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

3 голосов
/ 12 декабря 2016

Оказывается, есть решение для этой общей проблемы . Попробуйте использовать функцию 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
1 голос
/ 08 ноября 2018

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

Существует способ работать значительно быстрее, чем 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.

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

0 голосов
/ 26 июня 2018

Для фильтрации можно использовать этот вариант:

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#boolean-values),, поэтому это принуждение не является взломом (в отличие от некоторых других языков, таких как C / C ++).

0 голосов
/ 13 декабря 2017

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

0 голосов
/ 24 декабря 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...