20

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
Community
  • 1
  • 1
Deon
  • 3,283
  • 3
  • 18
  • 8

5 Answers5

27

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.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
  • I like this -- maybe you can update it to convert the args to bools explicitly? – Rick Copeland Jun 23 '09 at 13:11
  • 3
    I think you mean to write ``sum(bool(a) for a in args) == 1`` -- the variables themselves might not be booleans. – elo80ka Jun 23 '09 at 13:13
  • I just love one-liners. I just learned something new about bools. – Deon Jun 23 '09 at 13:13
  • I think the fastest (with explicit conversion) would be only_one = lambda *args: sum(map(bool, args)) == 1 – Rick Copeland Jun 23 '09 at 13:16
  • 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 Jun 23 '09 at 13:19
  • 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 Jun 23 '09 at 13:21
  • 1
    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 Jun 23 '09 at 13:23
10

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)
A. Coady
  • 54,452
  • 8
  • 34
  • 40
  • 1
    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. – tzot Jun 28 '09 at 12:52
  • There's a healthy discussion of this usage here: http://stackoverflow.com/a/16801605/4403872, and here: http://stackoverflow.com/a/16522290/4403872 – vk1011 Feb 09 '16 at 18:39
5
>>> 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
  • 74,300
  • 25
  • 125
  • 131
  • I think this is the simplest answer, and the only one that is immediately clear. – A-y Oct 14 '20 at 14:53
  • I think @Andrew Hare 's answer is the one that fits this question, but this answer better fit my needs. I had 3 variables that can either be False or an integer value and I wanted to make sure that only one variable is an integer and the others are set to False. This one works fine – Abdelrahman Shoman Nov 08 '21 at 10:45
1

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
  • 14,837
  • 3
  • 36
  • 43
  • 1
    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 Jun 23 '09 at 14:35
  • You're right...though I guess the blame would be more on my faulty logic than `reduce`. Thanks for pointing it out. – elo80ka Jun 26 '09 at 10:43
1

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
  • 11,672
  • 5
  • 39
  • 38