Как я могу проверить, что список имеет одно и только одно правдоподобное значение?
В 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
Самое подробное решение не всегда является самым неэлегантным решением. Поэтому я добавляю только незначительную модификацию (чтобы сохранить некоторые избыточные булевы оценки):
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
Я не проверял статистическую значимость, но интересно, на этот раз подходы, предложенные Ф .J., и особенно то, что Джон Клементс снова кажется явно превосходящим.
Ответ 2
Тот, который не требует импорта:
def single_true(iterable):
i = iter(iterable)
return any(i) and not any(i)
Альтернативно, возможно, более читаемая версия:
def single_true(iterable):
iterator = iter(iterable)
has_true = any(iterator) # consume from "i" until first true or it exhuasted
has_another_true = any(iterator) # carry on consuming until another true value / exhausted
return has_true and not has_another_true # True if exactly one true found
Это:
- Предполагается, что
i
имеет любое истинное значение
- Сохраняет возможность поиска с этой точки в iterable, чтобы убедиться, что нет другого истинного значения
Ответ 3
Это зависит, если вы просто ищете значение True
или также ищете другие значения, которые будут оцениваться с помощью True
логически (например, 11
или "hello"
). Если первое:
def only1(l):
return l.count(True) == 1
Если последнее:
def only1(l):
return sum(bool(e) for e in l) == 1
поскольку это будет делать как подсчет, так и преобразование в одной итерации без необходимости создавать новый список.
Ответ 4
Однострочный ответ, который сохраняет короткое замыкание:
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()
работает быстрее для очень маленьких списков, но по мере увеличения списка ввода моя версия работает быстрее, даже если короткое замыкание невозможно. Когда короткое замыкание возможно на большом входе, разница в производительности очевидна.
Ответ 5
Я хотел получить значок некроманта, поэтому я обобщил отличный ответ Джона Клемента, сохранив преимущества короткозамкнутой логики и быстрой проверки предикатов с любыми и всеми.
Таким образом, здесь:
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 (trues) >= 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)
Ответ 6
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
Ответ 7
Вы можете сделать:
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
Ответ 8
Это, похоже, работает и должно иметь возможность обрабатывать любые итеративные, а не только 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])))
Вывод:
only1(testcase[0]): False
only1(testcase[1]): False
only1(testcase[2]): True
only1(testcase[3]): True
only1(testcase[4]): True
only1(testcase[5]): False
only1(testcase[6]): False
Ответ 9
if sum([bool(x) for x in list]) == 1
(Предполагая, что все ваши значения являются booleanish.)
Это, вероятно, быстрее, просто суммируя его
sum(list) == 1
хотя это может вызвать некоторые проблемы в зависимости от типов данных в вашем списке.
Ответ 10
Если есть только один True
, тогда длина True
должна быть одной:
def only_1(l): return 1 == len(filter(None, l))
Ответ 11
@Решение 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 True и не более n True
def test(iterable, n=1, m=1):
i = iter(iterable)
return m <= sum(any(i) for x in xrange(n+1)) <= n
Ответ 12
def only1(l)
sum(map(lambda x: 1 if x else 0, l)) == 1
Объяснение: Функция map
отображает список в другой список, делая True => 1
и False => 0
. Теперь у нас есть список 0s и 1s вместо True или False. Теперь мы просто суммируем этот список, и если он равен 1, было только одно значение True.
Ответ 13
Для полноты и для демонстрации расширенного использования потока управления Python для итерации цикла можно избежать дополнительного учета в принятом ответе, сделав это немного быстрее.:
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
Ответ 14
Это то, что вы ищете?
sum(l) == 1
Ответ 15
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
Ответ 16
Здесь что-то, что должно работать на что-нибудь правдоподобное, хотя оно не имеет короткого замыкания. Я нашел его, ища чистый способ запретить взаимоисключающие аргументы:
if sum(1 for item in somelist if item) != 1:
raise ValueError("or whatever...")
Ответ 17
Как насчет:
len([v for v in l if type(v) == bool and v])
Если вы хотите только подсчитать логические значения True.