Hasnep
Hasnep

Reputation: 121

How can I check that a Python list contains only True and then only False using one or two lines?

I would like to only allow lists where the first contiguous group of elements are True and then all of the remaining elements are False. I want lists like these examples to return True:

And lists like these to return False:

I am currently using this function, but I feel like there is probably a better way of doing this:

def my_function(x):
    n_trues = sum(x)
    should_be_true = x[:n_trues]  # get the first n items
    should_be_false = x[n_trues:len(x)]  # get the remaining items
    # return True only if all of the first n elements are True and the remaining
    # elements are all False
    return all(should_be_true) and all([not element for element in should_be_false])

Testing:

test_cases = [[True], [False],
              [True, False],
              [True, False, False],
              [True, True, True, False],
              [False, True],
              [True, False, True]]
print([my_function(test_case) for test_case in test_cases])
# expected output: [True, True, True, True, True, False, False]

Is it possible to use a comprehension instead to make this a one/two line function? I know I could not define the two temporary lists and instead put their definitions in place of their names on the return line, but I think that would be too messy.

Upvotes: 1

Views: 660

Answers (1)

Mad Physicist
Mad Physicist

Reputation: 114240

Method 1

You could use itertools.groupby. This would avoid doing multiple passes over the list and would also avoid creating the temp lists in the first place:

def check(x):
    status = list(k for k, g in groupby(x))
    return len(status) <= 2 and (status[0] is True or status[-1] is False)

This assumes that your input is non-empty and already all boolean. If that's not always the case, adjust accordingly:

def check(x):
    status = list(k for k, g in groupby(map(book, x)))
    return status and len(status) <= 2 and (status[0] or not status[-1])

If you want to have empty arrays evaluate to True, either special case it, or complicate the last line a bit more:

return not status or (len(status) <= 2 and (status[0] or not status[-1]))

Method 2

You can also do this in one pass using an iterator directly. This relies on the fact that any and all are guaranteed to short-circuit:

def check(x):
    iterator = iter(x)
    # process the true elements
    all(iterator)
    # check that there are no true elements left
    return not any(iterator)

Personally, I think method 1 is total overkill. Method 2 is much nicer and simpler, and achieves the same goals faster. It also stops immediately if the test fails, rather than having to process the whole group. It also doesn't allocate any temporary lists at all, even for the group aggregation. Finally, it handles empty and non-boolean inputs out of the box.

Since I'm writing on mobile, here's an IDEOne link for verification: https://ideone.com/4MAYYa

Upvotes: 2

Related Questions