Условный подсчет в 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() не принимает ключи (по крайней мере, в Python версии 2.5.1.

Большое спасибо!


person nicolaum    schedule 19.11.2009    source источник
comment
Не рекомендуется называть список как «список».   -  person MAK    schedule 19.11.2009
comment
Я полностью согласен, и изменил название списка.   -  person nicolaum    schedule 19.11.2009


Ответы (5)


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, доступность памяти для «инвестирования» в это ускорение и т. д., конечно, могут повлиять на точную производительность.

person Alex Martelli    schedule 19.11.2009
comment
Возможно, стоит объяснить, как это работает. Не для всех будет очевидно, что вы можете сложить список логических значений. - person Dave Webb; 19.11.2009
comment
Кроме того, почему этот подход лучше, чем: len([e для e в списке, если e.b == 1]), который не должен суммировать элементы? - person nicolaum; 19.11.2009
comment
Не занимая позицию в отношении того, что именно лучше, это позволяет избежать формирования целого списка, который на самом деле ни для чего не нужен. - person Mike Graham; 19.11.2009
comment
Ага! Это объяснение сделало это для меня, большое спасибо, Майк. Сначала я понял, что это создание списка и вызов метода sum() для списков. Все это имеет смысл сейчас. - person nicolaum; 19.11.2009
comment
@nicolaum и @Dave, я добавил подробное объяснение и сроки, которые на самом деле показывают, что бесполезный подход со списком работает быстрее, чем простой (по крайней мере, в одном примере). Самое простое не всегда самое быстрое: иногда, если у вас есть свободная и неиспользуемая память, в любом случае инвестирование может быть компромиссом, экономящим ваше время. - person Alex Martelli; 19.11.2009
comment
Интересно со сроками. Я думаю, поскольку циклы знают свою длину, они делают это быстрее. sum() Я думаю, что это обертка вокруг reduce(), поэтому я предполагаю, что все эти косвенные вызовы функций делают его медленным. - person Dave Webb; 19.11.2009
comment
@ Дэйв, я тот, кто изначально закодировал sum в интерпретаторе CPython, и я уверяю вас, что он даже не приблизится близко к reduce (не знаю, откуда вы взяли эту идею). - person Alex Martelli; 20.11.2009
comment
Хотя это очень умный и самый короткий код из всех примеров, я считаю его наименее питоническим и очевидным. len([x for x in L if cond]) многословен и содержит некоторую избыточность, но сразу становится понятно, что это значит. - person Ben James; 20.11.2009
comment
@ Бен, я думаю, что кое-что знаю о Pythonic - и любой, кто не знает, что логические значения можно суммировать (единственный способ, которым sum может быть неочевидным!-), достаточно новичок, чтобы не быть действительно квалифицированным. Что-то вроде печально часто встречающейся (для новичков) идиомы if x: return True // else: return False (по сравнению с очевидным return bool(x)), чтобы провести параллель, нет ничего умного в правильном использовании логических значений, для чего они есть - это просто самый простой и очевидный способ сделать это ( поскольку подход с использованием списка отходов быстрее, то, конечно, он оправдан!, но не основан на простоте!-) - person Alex Martelli; 20.11.2009
comment
@ Алекс, я не согласен с тем, что здесь есть параллель, что полностью избыточное использование логических значений явно бессмысленно и вообще не ограничивается питоном. - person Ben James; 20.11.2009
comment
@Ben, суммирование логических значений также не ограничивается Python - и в C правильный способ подсчета количества элементов › 5 - это total += (*pitm++) > 5, не if ((*pitm++)>5) total += 1 (напрямую используйте тот факт, что сравнения возвращают 0 и 1, не бегайте по кварталу, пытаясь избежать этого простого факта!-). - person Alex Martelli; 21.11.2009
comment
@Alex, что я считаю неочевидным в вашем подходе, так это не то, что логические значения можно суммировать (вы правы, это довольно очевидно для большинства языков). Неочевидно (по крайней мере для меня), почему вы делаете sum(x.b == 1 for x in L) вместо sum([x.b == 1 for x in L]). Ответ (после поиска в документации) заключается в том, что sum() ожидает итерируемый объект. Я просто предположил, что sum ожидает кортеж или список, так как все примеры его использования используют их. В остальном ваше решение довольно очевидно. - person nicolaum; 21.11.2009
comment
@Dave и @Alex, я думаю, что комментарий Дейва о reduce можно объяснить выдержкой из документации: [Обратите внимание, что sum(range(n), m) эквивалентно reduce(operator.add, range(n), m).] - person nicolaum; 21.11.2009
comment
@nicolaum, я уверен, что есть много бесполезно создаваемых списков (особенно в Python 2), где было бы достаточно итераций (Python 3 помогает, делая неявные конструкции списков намного реже - например, range больше не список, и adict.items, и так далее) -- отчасти потому, что списки - это более старая концепция, отчасти потому, что некоторые люди не находят итерации очевидными... и частично из-за len, который не принимает итерации (не может, потому что это должно быть O (1), в то время как итерация по своей сути O (N) - и это различие несколько тонкое). И да, эта фраза в документах верна, но глупа ;-). - person Alex Martelli; 21.11.2009
comment
@AlexMartelli: Если вы не используете трюк с суммированием логических значений, вы действительно можете получить код, который (с Python 2.7) на 30% быстрее, что близко к скорости версии len, то есть sum(1 for x in L if x==1) немного быстрее, чем sum(x==1 for x in L) для меня (с использованием Python 2.7 x64). - person Frerich Raabe; 02.12.2013
comment
У меня противоположные результаты. В Debian Linux: dlavrentyuk@b368:/var/log/mongodb$ python -mtimeit -s'L=[1,2,1,3,1]*100' 'len([x вместо x в L, если x== 1])' 10000 циклов, лучший из 3: 40,3 мкс на цикл dlavrentyuk@b368:/var/log/mongodb$ python -mtimeit -s'L=[1,2,1,3,1]*100' 'len ([x==1 для x в L])' 10000 циклов, лучшее из 3: 38,3 мкс на цикл - person arilou; 07.10.2014
comment
На Windiws-7: C:\Users\danil.lavrentyuk\work›python -mtimeit -sL=[1,2,1,3,1]*100 len([x вместо x в L, если x==1]) 10000 циклов, лучший из 3: 29,4 мкс на цикл C:\Users\danil.lavrentyuk\work›python -mtimeit -sL=[1,2,1,3,1]*100 len([x==1 for x в L]) 10000 циклов, лучшее из 3: 22,7 мкс на цикл - person arilou; 07.10.2014

