How can I check that a list has one and only one truthy value?
在Python中,我有一个列表,该列表应该只有一个Truthy值(即,
1 2 3 4 5 6 7 8 | 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 |
这看起来不太漂亮,也不是很像Python。有更聪明的方法吗?
不需要进口的:
1 2 3 | def single_true(iterable): i = iter(iterable) return any(i) and not any(i) |
号
或者,可能是更可读的版本:
1 2 3 4 5 | 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 有任何真正的价值 - 从ITerable中的这一点继续查看,以确保没有其他真正的值
这取决于您是只查找
1 2 | def only1(l): return l.count(True) == 1 |
如果后者:
1 2 | def only1(l): return sum(bool(e) for e in l) == 1 |
号
因为这将在一次迭代中同时进行计数和转换,而不必构建一个新的列表。
最冗长的解决方案并不总是最不合理的解决方案。因此,我只添加了一个小修改(为了保存一些冗余的布尔值计算):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
号
以下是一些比较时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | # 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 |
号
我的输出:
1 2 3 4 5 6 7 8 9 10 | $ 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解决方案明显优于这里发布的大多数其他解决方案。正如所料,最好的是那些有短路行为的,尤其是乔恩·克莱门茨发布的解决方案。至少对于长列表中的两个早期
在这里,没有
1 2 3 4 5 6 7 8 9 10 | $ 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 |
号
我没有检查统计学的显著性,但有趣的是,这次F.J.提出的方法,特别是乔恩·克莱门茨提出的方法,显然更优越。
保持短路行为的一行应答:
1 2 3 4 | from itertools import ifilter, islice def only1(l): return len(list(islice(ifilter(None, l), 2))) == 1 |
。
对于具有两个或两个以上真实值的非常大的iterables,这将比这里的其他替代方案快得多。
以下是一些时间比较:
设置代码:
1
2
3
4
5In [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表现出短路行为:
1
2
3
4
5
6
7In [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。
不发生短路的大列表:
1
2
3
4
5
6
7In [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号
小列表:
1
2
3
4
5
6
7In [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号
因此,对于非常小的列表,
我想赢得"巫师"徽章,所以我概括了乔恩·克莱门茨的优秀答案,保留了短路逻辑和快速谓词检查的优点。
因此,这里是:
n(trues)=n
1 2 3 | 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:
1 2 3 4 | 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:
1 2 3 | def at_least_n_trues(iterable, n=1): i = iter(iterable) return all(any(i) for j in range(n)) |
。
m<=n(trues)<=n
1 2 3 4 | 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) |
。
1 2 3 4 | >>> l = [0, 0, 1, 0, 0] >>> has_one_true = len([ d for d in l if d ]) == 1 >>> has_one_true True |
这似乎是可行的,应该能够处理任何不可行的,而不是just
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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]))) |
号
输出:
1 2 3 4 5 6 7 | 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 |
你可以这样做:
1 2 | x = [bool(i) for i in x] return x.count(True) == 1 |
或
1 2 | x = map(bool, x) return x.count(True) == 1 |
。
基于@joranbeasley的方法:
1 | sum(map(bool, x)) == 1 |
。
如果只有一个
1 | def only_1(l): return 1 == len(filter(None, l)) |
号
1 | if sum([bool(x) for x in list]) == 1 |
(假设你所有的价值观都是虚伪的。)
这可能会更快,只要求和它
1 | sum(list) == 1 |
。
尽管根据列表中的数据类型,它可能会导致一些问题。
@joncements的解决方案最多可扩展n个真值:
1 2 3 4 5 6 7 8 9 10 11 12 | # 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) |
编辑:更好的版本
1 2 3 | def test(iterable, n=1): i = iter(iterable) return sum(any(i) for x in xrange(n+1)) <= n |
。
edit2:至少包含m true和至多n true
1 2 3 | def test(iterable, n=1, m=1): i = iter(iterable) return m <= sum(any(i) for x in xrange(n+1)) <= n |
号
1 2 | def only1(l) sum(map(lambda x: 1 if x else 0, l)) == 1 |
说明:
为了完整起见,为了演示python的控制流在for循环迭代中的高级使用,可以避免在接受的答案中使用额外的计算,从而使这个过程稍微快一点。
1 2 3 4 5 6 7 8 9 10 11 | 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复杂的循环特性:
这是公认的答案,它使用了更多的会计方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
。
对这些进行计时:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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语言编写的内置
1 2 3 4 | >>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1]))) 2.7257133318785236 >>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100))) 2.012824866380015 |
。
怎么办:
1 | len([v for v in l if type(v) == bool and v]) |
号
如果只想计算布尔真值。
这就是你要找的吗?
1 | sum(l) == 1 |
1 2 3 4 5 | import collections def only_n(l, testval=True, n=1): counts = collections.Counter(l) return counts[testval] == n |
线性时间。使用内置的counter类,这是您应该用来检查计数的。
重新阅读你的问题,看起来你真的想要检查只有一个真实的值,而不是一个
1 2 3 4 5 | 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 |
号
虽然您可以获得更好的最佳情况性能,但没有任何东西具有更好的最坏情况性能。这也很短,很容易阅读。
下面是一个优化的版本,以获得最佳案例性能:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
最坏情况下的性能有很高的
下面是一个Ideone的性能实验:http://ideone.com/zrrv2m
这里有一些东西,应该为任何真实的工作,尽管它没有短路。我在寻找一种明确的方法来禁止相互排斥的论点时发现了这一点:
1 2 | if sum(1 for item in somelist if item) != 1: raise ValueError("or whatever...") |
号