Pular para o conteúdo

Fundamentos

Conjuntos (set) em Python: sem repetição e operações de conjunto

Aprenda conjuntos em Python: criar com set, tirar repetidos, add e discard, união, interseção, diferença, a ordem dos itens e usos na ESP32.

Por Prof. Rodrigo Faustino · Atualizado em · 12 min de leitura

Um conjunto, em Python set, é uma coleção de valores sem repetição e sem posição. Ele guarda cada valor uma só vez, responde rapidamente à pergunta "este valor está aqui?" e sabe fazer contas de conjunto: juntar dois, achar o que tem nos dois, achar o que tem em um e não no outro. Ele se escreve entre chaves, como em {1, 2, 3}, ou com a função set(...).

É a estrutura certa quando a pergunta é sobre existência e não sobre quantidade: quais valores diferentes apareceram, quais alunos estão nas duas turmas, quais sensores ainda não responderam. Para contar ocorrências, o dicionário do artigo anterior é a ferramenta, e para guardar valores em sequência, a lista. Aqui veremos como criar e alterar conjuntos, as quatro operações básicas, como tirar repetidos de uma lista, o aviso sobre a ordem dos itens, os erros mais comuns e dois usos na ESP32.

Criar um conjunto

No alto, a lista 3, 1, 2, 3, 1, com os dois últimos valores marcados como repetidos, passa por set de lista e vira o conjunto 1, 2, 3, com cada valor uma única vez. Embaixo, três quadros: sem repetição, porque add de 3 de novo não muda nada; sem posição, porque s de 0 dá TypeError e a ordem não é garantida; e teste rápido, porque 3 in s dá True. Uma faixa diz para usar conjunto quando importa se o valor existe.
O set guarda cada valor uma vez, sem posição, e responde rápido se um valor está nele.
a = {3, 1, 2, 3, 1}
print(a, len(a), type(a))
vazio = set()
print(vazio, len(vazio), type({}))
print(set([1, 2, 2, 3, 3, 3]))
print(set("banana"))
print({"abc"}, set("abc"))
print(set(range(5)))
print(set((1, 2, 2)))
print(2 in a, 9 in a, 9 not in a)
print({(1, 2), (1, 2), (2, 1)})
print("fim")

A saída é {1, 2, 3} 3 <class 'set'> e, na linha seguinte, set() 0 <class 'dict'>. Depois vêm {1, 2, 3}, {'a', 'n', 'b'}, {'abc'} {'a', 'c', 'b'}, {0, 1, 2, 3, 4}, {2, 1}, True False True, {(1, 2), (2, 1)} e fim.

O primeiro conjunto foi escrito com cinco valores e ficou com três: os repetidos foram descartados sem aviso. Esse é o comportamento central do tipo. A set(lista) e a set(texto) fazem a mesma coisa a partir de outras sequências, e a set("banana") mostra as três letras diferentes, a, n e b.

Três detalhes merecem atenção:

  • {} não é um conjunto vazio. É um dicionário vazio, e a segunda linha do exemplo prova com type({}). O conjunto vazio se escreve set().
  • {"abc"} e set("abc") são coisas diferentes. As chaves com um texto guardam o texto inteiro como um único valor, e set("abc") separa as letras.
  • Os valores precisam ser imutáveis. Números, textos e tuplas entram. Listas e dicionários não, porque podem mudar, como o dicionário já mostrou para as chaves. Veja o erro na seção própria.

Os dois pontos finais do exemplo confirmam isso. A tupla (1, 2) entrou duas vezes e ficou uma só, e (2, 1) é outra tupla, porque a ordem dentro dela importa.

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

A ordem dos itens

Repare nas saídas do exemplo. O set((1, 2, 2)) foi impresso como {2, 1}, e não como {1, 2}. Como no dicionário, o conjunto não guarda a ordem em que os valores entraram. No MicroPython a ordem é a de uma tabela de espalhamento (hash), e o simulador a copia. No Python de computador, a ordem dos itens de texto de um conjunto pode até mudar de uma execução para outra. O que vale nos dois lugares é a regra: um conjunto não tem primeiro, nem último, nem posição.

