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

В 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.