我怎样才能检查一个清单是否只有一个真值?

在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 

这似乎不雅,不是pythonic。 有一个更聪明的方法来做到这一点?

最详细的解决scheme并不总是最不起眼的解决scheme。 因此,我只是添加一个小修改(为了保存一些冗余的布尔评估):

 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解决scheme明显优于大多数其他解决scheme。 正如所料,最好的是那些短路行为,尤其是Jon Clements发布的解决scheme。 至less在长列表中有两个早期的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提出的方法,特别是Jon Clements的方法似乎再次明显地优越。

一个不需要import:

 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's 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有任何真正的价值
  • 从迭代中的这一点保持观察,以确保没有其他真正的价值

这取决于你是否正在寻找True值,或者是正在寻找其他逻辑值为True值(如11"hello" )。 如果前者:

 def only1(l): return l.count(True) == 1 

如果后者:

 def only1(l): return sum(bool(e) for e in l) == 1 

因为这将在一次迭代中进行计数和转换,而无需build立新的列表。

一个保留短路行为的答案:

 from itertools import ifilter, islice def only1(l): return len(list(islice(ifilter(None, l), 2))) == 1 

对于具有两个或更多真值相对较早的非常大的迭代,这将比其他替代scheme快得多。

ifilter(None, itr)给出了一个只能产生真值元素的迭代(如果bool(x)返回TruexTrue )。 islice(itr, 2)给出了一个迭代器,它将只产生itr的前两个元素。 通过将其转换为列表并检查长度是否等于1,我们可以validation只有一个truthy元素存在,而不需要在find两个元素之后检查任何其他元素。

以下是一些时间比较:

  • 设置代码:

     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()方法更快,但是随着input列表变大,我的版本更快,即使短路是不可能的。 当大input端短路时,性能差异很明显。

我想要获得死灵法师的徽章,所以我推广了Jon Clements的出色答案,保留了短路逻辑和快速谓词检查的好处。

因此这里是:

N(trues)= n

 def n_trues(iterable, n=1): i = iter(iterable) return all(any(i) for j in range(n)) and not any(i) 

N(trues)<= 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(trues)<= 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 

build立在@ JoranBeasley的方法:

 sum(map(bool, x)) == 1 

这似乎工作,应该能够处理任何迭代,而不仅仅是list 。 它尽可能短路,以最大限度地提高效率。 适用于Python 2和Python 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 

如果只有一个“ True ,那么“ True的长度应该是一个:

 def only_1(l): return 1 == len(filter(None, l)) 
 if sum([bool(x) for x in list]) == 1 

(假设你所有的值都是粗略的)

总结起来可能会更快

 sum(list) == 1 

虽然它可能会导致一些问题取决于您的列表中的数据types。

@ JonClements`解决scheme最多可扩展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: 至less包含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 
 def only1(l) sum(map(lambda x: 1 if x else 0, l)) == 1 

说明: map函数将一个列表映射到另一个列表,做True => 1False => 0 。 我们现在有一个0和1的列表,而不是真或假。 现在我们简单地总结这个列表,如果它是1,那么只有一个真值。

为了完整起见,为了演示用于循环迭代的Python的控制stream的高级使用,可以避免在接受的答案中进行额外的计算,使其略快。

 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. 

上面简单的控制stream程利用了Python复杂的循环特性: else 。 语义是,如果你完成迭代你正在使用的迭代器,而不break它,然后你inputelse块。

这是接受的答案,它使用更多的会计。

 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 

所以我们看到接受的答案需要更长的时间(略多于一个半百分之一)。

当然,使用C语言编写的内置any快得多(请参阅Jon Clement的实现答案 – 这是简写):

 >>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1]))) 2.7257133318785236 >>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100))) 2.012824866380015 

这是你在找什么?

 sum(l) == 1 
 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 

虽然您可以获得更好的最佳性能performance,但没有什么比最佳的性能performance更好。 这也是简短易读的。

以下是针对最佳性能优化的版本:

 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

这里有一些东西应该适合任何事情,尽pipe它没有短路。 我find了一个干净的方法,禁止相互排斥的论点:

 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]) 

如果你只想计算布尔真值。