Quando a ordem importa para mostrar o resultado, o sorted a pede. O sorted(conjunto) devolve uma lista ordenada, e é o que se usa nos exemplos adiante para que a saída seja previsível.

Alterar um conjunto

s = {1, 2}
s.add(3)
s.add(3)
print(s)
s.remove(2)
print(s)
s.discard(2)
s.discard(99)
print(s)
s.update([5, 6, 6])
print(s)
s.update({7}, [8])
print(s)
x = s.pop()
print(x in {1, 3, 5, 6, 7, 8}, len(s))
s.clear()
print(s, len(s))
print("fim")

A saída é {1, 2, 3}, {1, 3}, {1, 3}, {6, 1, 5, 3}, {6, 1, 7, 3, 8, 5}, True 5, set() 0 e fim.

O add acrescenta um valor, e acrescentar um que já existe não faz nada. O remove tira um valor, e dá erro se ele não estiver lá. O discard tira sem reclamar: tanto faz o 2, que já tinha saído, como o 99, que nunca esteve. A escolha entre os dois é a mesma do [] e do get no dicionário: use remove quando a ausência for um bug, e discard quando for normal.

O update junta vários valores de uma vez, vindos de uma lista, de outro conjunto ou de qualquer sequência, e aceita mais de um argumento. O pop tira um valor qualquer e o devolve. Não se sabe qual, e por isso o exemplo só confere que ele estava entre os valores esperados e que sobraram cinco. O clear esvazia.

Como as listas e os dicionários, o conjunto é mutável, e b = a só dá outro nome ao mesmo conjunto:

a = {1, 2}
b = a
b.add(3)
print(a, b, a is b)
c = a.copy()
c.add(4)
print(a, c)
print("fim")

A saída é {1, 2, 3} {1, 2, 3} True, {1, 2, 3} {4, 1, 2, 3} e fim. O copy() cria um conjunto independente.

As quatro operações

Quatro diagramas de dois círculos para os conjuntos a igual a 1, 2, 3, 4 e b igual a 3, 4, 5, 6. A união a barra b pinta os dois círculos e dá 1, 2, 3, 4, 5, 6. A interseção a e b pinta só o meio e dá 3, 4. A diferença a menos b pinta só o lado de a e dá 1, 2. A diferença simétrica a circunflexo b pinta os dois lados sem o meio e dá 1, 2, 5, 6.
As quatro operações de conjunto, com os métodos equivalentes embaixo de cada uma.

Os conjuntos têm as operações da matemática. Cada uma tem um operador e um método com o mesmo resultado:

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(b - a)
print(a ^ b)
print(a.union(b), a.intersection(b), a.difference(b), a.symmetric_difference(b))
print({1, 2} <= a, {1, 2} < a, a <= a, a < a)
print(a.issubset({1, 2, 3, 4, 5}), a.issuperset({1, 2}))
print(a.isdisjoint({7, 8}), a.isdisjoint(b))
print(a == {4, 3, 2, 1})
print(a, b)
print("fim")

A saída é {6, 1, 2, 3, 4, 5}, {4, 3}, {1, 2}, {5, 6} e {6, 1, 2, 5} nas cinco primeiras linhas, e a linha dos métodos repete os quatro resultados: {6, 1, 2, 3, 4, 5} {4, 3} {1, 2} {6, 1, 2, 5}. Depois vêm True True True False, True True, True False, True, {4, 1, 2, 3} {4, 5, 6, 3} e fim.

  • União, a | b: tudo que está em a, em b ou nos dois, sem repetir.
  • Interseção, a & b: só o que está nos dois.
  • Diferença, a - b: o que está em a e não está em b. Ela não é simétrica: a - b dá {1, 2} e b - a dá {5, 6}.
  • Diferença simétrica, a ^ b: o que está em um dos dois, mas não nos dois. É a união menos a interseção.

