python: получить количество элементов из списка (последовательности) с определенным условием

84

Предполагая, что у меня есть список с огромным количеством элементов.

l = [ 1, 4, 6, 30, 2, ... ]

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

count = len([i for i in l if my_condition(l)])

Но если отфильтрованный список my_condition () также имеет большое количество элементов, я думаю, что создание нового списка для отфильтрованного результата - просто пустая трата памяти. Для эффективности, IMHO, вышеупомянутый вызов не может быть лучше, чем:

count = 0
for i in l:
    if my_condition(l):
        count += 1

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

Заранее спасибо.

Цинск
источник
3
Выбор между генераторами и списками - это выбор между временем выполнения и потреблением памяти. Вы будете удивлены, насколько часто результаты противоречат интуиции, если вы профилируете код. Преждевременная оптимизация - это корень всех зол.
Пауло Скардин,

Ответы:

102

Вы можете использовать выражение генератора :

>>> l = [1, 3, 7, 2, 6, 8, 10]
>>> sum(1 for i in l if i % 4 == 3)
2

или даже

>>> sum(i % 4 == 3 for i in l)
2

который использует тот факт, что int(True) == 1.

В качестве альтернативы вы можете использовать itertools.imap(python 2) или просто map(python 3):

>>> def my_condition(x):
...     return x % 4 == 3
... 
>>> sum(map(my_condition, l))
2
DSM
источник
1
@mgilson: Я не думаю, что он когда-либо делал это вычисление - по startумолчанию 0, поэтому первое добавление True + 0, нет?
DSM
4
Да. Может быть, я должен быть более ясным ... Неважно, что int(True). int("1") == 1также, но это не значит, что вы можете это сделать "1" + 0. Важно то, как python оценивает integer + Trueили integer + False.
mgilson
2
@mgilson: хм, ладно, ты меня убедил.
DSM
4
Дело в том, что boolэто подкласс intand you, поэтому вы можете легко добавлять bools и int (со Trueзначением 1 и Falseзначением 0).
mgilson
Что ж, это то, что я имел в int(True) == 1виду, упомянув , но ваша точка зрения, которая int("1") == 1доказывает, что такое сокращение может подразумевать вещи, которые не соответствуют действительности.
DSM
21

Здесь вам нужен генератор понимания, а не список.

Например,

l = [1, 4, 6, 7, 30, 2]

def my_condition(x):
    return x > 5 and x < 20

print sum(1 for x in l if my_condition(x))
# -> 2
print sum(1 for x in range(1000000) if my_condition(x))
# -> 14

Или используйте itertools.imap(хотя я думаю, что явные выражения списка и генератора выглядят несколько более Pythonic).

Обратите внимание, что, хотя это не очевидно из sumпримера, вы можете хорошо составить понимание генератора. Например,

inputs = xrange(1000000)      # In Python 3 and above, use range instead of xrange
odds = (x for x in inputs if x % 2)  # Pick odd numbers
sq_inc = (x**2 + 1 for x in odds)    # Square and add one
print sum(x/2 for x in sq_inc)       # Actually evaluate each one
# -> 83333333333500000

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

JohnJ
источник
10

Это также можно сделать с помощью, reduceесли вы предпочитаете функциональное программирование

reduce(lambda count, i: count + my_condition(i), l, 0)

Таким образом, вы выполняете только 1 проход, и промежуточный список не создается.

Маленький ученик Ферма
источник
7

вы могли бы сделать что-то вроде:

l = [1,2,3,4,5,..]
count = sum(1 for i in l if my_condition(i))

который просто добавляет 1 для каждого элемента, удовлетворяющего условию.

Jsdodgers
источник
2
from itertools import imap
sum(imap(my_condition, l))
kkonrad
источник
2
imapнедоступно с текущим Python.
Torsten Bronger 01