Как увеличивать два счетчика в зависимости от булевого выражения и от обратного ему

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

Нам надо посчитать, сколько раз случилось True, а сколько раз наоборот, False.

Как это сделать, проверив условие два раза - понятно:

import random
positive_counter = 0
negative_counter = 0

for _ in range(10):
    input_var = random.random()
    positive_counter += input_var > 0.5
    negative_counter += input_var <= 0.5 # ну или not(input_var > 0.5)


print(positive_counter, negative_counter)

Вопрос в том, как это можно сделать не проверяя условие два раза и желательно в одном выражении.


Ответы (2 шт):

Автор решения: CrazyElf

Считаем через словарь (или список), как уже предложили в комментариях:

import random

counter = {True: 0, False: 0}
for _ in range(10):
    input_var = random.random()
    counter[input_var > 0.5] += 1

print(*counter.values())

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

import random

n = 10
positive = sum(random.random() > 0.5 for _ in range(n))
print(positive, n - positive)

P.S. Список вместо словаря будет чуть-чуть быстрее, за счёт того, что словарю нужно ещё хэш элементов вычислять, чтобы узнать индекс доступа, а списку не нужно, у него сразу готовый индекс есть. Только нужно помнить, что в этом случае позитивный счётчик идёт вторым элементом:

import random

counter = [0, 0]
for _ in range(10):
    input_var = random.random()
    counter[input_var > 0.5] += 1
negative, positive = counter
print(positive, negative)
→ Ссылка
Автор решения: Serge3leo

Сравним варианты выражений без двойной проверки с вариантом двойной проверки:

import random
n = 100_000
rnds = [random.random() for _ in range(n)]
%%timeit
sum(r > 0.2 for r in rnds)

5.2 ms ± 24.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
sum(1000_000_000 + (r > 0.2) for r in rnds)

8.38 ms ± 51.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit max_n = 1000_000_000
sum(max_n + (r > 0.2) for r in rnds)

8.46 ms ± 20.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit lst = [0, 0]
for r in rnds:
    lst[r > 0.2] += 1

8.69 ms ± 42 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit s, c = 0, 0
for r in rnds:
    s += r > 0.2
    c += 1

8.77 ms ± 172 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit dct = {True: 0, False: 0}
for r in rnds: 
    dct[r > 0.2] += 1

9.21 ms ± 202 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit a, b = 0, 0
for r in rnds:
    cnd = r > 0.2
    a += cnd
    b += not cnd

10.4 ms ± 16.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit positive_counter, negative_counter = 0, 0
for r in rnds:
    positive_counter += r > 0.2
    negative_counter += r <= 0.2

12.6 ms ± 205 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Да, похоже, что овчинка стоит выделки. И, если есть бесплатная информация об общем числе значений, то её стоит использовать.

Варианты вида max_n + (r > 0.2) хороши для использования в sum() и прочих итераторах, но в операторах вида s += ... они такие же, как вариант списка (lst = [0, 0]).

→ Ссылка