97

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? Right now, I am just iterating across the list and manually checking:

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

This seems inelegant and not very pythonic. Is there a cleverer way to do this?

Matthew Scouten
  • 15,303
  • 9
  • 33
  • 50

17 Answers17

301

One that doesn't require imports:

def single_true(iterable):
    i = iter(iterable)
    return any(i) and not any(i)

Alternatively, perhaps a more readable version:

def single_true(iterable):
    iterator = iter(iterable)

    # consume from "i" until first true or it's exhausted
    has_true = any(iterator) 

    # carry on consuming until another true value / exhausted
    has_another_true = any(iterator) 

    # True if exactly one true found
    return has_true and not has_another_true

This:

  • Looks to make sure i has any true value
  • Keeps looking from that point in the iterable to make sure there is no other true value
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • 1
    x and not x is always False irregardless of x – Matthew Scouten May 28 '13 at 21:15
  • 34
    @MatthewScouten no... we're consuming from an iterable here... try running the code... – Jon Clements May 28 '13 at 21:16
  • 10
    Also, somewhat less relevantly, *irregardless* isn't a word. Anyway, +1, elegant and concise answer. – Gareth Latty May 28 '13 at 21:23
  • @Jon Clements: What the heck? You are right. but: I see not guarantee that any will only consume part of the iterable. And this will NEVER get past code review. no one will believe it until they try it. – Matthew Scouten May 28 '13 at 21:27
  • 7
    It makes complete sense. `any()` is guarenteed to stop consuming the second it hits a `True` value, and iterators will always be consumed. This is easily the best way to do this here. It's efficient, short and readable. – Gareth Latty May 28 '13 at 21:31
  • 13
    @MatthewScouten as per consumption of the iterable. `any` will as per docs return True as soon as a non-false value is found. After that, we look for a true value again, and if found treat it as a failure... So this'll work for empty lists, lists/other sequences, and any iterable... – Jon Clements May 28 '13 at 21:31
  • 13
    @MathewScouten Side effects break all theorems! `x and not x = False` is only correct if `x` is referentially transparent. – Ben May 28 '13 at 22:04
  • 2
    -1 for the same reason as [here](http://stackoverflow.com/a/16522290/674039) - this line is a mental stumbling block. readability counts. – wim May 29 '13 at 02:10
  • 4
    @Lattyware IMO it is not readable because it relies on hidden prior knowledge of the implementation detail of `any`. Someone who has experience with python knows that it's short-circuiting and works on infinite sequences, and consumes an iterator in the obvious way. But it is quite natural to [wrongly] assume it's behaving like a set-theoretic or mathematical abstraction, for example working on unordered collections like finite sets in parallel, rather than iterating and consuming. It is for this reason that the line looks awkward and confusing, as evidenced in the comments. – wim May 29 '13 at 02:19
  • 1
    I think the right question is whether or not it's idiomatic for the given language. Is it? I'm not a python person, but I can come up with several constructs that are idiomatic in C which I would find awkward in other languages. – Euro Micelli May 29 '13 at 02:40
  • If an infinite iterables fills the passes the test logically, this will never return. right? It only "works" for infinite iterables if they fail the test. – Matthew Scouten May 29 '13 at 03:48
  • @MatthewScouten You are almost correct - with infinite sequences, it would work for the failure cases where there are 2 trues. But there is also the failure case because of 0 trues, which would never return. – wim May 29 '13 at 06:19
  • 15
    @wim It is *not* an implementation detail of `any()` - it is a documented feature of the function, and the guaranteed functionality of any implementation that conforms to the Python spec. – Gareth Latty May 29 '13 at 08:00
  • 1
    @Lattyware [I'm well aware of that](http://bugs.python.org/issue17255). However, the line of code that looks like `any(i) and not any(i)` is implicitly hiding quite a bit of sophistication, and I think the OP's code is arguably more pythonic. The logic crucially relies on side-effects of `any`/`all` advancing iterators. Understanding that line also relies on knowing that the left side of `and` will be evaluated before the right side. It is not immediately obvious to see that the `i` on the left side is different than the `i` on the right side. Maybe if it was broken over multiple lines.. – wim May 29 '13 at 08:24
  • 8
    `Zen of python #17: If the implementation is hard to explain, it's a bad idea.` [The last time Jon Clements posted the same line of code](http://stackoverflow.com/a/16522290/674039) there was a pagefull of comments like "I had to read it at least 4 times until I understood it", "I don't get it. It reads like: if True and not True. Help me understand." and "Even with explanation I can't understand behavior" all from different users. – wim May 29 '13 at 08:43
  • 4
    I would argue it's not hard to explain, it's just not supremely readable. I agree that splitting it over a couple of lines, perhaps with an inline comment if you wanted to be super clear, might be worthwhile for readers of the code to ensure they understood. Given that it's in a function with an appropriate name, however, I don't think it's *too* hard to see what's going on, and the reality is any non-trivial code is never going to be instantly understood at first glance. – Gareth Latty May 29 '13 at 08:49
  • 8
    Maybe something like `seen_one = any(i); seen_another = any(i); return seen_one and not seen_another`. Although perhaps it's fixing up zen #2 at the cost of sacrificing zen #1 ... sigh :) – wim May 29 '13 at 08:52
  • 2
    I don't think that solution is so ugly - I'd argue that splitting it up like that makes it very clear and readable, while retaining a good method. – Gareth Latty May 29 '13 at 10:12
  • 4
    I've edited the code to split it up slightly - with some additional comments and explicit variable names – Jon Clements May 29 '13 at 10:25
  • 2
    I removed my downvote because the alternative looks good. The inline comments are overkill though, it is explicit enough to be self-commenting code . _It's the first version that needs a comment_. – wim May 29 '13 at 13:31
  • 2
    If I could accept 2, I would accept this as well. It has the advantages of being short, efficient, and clever. It also inspired interesting and useful conversation. The problem is that it looks badly wrong to the casual reader. – Matthew Scouten May 29 '13 at 14:05
  • 3
    @pnuts I voted -1 because I felt the answer had some problems in its initial form. I left a comment explaining what I felt was a shortcoming, and after some discussions the answer was subsequently edited by the author. This edit has improved the answer sufficiently and so I've retracted my downvote. Could you explain exactly where this is contrary to the desired SO spirit, or what this **desired SO spirit** means to you? – wim May 29 '13 at 14:12
  • @Lattyware irregardless is a word. – joneshf Jun 03 '13 at 09:57
  • 2
    @joneshf It's not. It's often used, but it's never been a word. If you look it up, [most dictionaries list it as "nonstandard" or "incorrect"](http://en.wikipedia.org/wiki/Irregardless). The correct word would have been one of *regardless* or *irrespective*. – Gareth Latty Jun 03 '13 at 11:08
  • 1
    @Lattyware, this is much more a discussion for english.stackexchange, so I'll keep it short, and not respond afterwards. Irregardless of what you think, it is a word. As reputable as wikipedia is, actual dictionaries (i.e. Merriam-Webster and Oxford) list it as a word. – joneshf Jun 04 '13 at 03:43
  • 1
    @joneshf ["Its reputation has not risen over the years, and it is still a long way from general acceptance. Use regardless instead."](http://www.merriam-webster.com/dictionary/irregardless) - the OED isn't online, but it also lists the word as incorrect. If you want to be technical, yes, the word exists - but it's a nonsensical word that shouldn't be used. When I say 'it's not a word', that was shorthand - clearly the word does exist, it's just not a good one to use. – Gareth Latty Jun 04 '13 at 11:48
  • 18
    Anyone who thinks this is not a readable solution should consider this: It is concise and relies only on known behaviors and common construct of Python. Just because a noob would not understand it, does not make it readable. It is also an excellent means of teaching what should be known since it incites immediate curiosity in those that do not see how it works. – dansalmo Jun 07 '13 at 18:58
  • 1
    +1 but inline comments in the second one are unnecessary, well chosen variable names ftw. – Andy Hayden Jun 10 '13 at 04:19
  • 4
    Re: all the discussion about readability here; I would guess that the reason many people have found this answer difficult to understand is *not* that they don't understand the behaviour of `any`, but that they don't realise the `iter()` creates a consumable iterator, or are unfamiliar with the concept of a consumable iterator altogether. A problem like checking that a list has a single `True` value is something that a complete newbie programmer might well be faced with; it's understandable that such people find needing to explicitly think about consumable iterators to be weird and hard. – Mark Amery Jan 10 '14 at 12:46
  • 1
    @MarkAmery You know what - I think you're probably right that's the main cognitive stumbling block here (it was also raised on a similar answer on another post). I did mean to come back to this post at some point but until now it has slipped my mind (although you did mention it on meta the other day). If I have a moment, I'll see if I can draft something a lot less "surprising"/"confusing"/"immediately wrong looking" - I wasn't completely happy with the update I made to it originally anyway. Thanks for your perspective and the reminder. – Jon Clements Jan 10 '14 at 17:23
  • 1
    @Jon Clements Given your last comment, what do you think of ``return not any(i) if any(i) else False`` to improve the readability ? I esteem your first answer to be astute and enough clear but it isn't the case for all apparently. – eyquem Apr 07 '14 at 21:07
  • @eyquem: `has_true and not has_another_true` seems readable enough. Though a link to the explanation that you can only iterate once over an iterator won't hurt i.e., the mere act of iteration "consumes" iterator in Python. Though I would use `it` or `iterator` in the first example. – jfs Apr 25 '14 at 02:48
  • 1
    Especially with the second version of the code, this is definitely the right answer. Easier to read _and_ uses more Pythony goodness. – Rob Grant May 12 '15 at 08:19
  • I learned a lot with this answer, both about the iter() function that I never used before and actually wonder when to use except for corner cases like this and about a special property of any that makes it very efficient. Clearly the second example is better. I would however emphasise more the role of `iter()` since I completely ignored it until I red the comments.. – Yohan Obadia Feb 09 '18 at 13:05
55

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.

David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • 3
    In Python 3: `list(map(bool, l)).count(True)` – poke May 28 '13 at 20:56
  • This only finds the literal True, not other true values (ie: positive ints not empty containers, etc) – Matthew Scouten May 28 '13 at 20:56
  • 6
    Just to point out to the OP this likely won't short circuit when more than one "True" value is found, so their code may give them more efficiency in certain circumstances. – NominSim May 28 '13 at 20:59
  • 2
    The second function can be written as `return sum(bool(e) for e in l) == 1`. `bool` subclasses `int` and True/False behave as 1/0 regarding arithmetic. –  May 28 '13 at 21:00
  • @delnan: Thanks, made that change – David Robinson May 28 '13 at 21:01
  • 1
    I would avoid using `l` as a variable name (it looks too much like `1` here), and I would rewrite `sum(bool(e) for e in l)` as `sum(1 for e in l if e)` – wim May 29 '13 at 02:21
  • I like this. Nice and clean. For my case however, I needed to count None as False, and the rest as True (including 0). But that's a small change to make: only1 = lambda: sum(e is not None for e in mylist) == 1 – vtlinh Aug 12 '14 at 23:59
47

The most verbose solution is not always the most unelegant solution. Therefore I add just a minor modification (in order to save some redundant boolean evaluations):

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

Here are some timings for comparison:

# 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

My output:

$ 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

As can be seen, the OP solution is significantly better than most other solutions posted here. As expected, the best ones are those with short circuit behavior, especially that solution posted by Jon Clements. At least for the case of two early True values in a long list.

Here the same for no True value at all:

$ 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

I did not check the statistical significance, but interestingly, this time the approaches suggested by F.J. and especially that one by Jon Clements again appear to be clearly superior.

moooeeeep
  • 31,622
  • 22
  • 98
  • 187
  • 4
    Umm - looking at the early true timings - isn't `0.446` the quickest? – Jon Clements May 28 '13 at 21:53
  • 2
    @JonClements that's why I wrote _most_, made it clearer now. (most of the posted, not most of the tested...) – moooeeeep May 28 '13 at 21:59
  • 1
    I suspect JonClement's is so fast because most of `any` is implemented in C – Matthew Scouten May 29 '13 at 03:57
  • 1
    +1 For your opening line. All the answers with `sum` are actually worse than the OP's simple and direct code .. – wim May 29 '13 at 13:36
  • I accepted this because it is what I decided to actually use for my code, and because it was the first answer with timings on it. @Jon Clements answer was a close runner up. – Matthew Scouten May 29 '13 at 14:08
  • Eh... perhaps this isn't the right call, but I'm throwing down a -1 for randomly bringing in detailed profiling where we have no reason to think it's warranted. There was no indication in the question that the most efficient solution possible was desired, so the fact that you've described the better performing solutions as 'superior' without any concern for other factors - like readability - just seems out of place. – Mark Amery Jul 05 '13 at 20:31
  • 2
    @MarkAmery I added both a section on readability and elegance (a short one admittedly) and on performance evaluation. As the question asked for cleverness both aspects should be subject of consideration, I think. As I see it, I have provided an answer to address both of these relevant aspects. If you feel this answer is not useful, feel free to downvote. – moooeeeep Jul 08 '13 at 16:32
24

A one-line answer that retains the short-circuiting behavior:

from itertools import ifilter, islice

def only1(l):
    return len(list(islice(ifilter(None, l), 2))) == 1

This will be significantly faster than the other alternatives here for very large iterables that have two or more true values relatively early.

ifilter(None, itr) gives an iterable that will only yield truthy elements (x is truthy if bool(x) returns True). islice(itr, 2) gives an iterable that will only yield the first two elements of itr. By converting this to a list and checking that the length is equal to one we can verify that exactly one truthy element exists without needing to check any additional elements after we have found two.

Here are some timing comparisons:

  • Setup code:

    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
    
  • Exhibiting short-circuit behavior:

    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
    
  • Large list where short-circuiting does not occur:

    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
    
  • Small list:

    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
    

So the sum() approach is faster for very small lists, but as the input list gets larger my version is faster even when short-circuiting is not possible. When short-circuiting is possible on a large input, the performance difference is clear.

Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • 6
    Ouch. Took me thrice as long as the other options to understand. If short-circuiting is important, I'd take OP's code as it is much more obvious and roughly as efficient. –  May 28 '13 at 21:02
  • 1
    Up vote for style, and retaining the short circuit. But this is harder to read. – Matthew Scouten May 28 '13 at 21:03
  • 1
    +1. The only one to reproduce the OP's full intention of short-circuiting. – NominSim May 28 '13 at 21:04
  • 1
    +1 if you provide some `timeit` experimentation for an objective performance-wise comparison to the OP solution. – moooeeeep May 28 '13 at 21:08
  • @moooeeeep Naively if you had an infinite iterable that has two `True` values somewhere "early on", this will finish, versus the other answers spinning their wheels forever trying to get the count. – NominSim May 28 '13 at 21:13
  • This can be done without imports as shown in my answer - but definitely nice to use `itertools` ;) – Jon Clements May 28 '13 at 21:26
  • @NominSim please have a look at the timings provided in my answer: http://stackoverflow.com/a/16801638/1025391 – moooeeeep May 28 '13 at 21:42
15

I wanted to earn the necromancer badge, so I generalized the Jon Clements' excellent answer, preserving the benefits of short-circuiting logic and fast predicate checking with any and all.

Thus here is:

N(trues) = n

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:

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:

def at_least_n_trues(iterable, n=1):
    i = iter(iterable)
    return all(any(i) for j in range(n))

m <= N(trues) <= 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)
12
>>> l = [0, 0, 1, 0, 0]
>>> has_one_true = len([ d for d in l if d ]) == 1
>>> has_one_true
True
gariel
  • 687
  • 3
  • 13
  • 4
    Why was this downvoted? I think it is the simplest and most readable of all. – dansalmo Jun 07 '13 at 19:00
  • 1
    @dansalmo: It's hard to be sure of course, but my theory is that many n00b python programmers - perhaps those with Java background in particular - feel more comfortable with longer syntax. (Myself, I used to be a little bit like that 5-10 years ago, but today I consider it unprofessional and ignorant.) +1 – Jonas Byström May 22 '17 at 06:45
8
if sum([bool(x) for x in list]) == 1

(Assuming all your values are booleanish.)

This would probably be faster just summing it

sum(list) == 1   

although it may cause some problems depending on the data types in your list.

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
5

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
karthikr
  • 97,368
  • 26
  • 197
  • 188
4

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))
Marc Laugharn
  • 1,307
  • 8
  • 11
