4

I want to get all exclusive elements between all my lists. So if I have 3 lists like:

list1 = [1, 3, 2]
list2 = ["a", 1, 3]
list3 = [2, 0]

My output should be:

['a', 0]

I tried to do symmetric differencing with all of the lists like:

set(list1) ^ set(list2) ^ set(list3)

But this doesn´t work well.

Also I tried:

def exclusive(*lista):
    excl = set(lista[0])
    for idx in range(len(lista)):
        excl ^= set(lista[idx])
    return excl

That works the same as the first method but it doesn´t produce what I want.

Then I tried (set(list1) ^ set(list2)) ^ (set(list2) ^ (set(list3)) and found that it's not the same as what I first tried.

EDIT:

I give 3 list as an example but function take undifined number of arguments

Daniel
  • 53
  • 5
  • 5
    What do you mean "doesn't work well"? `set(list1) ^ set(list2) ^ set(list3)` works perfectly fine. – Aran-Fey May 01 '19 at 16:57
  • 1
    If you want a list as the result, convert the result to a list: `list(set(list1) ^...)`. – DYZ May 01 '19 at 16:59
  • 1
    @Aran-Fey No, it does not work perfectly fine. Symmetric difference on 3 sets would return the intersection of the 3 sets in addition to the exclusive items. – blhsing May 01 '19 at 17:17
  • 1
    Changing the values of your inputs would better highlight the issues that result from sequential symmetric differencing for more than 2 sets. For example, try the proposed solutions with the following inputs in this order: `[1, 3, 2, 0]`, `['a', 1, 3, 0]`, `[2, 0]`. In this case, the only exclusive element that should result is "a". – benvc May 01 '19 at 17:36
  • 1
    @DYZ I prefer in set. I just want the group of elements – Daniel May 01 '19 at 18:43
  • 1
    Possible duplicate of [how to add multiple sets in python but only if items are unique](https://stackoverflow.com/questions/48674775/how-to-add-multiple-sets-in-python-but-only-if-items-are-unique) – pault May 01 '19 at 19:33
  • @pault Im new. Do I delete this post then? – Daniel May 01 '19 at 19:47
  • @DanielMuñoz I'm not sure if you can at this point. It's fine to keep it anyway, duplicates are okay - they can act as sign posts. – pault May 01 '19 at 20:05

3 Answers3

5

You could also take a non-set approach using collections.Counter:

from itertools import chain
from collections import Counter

res = [k for k, v in Counter(chain(list1, list2, list3)).items() if v==1]
print(res)
#['a', 0]

Use itertools.chain to flatten your lists together and use Counter to count the occurrences. Keep only those where the count is 1.


Update: Here is a better example that demonstrates why the other methods do not work.

list1 = [1, 3, 2]
list2 = ["a", 1, 3]
list3 = [2, 0]
list4 = [1, 4]
all_lists = [list1, list2, list3, list4]

Based on your criteria, the correct answer is:

print([k for k, v in Counter(chain(*all_lists)).items() if v==1])
#['a', 4, 0]

Using reduce(set.symmetric_difference, ...):

sets = map(set, all_lists)
print(reduce(set.symmetric_difference, sets))
#{0, 1, 4, 'a'}

Using the symmetric difference minus the intersection:

set1 = set(list1)
set2 = set(list2)
set3 = set(list3)
set4 = set(list4)

print((set1 ^ set2 ^ set3 ^ set4) - (set1 & set2 & set3 & set4))
#{0, 1, 4, 'a'}
pault
  • 41,343
  • 15
  • 107
  • 149
  • It doesn’t function if I have two or more equal elements in one list: `[1]` and `[1, 2, 2]` will return an empty list instead of `[2, 2]`. – Mykola Zotko May 01 '19 at 17:50
  • 1
    @MykolaZotko this is a true statement, but it's not how I am interpreting the requirements of the question, based on the fact that the OP converted their lists to sets in their attempt. – pault May 01 '19 at 17:52
  • @pault I convert the lists is sets to elimanate any problem with duplicate elements – Daniel May 01 '19 at 18:40
  • @pault I almost understand your answer but dont **Counter().items()**. – Daniel May 01 '19 at 19:08
  • @DanielMuñoz - [collections.Counter](https://docs.python.org/3/library/collections.html#collections.Counter) is a subclass of `dict` so `Counter.items()` returns a view (sort of like a list) of key value pairs. In the case of `Counter`, the key is the element and the value is the count. – benvc May 01 '19 at 19:19
0

You should subtract the intersection of the 3 sets from the symmetric difference of the 3 sets in order to get the exclusive items:

set1 = set(list1)
set2 = set(list2)
set3 = set(list3)

(set1 ^ set2 ^ set3) - (set1 & set2 & set3)

so that given:

list1 = [1,3,2]
list2 = ["a",1,3]
list3 = [2,0,1]

this returns:

{0, 'a'}

whereas your attempt of set1 ^ set2 ^ set3 would incorrectly return:

{0, 1, 'a'}
blhsing
  • 91,368
  • 6
  • 71
  • 106
  • I still don't think this works in general- I updated my answer to include an example that demonstrates this. **Edit**: In my updated example, the issue is that `set1 & set2 & set3 & set4` returns an empty set. – pault May 01 '19 at 17:40
  • 2
    Yes, I should have noted that the method only works for 3 sets. – blhsing May 01 '19 at 17:49
0

This can be done primarily with set operations, but I prefer the simplicity of the answer from @pault. In order to get the symmetric difference of an arbitrary number of sets, you can find the intersection among all set combinations and then get the symmetric difference of that combined intersection from a union of all sets.

from itertools import combinations

def symdiff(*sets):
    union = set()
    union.update(*sets)

    intersect = set()
    for a, b in combinations(sets, 2):
        intersect.update(a.intersection(b))

    return intersect.symmetric_difference(union)

distincts = symdiff(set([1, 3, 2]), set(['a', 1, 3]), set([2, 0]))
print(distincts)
# {0, 'a'}

Following are better example inputs where a simple sequential symmetric difference of the sets would not provide the same result.

distincts = symdiff(set([1, 3, 2, 0]), set(['a', 1, 3, 0]), set([2, 0]))
print(distincts)
# {'a'}
benvc
  • 14,448
  • 4
  • 33
  • 54