tags:

views:

188

answers:

5

I'm looking for a simple method to check if only one variable in a list of variables has a True value. I've looked at this logical xor post and is trying to find a way to adapt to multiple variables and only one true.

Example

>>>TrueXor(1,0,0)
True

>>>TrueXor(0,0,1)
True

>>>TrueXor(1,1,0)
False

>>>TrueXor(0,0,0,0,0)
False
+2  A: 
>>> def f(*n):
...     n = [bool(i) for i in n]
...     return n.count(True) == 1
...
>>> f(0, 0, 0)
False
>>> f(1, 0, 0)
True
>>> f(1, 0, 1)
False
>>> f(1, 1, 1)
False
>>> f(0, 1, 0)
True
>>>
FogleBird
+6  A: 

There isn't one built in but it's not to hard to roll you own:

def TrueXor(*args):
    return sum(args) == 1

Since "[b]ooleans are a subtype of plain integers" (source) you can sum the list of integers quite easily and you can also pass true booleans into this function as well.

So these two calls are homogeneous:

TrueXor(1, 0, 0)
TrueXor(True, False, False)

If you want explicit boolean conversion: sum( bool(x) for x in args ) == 1.

Andrew Hare
I like this -- maybe you can update it to convert the args to bools explicitly?
Rick Copeland
I think you mean to write ``sum(bool(a) for a in args) == 1`` -- the variables themselves might not be booleans.
elo80ka
I just love one-liners. I just learned something new about bools.
Deon
I think the fastest (with explicit conversion) would be only_one = lambda *args: sum(map(bool, args)) == 1
Rick Copeland
Perhaps I am missing something - why bool() the arguments when there is an implicit conversion? I understand that "explicit is better than implicit" but still it doesn't make much sense to me to do an explicit conversion in this case.
Andrew Hare
Say the arguments are strings -- in this case, there is no implicit conversion in your function. Same for tuples, lists, dicts, sets... none work with the builtin sum().
Rick Copeland
Good point. I guess I would rather the function explode on bad input rather than use the bool() function and mask a problem. Still, good point :)
Andrew Hare
A: 

The question you linked to already provides the solution for two variables. All you have to do is extend it to work on n variables:

import operator

def only_one_set(*vars):
    bools = [bool(v) for v in vars]
    return reduce(operator.xor, bools, False)

>>> a, b, c, d, e = False, '', [], 10, -99
>>> only_one_set(a, b, c, d)
True
>>> only_one_set(a, b, c, d, e)
False
elo80ka
As usual, reduce is a false friend: this actually checks if there's an ODD number of true values -- it will be just as happy with 7 or 77 ones as with just 1!
Alex Martelli
You're right...though I guess the blame would be more on my faulty logic than `reduce`. Thanks for pointing it out.
elo80ka
+1  A: 

Here's my straightforward approach. I've renamed it only_one since xor with more than one input is usually a parity checker, not an "only one" checker.

def only_one(*args):
    result = False
    for a in args:
        if a:
            if result:
                return False
            else:
                result = True
    return result

Testing:

>>> only_one(1,0,0)
True
>>> only_one(0,0,1)
True
>>> only_one(1,1,0)
False
>>> only_one(0,0,0,0,0)
False
>>> only_one(1,1,0,1)
False
Rick Copeland
+1  A: 

I think the sum-based solution is fine for the given example, but keep in mind that boolean predicates in python always short-circuit their evaluation. So you might want to consider something more consistent with all and any.

def any_one(iterable):
    it = iter(iterable)
    return any(it) and not any(it)
Coady
Nice. You should explain that the `not any(it)` works on the rest of the items left over by the `any(it)`, for people not that comfortable with iterators.
ΤΖΩΤΖΙΟΥ