Условный подсчет в Python
Не уверен, что это было задано раньше, но я не мог найти очевидного ответа. Я пытаюсь подсчитать количество элементов в списке, равное определенному значению. Проблема в том, что эти элементы не имеют встроенного типа. Поэтому, если у меня есть
class A:
def __init__(self, a, b):
self.a = a
self.b = b
stuff = []
for i in range(1,10):
stuff.append(A(i/2, i%2))
Теперь мне нужно подсчитать элементы списка, поле b = 1. Я придумал два решения:
print [e.b for e in stuff].count(1)
и
print len([e for e in stuff if e.b == 1])
Какой лучший метод? Есть ли лучшая альтернатива? Похоже, что метод count() не принимает ключи (по крайней мере, в версии 2.5.1 Python.
Большое спасибо!
Ответы
Ответ 1
sum(x.b == 1 for x in L)
Логическое значение (как результат сравнения, например x.b == 1
) также равно int
со значением 0
для False
, 1
для True
, поэтому арифметика, такая как суммирование, работает просто хорошо.
Это простейший код, но, возможно, не самый быстрый (только timeit
может вам точно сказать;-). Рассмотрим (упрощенный случай, чтобы хорошо вписываться в командные строки, но эквивалентно):
$ py26 -mtimeit -s'L=[1,2,1,3,1]*100' 'len([x for x in L if x==1])'
10000 loops, best of 3: 56.6 usec per loop
$ py26 -mtimeit -s'L=[1,2,1,3,1]*100' 'sum(x==1 for x in L)'
10000 loops, best of 3: 87.7 usec per loop
Итак, для этого случая подход "потеря памяти" для создания дополнительного временного списка и проверки его длины на самом деле прочно быстрее, чем более простая, более короткая, экономящая память, которую я предпочитаю. Разумеется, другие сочетания значений списка, реализации Python, доступность памяти для "инвестиций" в это ускорение и т.д. Могут влиять на точную производительность.
Ответ 2
print sum(1 for e in L if e.b == 1)
Ответ 3
Я предпочел бы второй, поскольку он только циклически перебирает список.
Если вы используете count()
, вы перебираете список один раз, чтобы получить значения b
, а затем снова перебираете его, чтобы увидеть, сколько из них равно 1.
В аккуратном способе можно использовать reduce()
:
reduce(lambda x,y: x + (1 if y.b == 1 else 0),list,0)
Документация сообщает нам, что reduce()
будет:
Применить функцию двух аргументов кумулятивно к элементам итерации слева направо, чтобы уменьшить итерацию до одного значения.
Итак, мы определяем a lambda
, который добавляет одно накопленное значение, только если атрибут b
элемента списка равен 1.
Ответ 4
Чтобы скрыть детали reduce
, вы можете определить функцию count
:
def count(condition, stuff):
return reduce(lambda s, x: \
s + (1 if condition(x) else 0), stuff, 0)
Затем вы можете использовать его, указав условие для подсчета:
n = count(lambda i: i.b, stuff)