Python União, Interseção e Diferença

Na aula Conjuntos, você viu que o set guarda valores sem repetir. A outra grande força dele são as contas entre conjuntos, as mesmas dos diagramas de bolinhas da escola: juntar dois grupos, ver quem está nos dois, quem está só em um. A Ana dá aula de violão e de inglês no centro comunitário, e essas contas respondem na hora perguntas como "quem faz as duas aulas?" e "quem faz só violão?".

Você vai aprender
  • A união (|): todos de um grupo ou do outro.
  • A interseção (&): só quem está nos dois.
  • A diferença (-): quem está num e não está no outro, e por que a ordem importa.
  • A diferença simétrica (^): quem está em um só.
  • Os métodos com nome (union, intersection…) e quando preferi-los.
  • A perguntar se um grupo está dentro do outro (<=, issubset) e a atualizar no lugar (|=, update).
  • Os erros reais ao misturar conjunto com lista e ao usar + ou and.

A ideia: as duas bolinhas

Desenhe duas bolinhas que se cruzam. Na da esquerda, os alunos de violão: Ana, Bia, Caio e Davi. Na da direita, os de inglês: Bia, Davi e Eva. A Bia e o Davi fazem as duas aulas, então ficam no pedaço do meio, onde as bolinhas se cruzam.

violao ingles Ana Caio Bia Davi Eva
As duas turmas da Ana. O meio é quem faz as duas aulas.
PerguntaPedaço do desenhoNomeEm Python
Quem faz alguma das aulas?As duas bolinhas inteirasUniãoviolao | ingles
Quem faz as duas?Só o meioInterseçãoviolao & ingles
Quem faz só violão?A esquerda, sem o meioDiferençaviolao - ingles
Quem faz só uma das duas?As pontas, sem o meioDiferença simétricaviolao ^ ingles

1. As quatro contas de uma vez

violao = {"Ana", "Bia", "Caio", "Davi"}
ingles = {"Bia", "Davi", "Eva"}
print("União:", sorted(violao | ingles))
print("Interseção:", sorted(violao & ingles))
print("Só violão:", sorted(violao - ingles))
print("Só inglês:", sorted(ingles - violao))
print("Só uma das duas:", sorted(violao ^ ingles))
$ python3 o_turmas.py
União: ['Ana', 'Bia', 'Caio', 'Davi', 'Eva']
Interseção: ['Bia', 'Davi']
Só violão: ['Ana', 'Caio']
Só inglês: ['Eva']
Só uma das duas: ['Ana', 'Caio', 'Eva']
violao | ingles
União. A barra vertical | lembra "ou": quem está em violao ou em ingles. A Bia e o Davi aparecem uma vez só, porque o resultado também é um conjunto.
violao & ingles
Interseção. O & ("e comercial") lembra "e": quem está em violao e em ingles.
violao - ingles
Diferença. Como numa subtração: comece com a turma de violão e tire quem também está na de inglês.
ingles - violao
A diferença ao contrário dá outro resultado: quem faz só inglês. Na diferença, a ordem importa.
violao ^ ingles
Diferença simétrica. O acento circunflexo ^ quer dizer "em um dos dois, mas não nos dois". É a união menos a interseção.
sorted( … )
Cada conta devolve um conjunto novo, e conjunto de textos não tem ordem fixa. O sorted() põe o resultado em ordem alfabética para o print sair sempre igual.

Nenhuma das contas muda violao ou ingles: todas criam um conjunto novo, do mesmo jeito que 2 + 3 não muda o 2 nem o 3.

Com números, sem sorted

Com números inteiros pequenos, o print do conjunto sai sempre igual (lembre da aula anterior: a "arrumação" dos números não muda de uma execução para outra), então dá para ver o conjunto direto:

a = {1, 2, 3, 4}
b = {3, 4, 5}
print(a | b)
print(a & b)
print(a - b)
print(b - a)
print(a ^ b)
$ python3 o_numeros.py
{1, 2, 3, 4, 5}
{3, 4}
{1, 2}
{5}
{1, 2, 5}

Missão: a Ana e a Bia fizeram cada uma a sua lista de compras. Mostre, em ordem alfabética, o que está nas duas listas (para não comprar em dobro). Troque o ? pela conta certa entre ana e bia.

ana = {"pão", "leite", "café", "ovos"}
bia = {"leite", "arroz", "pão"}
print(sorted(ana))  # troque ana pela conta: ana ? bia
ana = {"pão", "leite", "café", "ovos"}
bia = {"leite", "arroz", "pão"}
print(sorted(ana & bia))

2. Os mesmos cálculos com nome: os métodos

Cada símbolo tem um método equivalente, com o nome em inglês da conta. O resultado é o mesmo:

violao = {"Ana", "Bia", "Caio", "Davi"}
ingles = {"Bia", "Davi", "Eva"}
print(sorted(violao.union(ingles)))
print(sorted(violao.intersection(ingles)))
print(sorted(violao.difference(ingles)))
print(sorted(violao.symmetric_difference(ingles)))
print(sorted(violao.intersection(["Eva", "Ana", "Ana"])))
$ python3 o_metodos.py
['Ana', 'Bia', 'Caio', 'Davi', 'Eva']
['Bia', 'Davi']
['Ana', 'Caio']
['Ana', 'Caio', 'Eva']
['Ana']