A linha dos testes mostra comparações. O <= pergunta se um conjunto está contido no outro, o < pergunta o mesmo e exige que o outro seja maior, e por isso a <= a dá True e a < a dá False. Os métodos issubset e issuperset dizem as mesmas coisas com palavras, e o isdisjoint pergunta se os dois conjuntos não têm nada em comum. A igualdade a == {4, 3, 2, 1} dá True, porque a ordem não conta.

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

Tirar repetidos de uma lista

O uso mais comum do conjunto é eliminar valores repetidos. Convém ver o que sai de cada forma:

leituras = [5, 3, 5, 2, 3, 9, 5]
print(len(leituras), len(set(leituras)))
print(sorted(set(leituras)))
unicos = []
for x in leituras:
    if x not in unicos:
        unicos.append(x)
print(unicos)
print(list(set(leituras)))
print(set("amor") == set("roma"))
print(sorted(set("banana")))
vogais = set("aeiou")
print(sorted(set("programacao") & vogais))
print(len(set("programacao") - vogais))
print({x % 3 for x in range(10)})
print({len(p) for p in ["a", "bb", "cc", "ddd"]})
print("fim")

A saída é 7 4, [2, 3, 5, 9], [5, 3, 2, 9], [9, 5, 2, 3], True, ['a', 'b', 'n'], ['a', 'o'], 5, {0, 1, 2}, {1, 2, 3} e fim.

A primeira linha compara os tamanhos: sete leituras, quatro valores diferentes. O len(set(lista)) é a forma mais curta de contar quantos valores distintos existem.

As três linhas seguintes mostram três maneiras de obter os valores sem repetição, e as ordens diferem. O sorted(set(...)) entrega os valores ordenados, [2, 3, 5, 9]. O laço com not in, do artigo sobre operações com listas, mantém a ordem da primeira aparição, [5, 3, 2, 9]. E o list(set(...)) entrega os valores na ordem interna do conjunto, que não é nem uma nem outra. A escolha depende do que se quer: se a ordem original importa, o laço, e se basta ordenar ou não importa, o conjunto.

As quatro últimas linhas do bloco mostram o que mais se faz com texto. A comparação set("amor") == set("roma") dá True, porque as duas palavras têm as mesmas letras, e é a base de uma verificação simples de anagrama, desde que as letras repetidas não precisem bater. O set("programacao") & vogais acha as vogais que aparecem na palavra, a e o, e o - conta quantas letras diferentes não são vogais, cinco. As duas últimas linhas são compreensões de conjunto, parentes das compreensões de lista e de dicionário: {x % 3 for x in range(10)} guarda os restos possíveis, e {len(p) for p in ...} guarda os tamanhos diferentes de palavra.

Comparar duas coleções

Quando há duas coleções e a pergunta é sobre o que elas têm em comum, o conjunto resolve em uma linha o que um laço duplo resolveria em cinco:

turma_a = {"Ana", "Bia", "Caio"}
turma_b = {"Bia", "Caio", "Duda", "Edu"}
print(sorted(turma_a & turma_b))
print(sorted(turma_a | turma_b))
print(sorted(turma_a - turma_b))
print(sorted(turma_a ^ turma_b))
print("Ana" in turma_a, "Duda" in turma_a)
for nome in sorted(turma_a | turma_b):
    print(nome)
print("fim")

A saída é ['Bia', 'Caio'], ['Ana', 'Bia', 'Caio', 'Duda', 'Edu'], ['Ana'], ['Ana', 'Duda', 'Edu'], True False, os cinco nomes um por linha e fim. Os alunos que estão nas duas turmas são a interseção, a lista geral de nomes sem repetir é a união, e quem está só na turma A é a diferença. Em todos os casos o sorted põe o resultado em ordem alfabética para que o programa sempre mostre a mesma coisa.

Na ESP32: o que falta e o que se repete

