在python中,我有一个列表应该只有一个 truthy值(即,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.有更聪明的方法吗?
一个不需要进口:
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
因为这样可以在一次迭代中完成计数和转换,而无需构建新的列表.
最详细的解决方案并不总是最不优雅的解决方案.因此,我只添加了一个小修改(为了保存一些冗余的布尔值评估):
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解决方案明显优于此处发布的大多数其他解决方案.正如预期的那样,最好的是具有短路行为的那些,尤其是Jon Clements发布的解决方案.至少对于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的方法再次显然更优越.
保留短路行为的单线答案:
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
.通过将其转换为列表并检查长度是否等于1,我们可以验证确实存在一个真实元素,而不需要在找到两个之后检查任何其他元素.
以下是一些时序比较:
设置代码:
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()
对于非常小的列表,该方法更快,但随着输入列表变大,即使无法进行短路,我的版本也会更快.当在大输入端上进行短路时,性能差异很明显.
我想获得死灵法师徽章,所以我概括了Jon Clements的优秀答案,保留了短路逻辑和快速谓词检查的好处.
这样就是:
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