Sets: conjuntos e operações matemáticas

1. Introdução aos Sets em Python

Em Python, um set (conjunto) é uma coleção não ordenada, mutável e que não permite elementos duplicados. Diferentemente de listas e tuplas, os sets não mantêm uma ordem específica e não suportam indexação. Eles são ideais para operações matemáticas de conjuntos e testes de pertinência eficientes.

Para criar um set, utilizamos a função set() ou chaves {} com elementos separados por vírgula:

# Criando sets
frutas = {'maçã', 'banana', 'laranja', 'maçã'}  # A duplicata será removida
numeros = set([1, 2, 3, 4, 5])
vazio = set()  # {} cria um dicionário vazio, não um set!

print(frutas)  # {'banana', 'maçã', 'laranja'} - ordem pode variar
print(numeros)  # {1, 2, 3, 4, 5}

Os elementos de um set devem ser imutáveis (números, strings, tuplas). Listas, dicionários e outros sets não podem ser elementos:

# Elementos permitidos
set_valido = {1, 'Python', (1, 2, 3), 3.14}

# Elementos não permitidos (geram TypeError)
# set_invalido = {[1, 2], {'chave': 'valor'}}  # Descomente para ver o erro

2. Características Fundamentais dos Sets

Impossibilidade de duplicatas: Sets eliminam automaticamente elementos repetidos, tornando-os perfeitos para garantir unicidade:

repetidos = {1, 2, 2, 3, 3, 3, 4}
print(repetidos)  # {1, 2, 3, 4}

Não ordenação: A ordem dos elementos em um set não é garantida e pode variar entre execuções. Isso significa que não podemos acessar elementos por índice:

meu_set = {'a', 'b', 'c'}
# print(meu_set[0])  # TypeError: 'set' object is not subscriptable

Mutabilidade: Diferente de frozenset, os sets podem ser modificados após a criação, permitindo adição e remoção de elementos.

3. Métodos Essenciais para Manipulação

Adição de elementos

conjunto = {1, 2, 3}

# add() - adiciona um único elemento
conjunto.add(4)
print(conjunto)  # {1, 2, 3, 4}

# update() - adiciona múltiplos elementos de um iterável
conjunto.update([5, 6, 7])
print(conjunto)  # {1, 2, 3, 4, 5, 6, 7}

# Tentativa de adicionar elemento existente é ignorada
conjunto.add(1)
print(conjunto)  # {1, 2, 3, 4, 5, 6, 7} - sem alteração

Remoção de elementos

conjunto = {1, 2, 3, 4, 5}

# remove() - lança KeyError se o elemento não existir
conjunto.remove(3)
print(conjunto)  # {1, 2, 4, 5}
# conjunto.remove(10)  # KeyError: 10

# discard() - seguro, não lança erro se o elemento não existir
conjunto.discard(10)  # Silenciosamente ignorado
conjunto.discard(1)
print(conjunto)  # {2, 4, 5}

# pop() - remove e retorna um elemento aleatório
elemento = conjunto.pop()
print(f"Elemento removido: {elemento}")

# clear() - remove todos os elementos
conjunto.clear()
print(conjunto)  # set()

Verificação de pertinência

numeros = {1, 2, 3, 4, 5}
print(3 in numeros)    # True
print(10 in numeros)   # False
print(10 not in numeros)  # True

4. Operações de Conjuntos: União e Interseção

União (union() e operador |)

A união combina todos os elementos únicos de dois ou mais conjuntos:

tags_python = {'python', 'programação', 'backend', 'dados'}
tags_web = {'javascript', 'frontend', 'backend', 'css'}

# Usando método
todas_tags = tags_python.union(tags_web)
print(todas_tags)
# {'python', 'programação', 'backend', 'dados', 'javascript', 'frontend', 'css'}

# Usando operador
todas_tags_op = tags_python | tags_web
print(todas_tags_op)  # Mesmo resultado

Interseção (intersection() e operador &)

A interseção retorna apenas os elementos comuns entre os conjuntos:

preferencias_a = {'filme', 'música', 'livro', 'esporte'}
preferencias_b = {'música', 'teatro', 'esporte', 'dança'}

comuns = preferencias_a.intersection(preferencias_b)
print(comuns)  # {'música', 'esporte'}

# Usando operador
comuns_op = preferencias_a & preferencias_b
print(comuns_op)  # {'música', 'esporte'}

5. Operações de Conjuntos: Diferença e Diferença Simétrica

Diferença (difference() e operador -)

A diferença retorna elementos presentes no primeiro conjunto mas não no segundo:

usuarios_ativos = {'ana', 'joão', 'maria', 'pedro'}
usuarios_premium = {'joão', 'maria'}