Repare na última linha: o método aceitou uma lista (com repetido e tudo). Essa é a grande diferença entre os dois jeitos: os símbolos só funcionam entre dois conjuntos; os métodos aceitam qualquer coisa que tenha itens (lista, tupla, texto) e convertem sozinhos.

Mais de dois conjuntos

As contas podem ser encadeadas. Os clientes que vieram na segunda, na quarta e na sexta, por número:

seg = {1, 2, 3}
qua = {2, 3, 4}
sex = {3, 4, 5}
print(seg & qua & sex)
print(seg | qua | sex)
print(seg.union(qua, sex))
$ python3 o_tres.py
{3}
{1, 2, 3, 4, 5}
{1, 2, 3, 4, 5}

Só o cliente 3 veio nos três dias. E os métodos aceitam vários conjuntos de uma vez, separados por vírgula: seg.union(qua, sex).


3. Um conjunto dentro do outro

Além de calcular, dá para perguntar sobre dois conjuntos. "Todos desta dupla fazem violão?" é perguntar se a dupla é um subconjunto da turma: se cabe inteira dentro dela.

violao = {"Ana", "Bia", "Caio", "Davi"}
dupla = {"Bia", "Davi"}
print(dupla <= violao)
print(dupla.issubset(violao))
print(violao >= dupla)
print(violao.issuperset(dupla))
print(dupla < violao)
print(violao < violao)
print(violao <= violao)
print(violao.isdisjoint({"Eva", "Fábio"}))
print(violao.isdisjoint({"Ana"}))
$ python3 o_sub.py
True
True
True
True
True
False
True
True
False
EscritaPerguntaAqui
dupla <= violao ou dupla.issubset(violao)Todos de dupla estão em violao? (subconjunto)True
violao >= dupla ou violao.issuperset(dupla)A mesma pergunta, vista do outro lado: violao contém dupla? (superconjunto)True
dupla < violaoSubconjunto e menor (falta alguém)? (subconjunto próprio)True
violao < violaoUm conjunto não é "menor" que ele mesmoFalse
violao <= violaoMas cabe dentro dele mesmoTrue
violao.isdisjoint(outro)Os dois não têm ninguém em comum? ("disjuntos")True com Eva e Fábio; False com Ana
< não é "tem menos itens"

Com conjuntos, < e <= não comparam tamanhos, comparam conteúdo. {1, 2} < {5, 6, 7} é False, mesmo o primeiro sendo menor, porque o 1 e o 2 não estão no outro. Para comparar tamanhos, compare os len.


4. Atualizando o conjunto no lugar

As contas acima criam um conjunto novo. Quando você quer mudar o próprio conjunto, use a versão com =, como o += dos números (aula Operadores de Atribuição), ou o método update:

estoque = {"arroz", "feijão"}
estoque |= {"café", "arroz"}
print(sorted(estoque))
estoque -= {"feijão"}
print(sorted(estoque))
estoque.update(["leite", "pão"])
print(sorted(estoque))
$ python3 o_update.py
['arroz', 'café', 'feijão']
['arroz', 'café']
['arroz', 'café', 'leite', 'pão']
estoque |= {"café", "arroz"}
O mesmo que estoque = estoque | {"café", "arroz"}: junta os itens novos. O arroz já estava e não se repete.
estoque -= {"feijão"}
Tira do estoque tudo o que está no outro conjunto.
estoque.update(["leite", "pão"])
"Atualizar": a versão método do |=. Como todo método, aceita uma lista.

Existem também &= (fica só com o que está nos dois) e ^=, e os métodos intersection_update, difference_update e symmetric_difference_update, com nomes compridos mas a mesma ideia.


Formas diferentes de fazer a mesma conta

ContaSímboloMétodoAtualizando no lugar
Uniãoa | ba.union(b)a |= b / a.update(b)
Interseçãoa & ba.intersection(b)a &= b / a.intersection_update(b)
Diferençaa - ba.difference(b)a -= b / a.difference_update(b)
Diferença simétricaa ^ ba.symmetric_difference(b)a ^= b / a.symmetric_difference_update(b)

Qual usar? Os símbolos são curtos e se leem como uma conta; use quando os dois lados já são conjuntos. Os métodos são mais fáceis de entender para quem não decorou os símbolos, e aceitam listas e tuplas sem precisar converter: use quando o outro lado não é um conjunto.


Quando dá errado

Símbolo entre conjunto e lista
violao = {"Ana", "Bia"}
novos = ["Caio", "Davi"]
todos = violao | novos
$ python3 o_lista_erro.py
Traceback (most recent call last):
  File "/home/ana/o_lista_erro.py", line 3, in <module>
    todos = violao | novos
            ~~~~~~~^~~~~~~
