In Python, how to you check to see if a list of items is exclusive to one list out of two?

StackOverflow https://stackoverflow.com/questions/23527802

  •  17-07-2023
  •  | 
  •  

Question

I have two lists that contain mutually exclusive items - let's pick oil and water based liquids for these lists since these naturally can't mix:

waters = ['tea', 'lemonade', 'juice', 'pepsi']
oils = ['olive oil', 'corn oil', 'peanut oil']

I want to test if foo only contains items in the water list or the oils list but NOT both. such that:

foo = ['tea', 'corn oil'] => FAIL
bar = ['pepsi', 'tea', 'juice'] => PASS
baz = ['olive oil'] => PASS

my attempt so far:

def contains_conflicting_types(test, targets, conflicts):
    intarget = False
    inconflict = False
    for t in test:
        if t in targets:
            intarget = True
        if t in conflicts:
            inconflict = True
        if intarget and inconflict:
            return True
    return False

#Usage:
contains_conflicting_types(['A','B'], ['A','1'], ['B','2']) #returns True A and B are in conflict

needless to say it's ugly but works? how can I do it better?

Was it helpful?

Solution

Define everything as sets (or convert them to sets) and then it's straightforward set/bitwise ops:

bool(oils & foo) ^ bool(waters & foo)
Out[19]: False

bool(oils & bar) ^ bool(waters & bar)
Out[20]: True

bool(oils & baz) ^ bool(waters & baz)
Out[21]: True

OTHER TIPS

A quick one-liner might look like:

def contains_conflicting_types(test, targets, conflicts):
    return not(all(t in targets for t in test) or all(t in conflicts for t in test))

This would be faster if targets and conflicts were sets, since in that case the in operator would work in constant time. If you can't make the inputs sets, then you can write:

def contains_conflicting_types(test, targets, conflicts):
    targets, conflicts = set(targets), set(conflicts)
    return not(all(t in targets for t in test) or all(t in conflicts for t in test))

If test is also a set, then you can take advantage of the overloaded <= operator that does subset checks and write:

def contains_conflicting_types(test, targets, conflicts):
    return not (test <= targets or test <= conflicts)

Given the two sets to compare against

waters = frozenset(['tea', 'lemonade', 'juice', 'pepsi'])
oils = frozenset(['olive oil', 'corn oil', 'peanut oil'])

And a test group

foo = frozenset(['tea', 'corn oil'])

You can determine if the set contains items from exclusively one group (using the XOR operator) by checking if the sets are disjoint (two sets are disjoint if their intersection is the empty set).

foo.isdisjoint(waters) ^ foo.isdisjoint(oils)

For Python 2.5 and older, use:

bool(foo.intersection(waters)) ^ bool(foo.intersection(oils))

Alternatively, if you can remember that & is the intersection operator when acting on two sets. Because readability counts, if you or other people who are (or will be) maintaining your code aren't sure what the & character means without looking it up, just use s1.intersection(s2).

bool(foo & waters) ^ bool(foo & oils)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top