Dois usos de conjunto na ESP32. No primeiro, o conjunto dos sensores esperados dht, pir e hc menos o conjunto dos que responderam, dht e pir, dá o conjunto faltam, só com o hc. No segundo, os pinos usados 18, 19 e 21 com a interseção dos pinos novos 19 e 22 dão o conflito, só com o pino 19. Uma faixa diz que a subtração acha o que falta e a interseção acha o que se repete.
Subtrair conjuntos mostra o que falta, e a interseção mostra o que está repetido.

Dois problemas de projeto com a placa se resolvem bem com conjuntos. O primeiro é saber quem não respondeu: o programa tem uma lista de sensores esperados, e cada um que responde entra num conjunto. A diferença entre os dois mostra o que falta. O segundo é detectar um pino usado duas vezes, e a interseção entre os pinos já usados e os novos acha o conflito.

from machine import Pin

usados = {18, 19, 21}
novos = {19, 22}
conflito = usados & novos
print(conflito)
if conflito:
    print("pinos repetidos:", sorted(conflito))
livres = {18, 19, 21, 22, 23} - usados
print(sorted(livres))

ESPERADOS = {"dht", "pir", "hc"}
respondeu = {"dht", "pir"}
faltam = ESPERADOS - respondeu
print(faltam, len(faltam))
if faltam:
    print("sem resposta:", sorted(faltam))
for pino in sorted(usados):
    Pin(pino, Pin.OUT).off()
print("fim")

A saída é {19}, pinos repetidos: [19], [22, 23], {'hc'} 1, sem resposta: ['hc'] e fim. Um conjunto vazio vale como falso, então if conflito: e if faltam: só entram quando há algo a tratar, sem precisar de len. O sorted(usados) percorre os pinos em ordem para que o programa se comporte da mesma forma a cada execução. Os pinos 18, 19 e 21 são os mesmos dos LEDs do semáforo dos artigos anteriores, e o laço final apenas coloca todos em nível baixo. Os nomes dht, pir e hc são rótulos de exemplo, e não lidos de um sensor real, que o artigo sobre DHT22, HC-SR04 e PIR trata à parte.

Erros comuns

As mensagens abaixo são as do simulador, que imita o MicroPython. O tipo do erro costuma ser o mesmo no Python de computador, e o texto pode mudar.

s = {1, 2, 3}
print(s[0])
TypeError: 'set' object isn't subscriptable

O conjunto não tem posição, e portanto não aceita índice. Quem precisa de "o primeiro" quer uma lista, ou uma lista ordenada com sorted(s)[0].

s = {1, 2}
s.add([3, 4])
TypeError: unsupported type for __hash__: 'list'

Lista não entra em conjunto, porque pode mudar. A tupla (3, 4) entra. O Python de computador diz unhashable type: 'list'. A mesma mensagem aparece em {[1, 2], 3}.

s = {1, 2}
s.remove(9)
KeyError: 9

O remove exige que o valor exista. Com discard, o erro some.

s = {1, 2}
s.append(3)
AttributeError: 'set' object has no attribute 'append'

O método de acrescentar do conjunto se chama add. O append é da lista, e sort também não existe no conjunto, pelo mesmo motivo: sem posição não há o que ordenar no lugar.

print({1, 2} + {3})
TypeError: unsupported types for __add__: 'set', 'set'

Não existe + entre conjuntos. A junção é o |, ou o union.

print(set(5))
TypeError: 'int' object isn't iterable

O set(...) espera algo que se possa percorrer. Para um conjunto com o número 5, a escrita é {5}.

s = set()
s.pop()
KeyError: pop from an empty set

O pop precisa de pelo menos um valor. No Python de computador o texto do erro vem entre aspas.

a = {1, 2}
print(a - [1])
TypeError: unsupported types for __sub__: 'set', 'list'

Os operadores |, &, - e ^ pedem conjuntos dos dois lados. Para misturar com uma lista, converta antes: a - set([1]).

O que costuma aparecer em sala

Estes padrões se repetem em turmas de todos os níveis.

O mais frequente é o x = {} achando que criou um conjunto vazio. O programa roda, e só quebra no x.add(1), com um erro de dicionário, longe da causa. A frase para guardar é que o conjunto vazio se escreve set().

