在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。有更聪明的方法吗?
答案 0 :(得分:220)
不需要进口的商品:
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
具有任何真实值答案 1 :(得分:45)
这取决于您是仅仅查找值True
还是正在寻找其他逻辑上评估为True
的值(例如11
或"hello"
)。如果是前者:
def only1(l):
return l.count(True) == 1
如果是后者:
def only1(l):
return sum(bool(e) for e in l) == 1
因为这样可以在一次迭代中完成计数和转换,而无需构建新列表。
答案 2 :(得分:38)
最详细的解决方案并不总是最不优雅的解决方案。因此,我只添加了一个小修改(为了保存一些冗余的布尔评估):
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
我没有检查统计显着性,但有趣的是,这一次F.J.建议的方法,特别是Jon Clements所提出的方法似乎显然更优越。
答案 3 :(得分:20)
保留短路行为的单行答案:
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()
方法更快,但随着输入列表变大,即使无法进行短路,我的版本也会更快。当在大输入端上进行短路时,性能差异很明显。
答案 4 :(得分:13)
我想获得死灵法师徽章,所以我概括了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)
答案 5 :(得分:11)
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
答案 6 :(得分:4)
你可以这样做:
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
答案 7 :(得分:4)
如果只有一个True
,则True
的长度应为1:
def only_1(l): return 1 == len(filter(None, l))
答案 8 :(得分:4)
这似乎有效,应该能够处理任何迭代,而不仅仅是list
。它尽可能短路,以最大限度地提高效率。适用于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 :(得分:3)
if sum([bool(x) for x in list]) == 1
(假设你的所有值都是booleanish。)
这可能会更快只是总结它
sum(list) == 1
虽然根据列表中的数据类型可能会导致一些问题。
答案 10 :(得分:3)
@ 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真的和最多n真的
def test(iterable, n=1, m=1):
i = iter(iterable)
return m <= sum(any(i) for x in xrange(n+1)) <= n
答案 11 :(得分:2)
def only1(l)
sum(map(lambda x: 1 if x else 0, l)) == 1
说明:map
函数将列表映射到另一个列表,执行True => 1
和False => 0
。我们现在有一个0和1的列表,而不是True或False。现在我们简单地对这个列表求和,如果它是1,那么只有一个True值。
答案 12 :(得分:1)
为了完整性并且为了演示循环迭代的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
所以我们看到接受的答案需要更长的时间(略高于1.5%)。
当然,使用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
答案 13 :(得分:0)
这是你要找的吗?
sum(l) == 1
答案 14 :(得分:0)
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
答案 15 :(得分:0)
这里的东西应该适用于任何真正的东西,尽管它没有短路。我找到了一个干净的方式来禁止相互排斥的论点:
http://journals.plos.org/plosone/s/file?id=wjVg/PLOSOne_formatting_sample_main_body.pdf
答案 16 :(得分:0)
怎么样:
len([v for v in l if type(v) == bool and v])
如果你只想计算布尔真值。