4

This seems to work and should be able to handle any iterable, not justlists. It short-circuits whenever possible to maximize efficiency. Works in both Python 2 and 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])))

Output:

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
martineau
  • 119,623
  • 25
  • 170
  • 301
  • I like this approach, how about reworking the logic around `iter(x for x in my_list if x)` and then using `next`, maybe nicer than using `map` and `list.index` – wim May 29 '13 at 13:45
  • @wim: Although I didn't use the approach you suggested, your comment inspired me to revise my original answer and make it even more incremental in nature and gets rid of the `map` and `list.index`. – martineau May 29 '13 at 15:14
3

@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
Nisan.H
  • 6,032
  • 2
  • 26
  • 26
  • 1
    No, I mean at most. It returns True if at most N true-valued values exist: e.g. 3 Trues in a list of 1000 would get `iterable.count(True) = 3`, `NTrue(iterable, 1) = False`, `NTrue(iterable, 2) = False`, `NTrue(iterable, 3) = True`, `NTrue(iterable, 4) = True`, ... It basically extends the `and not any(i)` part to `and not any(i) and not any(i) and not... ` – Nisan.H May 28 '13 at 22:27
  • 1
    Doesn't `all(any(i) for i in xrange(n)) and not any(i)` work here? – Eric May 28 '13 at 23:15
  • @Eric that would only return True for _exactly_ n true's. It did give me the idea to sum over the `any`s, though. – Nisan.H May 29 '13 at 03:49
  • You rather mean `any(i) and not all(any(i) for x in xrange(n))` ? – moooeeeep Jun 06 '13 at 07:40
  • @moooeeeep Isn't `True and not all()` logically the same as `count(True) <= n`? The idea is still to test the smallest possible set and break on the first failure condition. – Nisan.H Jun 06 '13 at 16:14
  • There's no _at least one True_ in there. – moooeeeep Jun 07 '13 at 11:12
  • @moooeeeep I see. I edited to reflect that (basically test that the sum is within the range of [at_least, at_most] ) – Nisan.H Jun 07 '13 at 18:35
