How can I check that a list has one and only one truthy value?

后端 未结 17 2163
暗喜
暗喜 2020-11-27 12:23

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

相关标签:
17条回答
  • 2020-11-27 12:56

    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.

    0 讨论(0)
  • 2020-11-27 13:00

    What about:

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

    If you only want to count boolean True values.

    0 讨论(0)
  • 2020-11-27 13:05

    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
    
    0 讨论(0)
  • 2020-11-27 13:05

    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...")
    
    0 讨论(0)
  • 2020-11-27 13:06

    Is this what you're looking for?

    sum(l) == 1
    
    0 讨论(0)
  • 2020-11-27 13:07
    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.

    0 讨论(0)
提交回复
热议问题