Я бы предпочел второй, так как он только один раз перебирает список.

Если вы используете count(), вы перебираете список один раз, чтобы получить значения b, а затем снова перебираете его, чтобы увидеть, сколько из них равно 1.

Аккуратный способ может использовать reduce():

reduce(lambda x,y: x + (1 if y.b == 1 else 0),list,0)

Документация говорит нам, что reduce() будет:

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

Поэтому мы определяем lambda, который добавляет единицу к накопленному значению, только если атрибут b элемента списка равен 1.

person Dave Webb    schedule 19.11.2009
comment
Такой подход мне нравится больше всего. Не понимаю, почему он не получает голосов. - person phunehehe; 03.06.2011
comment
@phunehehe: я полагаю, что он не получил голосов, поскольку это, безусловно, самая медленная и самая многословная альтернатива, предложенная здесь. - person Frerich Raabe; 02.12.2013
comment
Забавно, я уже не помню. Может быть, этот ответ соответствует тому, что я делал (чего я тоже не помню): D - person phunehehe; 03.12.2013

Чтобы скрыть детали 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)
person Calvin    schedule 27.12.2014

Учитывая ввод

name = ['ball', 'jeans', 'ball', 'ball', 'ball', 'jeans']
price = [1, 4, 1, 1, 1, 4]
weight = [2, 2, 2, 3, 2, 2]

Сначала создайте defaultdict для записи события

from collections import defaultdict
occurrences = defaultdict(int)

Увеличить количество

for n, p, w in zip(name, price, weight):
    occurrences[(n, p, w)] += 1

Наконец, подсчитайте те, которые появляются более одного раза (True даст 1)

print(sum(cnt > 1 for cnt in occurrences.values())
person Alan Wu    schedule 26.02.2021

person    schedule
comment
Хороший, я думаю, что это более читаемая версия ответа Алекса Мартелли, суммирование 1 более очевидно, чем знание того, что True можно рассматривать как 1. - person Tendayi Mawushe; 19.11.2009
comment
Он также хорошо подходит в качестве общего шаблона: например, sum(len(n) for n in L if n.b == 1). - person ; 19.11.2009
comment
@TendayiMawushe: суммирование 1 вместо логических значений также примерно на 30% быстрее, по крайней мере, при использовании Python 2.7 (см. мой комментарий к ответу Алекса). - person Frerich Raabe; 02.12.2013