2
def only1(l)
    sum(map(lambda x: 1 if x else 0, l)) == 1

Explanation: The map function maps a list to another list, doing True => 1 and False => 0. We now have a list of 0s and 1s instead of True or False. Now we simply sum this list and if it is 1, there was only one True value.

Martin Konecny
  • 57,827
  • 19
  • 139
  • 159
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
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
1

Here's something that ought to work for anything truthy, though it has no short-circuit. I found it while looking for a clean way to forbid mutually-exclusive arguments:

if sum(1 for item in somelist if item) != 1:
    raise ValueError("or whatever...")
Andrew
  • 4,058
  • 4
  • 25
  • 37
1

What about:

len([v for v in l if type(v) == bool and v])

If you only want to count boolean True values.

Radek Svoboda
  • 177
  • 1
  • 8
0

Is this what you're looking for?

sum(l) == 1
c-urchin
  • 4,344
  • 6
  • 28
  • 30
  • 2
    This fails for a list: [2], since the author did not specify that the elements must only be True and False, or 1 and 0 – vtlinh Aug 12 '14 at 23:58
0
import collections

def only_n(l, testval=True, n=1):
    counts = collections.Counter(l)
    return counts[testval] == n

Linear time. Uses the built-in Counter class, which is what you should be using to check counts.

Re-reading your question, it looks like you actually want to check that there is only one truthy value, rather than one True value. Try this:

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

While you can get better best case performance, nothing has better worst-case performance. This is also short and easy to read.

Here's a version optimised for best-case performance:

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

The worst case performance has a high k (as in O(kn+c)), but it is completely general.

Here's an ideone to experiment with performance: http://ideone.com/ZRrv2m

Marcin
  • 48,559
  • 18
  • 128
  • 201