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 True
s should be one:
def only_1(l): return 1 == len(filter(None, l))