In python, I have a list that should have one and only one truthy value (that is, bool(value) is True). Is there a clever way to check for this
@JonClements` solution extended for at most N True values:
# 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)
edit: better version
def test(iterable, n=1):
i = iter(iterable)
return sum(any(i) for x in xrange(n+1)) <= n
edit2: include at least m True's and at most n True's
def test(iterable, n=1, m=1):
i = iter(iterable)
return m <= sum(any(i) for x in xrange(n+1)) <= n
It depends if you are just looking for the value True or are also looking for other values that would evaluate to True logically (like 11 or "hello"). If the former:
def only1(l):
return l.count(True) == 1
If the latter:
def only1(l):
return sum(bool(e) for e in l) == 1
since this would do both the counting and the conversion in a single iteration without having to build a new list.
You can do:
x = [bool(i) for i in x]
return x.count(True) == 1
Or
x = map(bool, x)
return x.count(True) == 1
Building on @JoranBeasley's method:
sum(map(bool, x)) == 1
For completeness' sake and to demonstrate advanced use of Python's control flow for for loop iteration, one can avoid the extra accounting in the accepted answer, making this slightly faster.:
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.
The above's simple control flow makes use of Python's sophisticated feature of loops: the else. The semantics are that if you finish iterating over the iterator that you are consuming without break-ing out of it, you then enter the else block.
Here's the accepted answer, which uses a bit more accounting.
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
to time these:
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
So we see that the accepted answer takes a bit longer (slightly more than one and a half of a percent).
Naturally, using the built-in any, written in C, is much faster (see Jon Clement's answer for the implementation - this is the short form):
>>> min(timeit.repeat(lambda: single_true([0]*100 + [1, 1])))
2.7257133318785236
>>> min(timeit.repeat(lambda: single_true([1, 1] + [0]*100)))
2.012824866380015
If there is only one True, then the length of the Trues should be one:
def only_1(l): return 1 == len(filter(None, l))