TypeError: unsupported operand type(s) for |: 'set' and 'list'
line 3
O erro está na linha 3.
~~~~~~~^~~~~~~
O ^ aponta para o operador |, e os tils marcam os dois lados da conta.
TypeError: unsupported operand type(s) for |: 'set' and 'list'
"Tipos de operando não suportados para |: set e list". O | não sabe juntar um conjunto com uma lista.

Como consertar: converta a lista (violao | set(novos)) ou use o método, que aceita listas: violao.union(novos).

Somar conjuntos com +
a = {1, 2}
b = {3}
print(a + b)
$ python3 o_mais_erro.py
Traceback (most recent call last):
  File "/home/ana/o_mais_erro.py", line 3, in <module>
    print(a + b)
          ~~^~~
TypeError: unsupported operand type(s) for +: 'set' and 'set'

Listas e tuplas se juntam com +, mas conjuntos não: "tipos não suportados para +: set e set". Como consertar: a "soma" de conjuntos é a união: a | b.

and no lugar de &: sem erro, resultado errado
violao = {1, 2, 3}
ingles = {3, 4}
print(violao and ingles)
print(violao & ingles)
$ python3 o_and.py
{3, 4}
{3}

A palavra and ("e") parece a interseção, mas não é. Ela é o "e" lógico da aula and, or e not: como violao não está vazio, violao and ingles devolve simplesmente o segundo conjunto, {3, 4}, sem calcular nada. Nenhum erro aparece, e o resultado está errado. O mesmo vale para or no lugar de |. Como consertar: para contas de conjunto, use os símbolos & e | (ou os métodos).

Ninguém em comum não é erro
violao = {"Ana", "Bia"}
xadrez = {"Caio"}
comum = violao & xadrez
print(comum)
print(len(comum))
if not comum:
    print("Ninguém faz as duas.")
$ python3 o_vazio.py
set()
0
Ninguém faz as duas.

Quando a conta não sobra ninguém, o resultado é o conjunto vazio, set(). Ele conta como "falso" num if, então if not comum: é o jeito de perguntar "ficou vazio?".


Referência rápida

EscritaResultado
a | bUnião: o que está em a ou em b
a & bInterseção: o que está nos dois
a - bDiferença: o que está em a e não em b
a ^ bDiferença simétrica: o que está em um só
a <= b / a.issubset(b)True se tudo de a está em b
a >= b / a.issuperset(b)True se a contém tudo de b
a.isdisjoint(b)True se não há nada em comum
a |= b, a &= b, a -= b, a ^= bFazem a conta e guardam o resultado em a
a.update(lista)Acrescenta os itens de qualquer sequência
SintomaCausa provável
TypeError: unsupported operand type(s) for |: 'set' and 'list'Símbolo entre conjunto e lista: use o método ou set()
TypeError: unsupported operand type(s) for +: 'set' and 'set'Conjunto não soma: use |
Resultado estranho, sem erroand/or no lugar de &/|, ou a ordem trocada na diferença
set()A conta não sobrou ninguém (não é erro)

Exercícios

1. A interseção

Quanto vale {1, 2, 3} & {2, 3, 4}? Escreva como o Python mostra.

2. A diferença

Quanto vale {1, 2, 3} - {2, 3, 4}?

3. O símbolo da união

Complete o símbolo que junta as duas turmas:

todos = violao ingles

4. Com violao = {"Ana", "Bia"} e ingles = {"Bia", "Eva"}, qual conta dá {'Eva'}?

5. {1, 2} | [3, 4] dá erro. Qual destas linhas funciona?

6. O que {"Bia"} <= {"Ana", "Bia"} responde?

Missão final: a lista de presença da festa

Missão: a Ana tem o conjunto de quem confirmou presença na festa e o de quem apareceu. Troque cada "?" por uma conta de conjuntos: quem confirmou e faltou (em ordem alfabética), quem veio sem confirmar (em ordem alfabética) e quantas pessoas diferentes estão nas duas listas juntas.

confirmados = {"Ana", "Bia", "Caio", "Davi"}
presentes = {"Bia", "Davi", "Eva"}
print("Faltaram:", "?")
print("Sem confirmar:", "?")
print("Total de pessoas:", "?")
confirmados = {"Ana", "Bia", "Caio", "Davi"}
presentes = {"Bia", "Davi", "Eva"}
print("Faltaram:", sorted(confirmados - presentes))
print("Sem confirmar:", sorted(presentes - confirmados))
print("Total de pessoas:", len(confirmados | presentes))

Resumo

  • | é a união (um ou outro), & a interseção (os dois), - a diferença (este e não aquele) e ^ a diferença simétrica (só um dos dois).
  • Toda conta devolve um conjunto novo; os originais não mudam. Na diferença, a ordem importa.
  • Os métodos union, intersection, difference e symmetric_difference fazem o mesmo e aceitam listas; os símbolos exigem dois conjuntos.
  • <=/issubset, >=/issuperset e isdisjoint respondem perguntas com True ou False.
  • |=, -=, &=, ^= e update mudam o próprio conjunto.
  • Conjunto não soma com +, e and/or não são &/|. Para mostrar um conjunto de textos sempre igual, use sorted().