usuarios_comuns = usuarios_ativos.difference(usuarios_premium)
print(usuarios_comuns)  # {'ana', 'pedro'}

# Usando operador
usuarios_comuns_op = usuarios_ativos - usuarios_premium
print(usuarios_comuns_op)  # {'ana', 'pedro'}

Diferença Simétrica (symmetric_difference() e operador ^)

A diferença simétrica retorna elementos que estão em um ou outro conjunto, mas não em ambos:

log_ontem = {'erro_1', 'erro_2', 'aviso_1', 'info_1'}
log_hoje = {'erro_2', 'erro_3', 'aviso_1', 'info_2'}

mudancas = log_ontem.symmetric_difference(log_hoje)
print(mudancas)  # {'erro_1', 'erro_3', 'info_1', 'info_2'}

# Usando operador
mudancas_op = log_ontem ^ log_hoje
print(mudancas_op)  # Mesmo resultado

6. Relações entre Conjuntos: Subconjuntos e Disjunção

Verificação de subconjunto e superconjunto

numeros = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
pares = {2, 4, 6, 8, 10}
primos = {2, 3, 5, 7}

print(pares.issubset(numeros))     # True
print(numeros.issuperset(pares))   # True

# Usando operadores
print(pares <= numeros)   # True (subconjunto)
print(numeros >= pares)   # True (superconjunto)
print(pares < numeros)    # True (subconjunto próprio)
print(primos < pares)     # False

Disjunção (isdisjoint())

Dois conjuntos são disjuntos se não possuem elementos em comum:

categoria_a = {'eletrônicos', 'informática'}
categoria_b = {'moda', 'beleza'}

print(categoria_a.isdisjoint(categoria_b))  # True

categoria_c = {'eletrônicos', 'games'}
print(categoria_a.isdisjoint(categoria_c))  # False (eletrônicos está em ambos)

7. Sets vs. Outras Estruturas de Dados

Sets vs. Listas

A principal diferença está no desempenho para testes de pertinência:

import time

# Lista grande
lista = list(range(1000000))
set_grande = set(range(1000000))

# Teste de pertinência
inicio = time.time()
print(999999 in lista)  # O(n) - leva tempo proporcional ao tamanho
print(f"Lista: {time.time() - inicio:.6f} segundos")

inicio = time.time()
print(999999 in set_grande)  # O(1) - extremamente rápido
print(f"Set: {time.time() - inicio:.6f} segundos")

Sets vs. Tuplas

Tuplas são imutáveis e ordenadas, sets são mutáveis e não ordenados. Sets garantem unicidade, tuplas permitem duplicatas.

Sets vs. Dicionários

Sets podem ser usados como chaves de dicionários indiretamente através de frozenset:

# Usando frozenset como chave de dicionário
dicionario = {
    frozenset({'a', 'b'}): 'valor_1',
    frozenset({'c', 'd'}): 'valor_2'
}
print(dicionario[frozenset({'a', 'b'})])  # 'valor_1'

8. Boas Práticas e Casos de Uso Comuns

Eliminação de duplicatas em listas

lista_com_duplicatas = [1, 2, 2, 3, 3, 3, 4, 5, 5]
lista_sem_duplicatas = list(set(lista_com_duplicatas))
print(lista_sem_duplicatas)  # [1, 2, 3, 4, 5] - ordem pode variar

Testes de pertinência eficientes

# Em vez de usar lista para verificar milhões de elementos
ids_bloqueados = set(range(1000000))

def verificar_acesso(user_id):
    return user_id not in ids_bloqueados  # O(1) - extremamente rápido

Análise de dados com conjuntos

# IDs de usuários que realizaram ações específicas
usuarios_compra = {101, 102, 103, 104, 105}
usuarios_visita = {102, 104, 106, 107, 108}
usuarios_cadastro = {101, 103, 105, 109}

# Usuários que compraram mas não visitaram
apenas_compra = usuarios_compra - usuarios_visita
print(f"Compraram sem visitar: {apenas_compra}")  # {101, 103, 105}

# Usuários que fizeram todas as ações
completos = usuarios_compra & usuarios_visita & usuarios_cadastro
print(f"Completaram tudo: {completos}")  # set() - vazio neste caso

Cuidados importantes

  • Elementos mutáveis não são permitidos: Use tuplas em vez de listas se precisar armazenar sequências
  • Perda de ordem original: Ao converter lista para set, a ordem original é perdida. Se a ordem for importante, use dict.fromkeys() para Python 3.7+ ou OrderedDict
# Mantendo ordem ao remover duplicatas (Python 3.7+)
lista = [3, 1, 2, 1, 3, 2]
lista_ordenada_sem_dup = list(dict.fromkeys(lista))
print(lista_ordenada_sem_dup)  # [3, 1, 2] - ordem preservada

Referências