O segundo é esperar a ordem da lista depois de list(set(lista)). Na máquina do aluno, para números pequenos, a ordem parece conservada, e no simulador ou na placa não é. Quem precisa da ordem original usa o laço com not in, ou ordena com sorted.

O terceiro é o s[0], a mesma reação que o d[0] provoca nos dicionários: quem vem das listas pergunta por posição, e o conjunto não tem.

O quarto é set("abc") e {"abc"}. Em uma o texto vira três letras, e na outra fica um valor só, e o aluno jura que o conjunto "perdeu" as letras.

O quinto é o remove onde cabia um discard, que aparece quando o valor deixa de estar no conjunto por uma razão legítima, como um sensor que já tinha sido tirado.

O sexto é trocar a ordem da diferença. A conta a - b não é a b - a, e o resultado errado costuma passar despercebido porque os dois têm tamanhos parecidos.

O sétimo é usar conjunto quando a contagem importa. Ao tirar os repetidos, a informação de quantas vezes cada valor apareceu se perde, e para isso o dicionário do artigo anterior é a ferramenta.

Exercício: números repetidos e ausentes

Escreva um programa que pergunte quantos números serão lidos, leia cada um e mostre a lista, os valores sem repetição em ordem, os valores que aparecem mais de uma vez e os números de 1 a 5 que não foram digitados.

quantidade = int(input("quantos numeros: "))
numeros = []
for i in range(quantidade):
    numeros.append(int(input("numero: ")))
unicos = sorted(set(numeros))
repetidos = sorted({x for x in numeros if numeros.count(x) > 1})
faltam = sorted(set(range(1, 6)) - set(numeros))
print("numeros", numeros)
print("unicos", unicos)
print("repetidos", repetidos)
print("faltam de 1 a 5", faltam)

Com as entradas 6, 3, 1, 3, 5, 1 e 3, as quatro linhas finais são numeros [3, 1, 3, 5, 1, 3], unicos [1, 3, 5], repetidos [1, 3] e faltam de 1 a 5 [2, 4]. O numeros.count(x) pergunta quantas vezes o valor aparece, e a compreensão de conjunto guarda só os que aparecem mais de uma vez. A subtração set(range(1, 6)) - set(numeros) acha os ausentes. Para treinar, mostre também os valores que aparecem uma única vez e a quantidade de valores distintos. Depois, refaça a parte dos repetidos usando um dicionário de contagem, e compare os dois caminhos.

Perguntas frequentes

Quando uso conjunto, lista ou dicionário? Lista, quando a ordem e a posição importam, ou os repetidos têm significado. Dicionário, quando cada valor tem um nome, ou quando é preciso contar. Conjunto, quando só importa se o valor existe e as contas são de "tem nos dois", "só em um" e "falta".

Posso ordenar um conjunto? Não no lugar, porque ele não tem ordem. O sorted(s) devolve uma lista ordenada, e é o que se usa.

O conjunto aceita números e textos misturados? Aceita, mas ordenar o resultado com sorted daria erro de comparação entre int e str, como em qualquer lista.

Existe conjunto imutável? No Python de computador, existe o frozenset. No simulador, o nome frozenset não está definido, e dá NameError. Se você precisa de um conjunto que não muda na placa, o caminho é não o alterar depois de pronto.

Como junto vários conjuntos? Com | em sequência, a | b | c, ou com update num conjunto só, s.update(b, c).

Para continuar

Para rever as bases desta leitura, os artigos sobre dicionários, operações com listas e tuplas completam este. O desafio Busca em Vetor trabalha a pergunta "este valor existe?", que o in do conjunto responde.

O próximo artigo trata de strings a fundo: fatiamento e os métodos mais usados, como split, join, upper, replace e find. Alguns, como split e lower, já apareceram nos exemplos, e agora ganham o espaço que merecem.

Para praticar no PyPlaca

Trilhas

Lições

Fichas de componentes

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre