В python у меня есть список, в котором должно быть одно и только одно истинное значение (то есть bool(value) is True
). Есть ли умный способ проверить это? Сейчас я просто просматриваю список и вручную проверяю:
def only1(l)
true_found = False
for v in l:
if v and not true_found:
true_found=True
elif v and true_found:
return False #"Too Many Trues"
return true_found
Это кажется неэлегантным и не очень питоническим. Есть ли более умный способ сделать это?
(= 1 (count-if #'identity list))
.sum(lst) == 1
True
или только одно истинное значение?Ответы:
Самое подробное решение - не всегда самое нелегкое. Поэтому я добавляю небольшую модификацию (чтобы сохранить некоторые избыточные логические оценки):
def only1(l): true_found = False for v in l: if v: # a True was found! if true_found: # found too many True's return False else: # found the first True true_found = True # found zero or one True value return true_found
Вот несколько таймингов для сравнения:
# file: test.py from itertools import ifilter, islice def OP(l): true_found = False for v in l: if v and not true_found: true_found=True elif v and true_found: return False #"Too Many Trues" return true_found def DavidRobinson(l): return l.count(True) == 1 def FJ(l): return len(list(islice(ifilter(None, l), 2))) == 1 def JonClements(iterable): i = iter(iterable) return any(i) and not any(i) def moooeeeep(l): true_found = False for v in l: if v: if true_found: # found too many True's return False else: # found the first True true_found = True # found zero or one True value return true_found
Мой вывод:
$ python -mtimeit -s 'import test; l=[True]*100000' 'test.OP(l)' 1000000 loops, best of 3: 0.523 usec per loop $ python -mtimeit -s 'import test; l=[True]*100000' 'test.DavidRobinson(l)' 1000 loops, best of 3: 516 usec per loop $ python -mtimeit -s 'import test; l=[True]*100000' 'test.FJ(l)' 100000 loops, best of 3: 2.31 usec per loop $ python -mtimeit -s 'import test; l=[True]*100000' 'test.JonClements(l)' 1000000 loops, best of 3: 0.446 usec per loop $ python -mtimeit -s 'import test; l=[True]*100000' 'test.moooeeeep(l)' 1000000 loops, best of 3: 0.449 usec per loop
Как видно, решение OP значительно лучше, чем большинство других решений, размещенных здесь. Как и ожидалось, лучшие - это те, которые имеют поведение короткого замыкания, особенно решение, опубликованное Джоном Клементсом. По крайней мере, в случае двух ранних
True
значений в длинном списке.Здесь то же самое без всякой
True
ценности:$ python -mtimeit -s 'import test; l=[False]*100000' 'test.OP(l)' 100 loops, best of 3: 4.26 msec per loop $ python -mtimeit -s 'import test; l=[False]*100000' 'test.DavidRobinson(l)' 100 loops, best of 3: 2.09 msec per loop $ python -mtimeit -s 'import test; l=[False]*100000' 'test.FJ(l)' 1000 loops, best of 3: 725 usec per loop $ python -mtimeit -s 'import test; l=[False]*100000' 'test.JonClements(l)' 1000 loops, best of 3: 617 usec per loop $ python -mtimeit -s 'import test; l=[False]*100000' 'test.moooeeeep(l)' 100 loops, best of 3: 1.85 msec per loop
Я не проверял статистическую значимость, но, что интересно, на этот раз подходы, предложенные FJ, и особенно подход Джона Клементса, снова оказались явно лучше.
источник
0.446
самый быстрый?any
них реализована на Csum
на самом деле хуже, чем простой и прямой код OP ..Тот, который не требует импорта:
def single_true(iterable): i = iter(iterable) return any(i) and not any(i)
В качестве альтернативы, возможно, более читабельная версия:
def single_true(iterable): iterator = iter(iterable) # consume from "i" until first true or it's exhausted has_true = any(iterator) # carry on consuming until another true value / exhausted has_another_true = any(iterator) # True if exactly one true found return has_true and not has_another_true
Этот:
i
имеет истинную ценностьисточник
any
согласно документации вернет True, как только будет найдено значение, отличное от ложного. После этого мы снова ищем истинное значение, и, если оно найдено, рассматриваем его как сбой ... Так что это будет работать для пустых списков, списков / других последовательностей и любых повторяемых ...x and not x = False
правильно только в том случае, еслиx
оно прозрачно по ссылкам.any()
- это задокументированная возможность функции и гарантированная функциональность любой реализации, соответствующей спецификации Python.Это зависит от того, ищете ли вы просто значение
True
или также ищете другие значения, которые будут оцениватьсяTrue
логически (например,11
или"hello"
). Если первое:def only1(l): return l.count(True) == 1
Если последнее:
def only1(l): return sum(bool(e) for e in l) == 1
поскольку при этом будут выполняться и подсчет, и преобразование за одну итерацию без создания нового списка.
источник
list(map(bool, l)).count(True)
return sum(bool(e) for e in l) == 1
.bool
подклассыint
и True / False ведут себя как 1/0 в отношении арифметики.l
в качестве имени переменной (это слишком похоже на1
здесь), и я бы переписалsum(bool(e) for e in l)
какsum(1 for e in l if e)
Однострочный ответ, который сохраняет поведение короткого замыкания:
from itertools import ifilter, islice def only1(l): return len(list(islice(ifilter(None, l), 2))) == 1
Это будет значительно быстрее, чем другие альтернативы здесь для очень больших итераций, которые имеют два или более истинных значения относительно рано.
ifilter(None, itr)
дает итерацию, которая будет давать только правдивые элементы (x
правдиво, еслиbool(x)
возвращаетсяTrue
).islice(itr, 2)
дает итерацию, которая даст только первые два элементаitr
. Преобразуя это в список и проверяя, что длина равна единице, мы можем проверить, что существует ровно один истинный элемент, без необходимости проверять какие-либо дополнительные элементы после того, как мы нашли два.Вот несколько сравнений по времени:
Код установки:
In [1]: from itertools import islice, ifilter In [2]: def fj(l): return len(list(islice(ifilter(None, l), 2))) == 1 In [3]: def david(l): return sum(bool(e) for e in l) == 1
Показывает поведение при коротком замыкании:
In [4]: l = range(1000000) In [5]: %timeit fj(l) 1000000 loops, best of 3: 1.77 us per loop In [6]: %timeit david(l) 1 loops, best of 3: 194 ms per loop
Большой список, где не происходит короткого замыкания:
In [7]: l = [0] * 1000000 In [8]: %timeit fj(l) 100 loops, best of 3: 10.2 ms per loop In [9]: %timeit david(l) 1 loops, best of 3: 189 ms per loop
Небольшой список:
In [10]: l = [0] In [11]: %timeit fj(l) 1000000 loops, best of 3: 1.77 us per loop In [12]: %timeit david(l) 1000000 loops, best of 3: 990 ns per loop
Таким образом,
sum()
подход быстрее для очень маленьких списков, но по мере увеличения списка ввода моя версия работает быстрее, даже когда короткое замыкание невозможно. Когда короткое замыкание возможно на большом входе, разница в производительности очевидна.источник
timeit
экспериментов для объективного сравнения производительности с решением OP.True
значения где-то «на раннем этапе», это закончится, по сравнению с другими ответами, которые постоянно крутят свои колеса, пытаясь подсчитать.Я хотел заработать значок некроманта, поэтому обобщил отличный ответ Джона Клементса, сохранив преимущества логики короткого замыкания и быстрой проверки предикатов для всех и каждого.
Таким образом, вот:
N (истина) = n
def n_trues(iterable, n=1): i = iter(iterable) return all(any(i) for j in range(n)) and not any(i)
N (истина) <= n:
def up_to_n_trues(iterable, n=1): i = iter(iterable) all(any(i) for j in range(n)) return not any(i)
N (истина)> = n:
def at_least_n_trues(iterable, n=1): i = iter(iterable) return all(any(i) for j in range(n))
m <= N (истина) <= n
def m_to_n_trues(iterable, m=1, n=1): i = iter(iterable) assert m <= n return at_least_n_trues(i, m) and up_to_n_trues(i, n - m)
источник
>>> l = [0, 0, 1, 0, 0] >>> has_one_true = len([ d for d in l if d ]) == 1 >>> has_one_true True
источник
Ты можешь сделать:
x = [bool(i) for i in x] return x.count(True) == 1
Или же
x = map(bool, x) return x.count(True) == 1
Основываясь на методе @ JoranBeasley:
sum(map(bool, x)) == 1
источник
if sum([bool(x) for x in list]) == 1
(Предполагая, что все ваши значения логические.)
Это, вероятно, было бы быстрее, просто суммируя это
sum(list) == 1
хотя это может вызвать некоторые проблемы в зависимости от типов данных в вашем списке.
источник
Если есть только один
True
, то длинаTrue
s должна быть равна единице:def only_1(l): return 1 == len(filter(None, l))
источник
Кажется, это работает и должно быть в состоянии обрабатывать любые итерации, а не только
list
s. По возможности он замыкает накоротко, чтобы максимизировать эффективность. Работает как в Python 2, так и в 3.def only1(iterable): for i, x in enumerate(iterable): # check each item in iterable if x: break # truthy value found else: return False # no truthy value found for x in iterable[i+1:]: # one was found, see if there are any more if x: return False # found another... return True # only a single truthy value found testcases = [ # [[iterable, expected result], ... ] [[ ], False], [[False, False, False, False], False], [[True, False, False, False], True], [[False, True, False, False], True], [[False, False, False, True], True], [[True, False, True, False], False], [[True, True, True, True], False], ] for i, testcase in enumerate(testcases): correct = only1(testcase[0]) == testcase[1] print('only1(testcase[{}]): {}{}'.format(i, only1(testcase[0]), '' if correct else ', error given '+str(testcase[0])))
Вывод:
источник
iter(x for x in my_list if x)
а затем использоватьnext
, может быть, лучше, чем использованиеmap
иlist.index
map
иlist.index
.Решение @ JonClements расширено не более чем на N истинных значений :
# Extend any() to n true values def _NTrue(i, n=1): for x in xrange(n): if any(i): # False for empty continue else: return False return True def NTrue(iterable, n=1): i = iter(iterable) return any(i) and not _NTrue(i, n)
изменить: лучшая версия
def test(iterable, n=1): i = iter(iterable) return sum(any(i) for x in xrange(n+1)) <= n
edit2: включить не менее m истинных и не более n истинных
def test(iterable, n=1, m=1): i = iter(iterable) return m <= sum(any(i) for x in xrange(n+1)) <= n
источник
iterable.count(True) = 3
,NTrue(iterable, 1) = False
,NTrue(iterable, 2) = False
,NTrue(iterable, 3) = True
,NTrue(iterable, 4) = True
, ... Это в основном распространяется наand not any(i)
часть кand not any(i) and not any(i) and not...
all(any(i) for i in xrange(n)) and not any(i)
работает?any
Тем не менее, это натолкнуло меня на мысль суммировать по s.any(i) and not all(any(i) for x in xrange(n))
?True and not all(<n booleans>)
Логически не то же самое, чтоcount(True) <= n
? Идея по-прежнему состоит в том, чтобы проверить наименьшее возможное множество и отключить при первом отказе.def only1(l) sum(map(lambda x: 1 if x else 0, l)) == 1
Объяснение:
map
Функция отображает список в другой список, делаяTrue => 1
иFalse => 0
. Теперь у нас есть список из 0 и 1 вместо True или False. Теперь мы просто суммируем этот список, и если он равен 1, то было только одно значение True.источник
Это то, что вы ищете?
sum(l) == 1
источник
Для полноты картины и демонстрации расширенного использования потока управления Python для итерации цикла for можно избежать лишнего учета в принятом ответе, что сделает это немного быстрее:
def one_bool_true(iterable): it = iter(iterable) for i in it: if i: break else: #no break, didn't find a true element return False for i in it: # continue consuming iterator where left off if i: return False return True # didn't find a second true.
В приведенном выше простом потоке управления используется сложная функция циклов Python: расширение
else
. Семантика такова, что если вы завершите итерацию по итератору, который вы используете, неbreak
выходя из него, вы затем войдете вelse
блок.Вот принятый ответ, в котором используется немного больше учета.
def only1(l): true_found = False for v in l: if v: # a True was found! if true_found: # found too many True's return False else: # found the first True true_found = True # found zero or one True value return true_found
время эти:
import timeit >>> min(timeit.repeat(lambda: one_bool_true([0]*100 + [1, 1]))) 13.992251592921093 >>> min(timeit.repeat(lambda: one_bool_true([1, 1] + [0]*100))) 2.208037032979064 >>> min(timeit.repeat(lambda: only1([0]*100 + [1, 1]))) 14.213872335107908 >>> min(timeit.repeat(lambda: only1([1, 1] + [0]*100))) 2.2482982632641324 >>> 2.2482/2.2080 1.0182065217391305 >>> 14.2138/13.9922 1.0158373951201385
Итак, мы видим, что принятый ответ занимает немного больше времени (чуть больше полутора процентов).
Естественно, использование встроенного
any
, написанного на C, намного быстрее (см. Ответ Джона Клемента по реализации - это краткая форма):>>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1]))) 2.7257133318785236 >>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100))) 2.012824866380015
источник
import collections def only_n(l, testval=True, n=1): counts = collections.Counter(l) return counts[testval] == n
Линейное время. Использует встроенный класс Counter, который вы должны использовать для проверки счетчиков.
Перечитывая свой вопрос, похоже, вы действительно хотите проверить, что существует только одно истинное значение, а не одно
True
значение. Попробуй это:import collections def only_n(l, testval=True, coerce=bool, n=1): counts = collections.Counter((coerce(x) for x in l)) return counts[testval] == n
Хотя вы можете добиться лучшей производительности в лучшем случае, ничто не может быть лучше в худшем случае. Это тоже коротко и легко читается.
Вот версия, оптимизированная для наилучшей производительности:
import collections import itertools def only_n(l, testval=True, coerce=bool, n=1): counts = collections.Counter() def iterate_and_count(): for x in itertools.imap(coerce,l): yield x if x == testval and counts[testval] > n: break counts.update(iterate_and_count()) return counts[testval] == n
В худшем случае производительность высока
k
(как вO(kn+c)
), но это вполне обычное явление.Вот идеон для экспериментов с производительностью: http://ideone.com/ZRrv2m
источник
Вот что-то, что должно работать для чего-нибудь правдивого, хотя в нем нет короткого замыкания. Я нашел это, когда искал чистый способ запретить взаимоисключающие аргументы:
if sum(1 for item in somelist if item) != 1: raise ValueError("or whatever...")
источник
Что о:
len([v for v in l if type(v) == bool and v])
Если вы хотите подсчитать только логические значения True.
источник