Conjuntos (set) são uma estrutura de dados nativa do Python que representam coleções não ordenadas de elementos únicos. Eles são extremamente úteis quando você precisa eliminar duplicatas, realizar operações matemáticas de conjuntos (união, interseção, diferença) ou testar pertinência de forma eficiente. Nesta aula, vamos explorar como criar, manipular e utilizar conjuntos, incluindo a variante imutável frozenset.

Criação

Você pode criar um conjunto de duas formas principais: usando chaves {} com elementos separados por vírgula ou usando a função construtora set(). É importante notar que chaves vazias {} criam um dicionário, não um conjunto. Para criar um conjunto vazio, use set().

Exemplos de criação:

# Conjunto com elementos
frutas = {"maçã", "banana", "laranja"}
print(frutas)  # Saída: {'banana', 'maçã', 'laranja'} (ordem pode variar)

# Conjunto a partir de uma lista (remove duplicatas)
numeros = set([1, 2, 2, 3, 4])
print(numeros)  # Saída: {1, 2, 3, 4}

# Conjunto vazio
vazio = set()
print(vazio)  # Saída: set()

# Conjunto com tipos mistos (desde que imutáveis)
conjunto_misto = {1, "Python", (2, 3)}
print(conjunto_misto)  # Saída: {1, 'Python', (2, 3)}

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

Operações (união, interseção)

Os conjuntos em Python suportam as operações matemáticas clássicas. As principais são: união (| ou union()), interseção (& ou intersection()), diferença (- ou difference()) e diferença simétrica (^ ou symmetric_difference()).

Exemplo com dois conjuntos:

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# União: elementos que estão em a OU em b
print(a | b)   # {1, 2, 3, 4, 5, 6}
print(a.union(b))

# Interseção: elementos que estão em a E em b
print(a & b)   # {3, 4}
print(a.intersection(b))

# Diferença: elementos que estão em a mas não em b
print(a - b)   # {1, 2}
print(a.difference(b))

# Diferença simétrica: elementos que estão em a ou b, mas não em ambos
print(a ^ b)   # {1, 2, 5, 6}
print(a.symmetric_difference(b))

Essas operações retornam novos conjuntos, sem modificar os originais. Existem também métodos que modificam o conjunto in-place, como update(), intersection_update(), etc.

Unicidade

A principal característica de um conjunto é que ele não permite elementos duplicados. Se você tentar adicionar um elemento já existente, o conjunto permanece inalterado. Isso torna os conjuntos ideais para eliminar duplicatas de sequências e para testes de pertinência rápidos (complexidade média O(1)).

Exemplo de unicidade:

# Removendo duplicatas de uma lista
lista_com_duplicatas = [1, 2, 2, 3, 3, 3, 4]
unicos = set(lista_com_duplicatas)
print(unicos)  # {1, 2, 3, 4}

# Tentando adicionar elemento repetido
conjunto = {1, 2, 3}
conjunto.add(2)
print(conjunto)  # {1, 2, 3} (inalterado)

# Teste de pertinência
print(2 in conjunto)  # True
print(5 in conjunto)  # False

A unicidade é garantida internamente por uma tabela hash, o que também torna os conjuntos não ordenados. Portanto, não é possível acessar elementos por índice.

frozenset

O frozenset é uma versão imutável do set. Uma vez criado, não é possível adicionar, remover ou modificar seus elementos. Por ser imutável, um frozenset pode ser usado como chave de dicionário ou elemento de outro conjunto. A criação é feita com a função frozenset().

Exemplo de frozenset:

# Criando um frozenset
fs = frozenset([1, 2, 3, 4])
print(fs)  # frozenset({1, 2, 3, 4})

# Tentativa de modificar gera erro
# fs.add(5)  # AttributeError: 'frozenset' object has no attribute 'add'

# frozenset como chave de dicionário
dicionario = {frozenset({1, 2}): "valor"}
print(dicionario)  # {frozenset({1, 2}): 'valor'}

# frozenset dentro de um set
conjunto_de_frozensets = {frozenset({1}), frozenset({2})}
print(conjunto_de_frozensets)  # {frozenset({2}), frozenset({1})}

As operações de união, interseção, etc. também funcionam com frozenset, retornando um frozenset como resultado.

Boas práticas e observações finais

Use conjuntos sempre que precisar garantir unicidade ou realizar operações de conjunto de forma eficiente. Prefira set para coleções mutáveis e frozenset quando precisar de imutabilidade (por exemplo, para usar como chave). Lembre-se de que conjuntos não preservam ordem; se a ordem for importante, use list ou tuple após processar o conjunto.

Referências

Exercícios

  1. Crie um conjunto com os números de 1 a 10. Em seguida, adicione o número 5 novamente e verifique se o conjunto permanece o mesmo. Imprima o conjunto.

    ✓ Resposta:
    conjunto = set(range(1, 11))
    print("Antes:", conjunto)  # {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    conjunto.add(5)
    print("Depois:", conjunto)  # {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} (inalterado)
    # O conjunto permanece o mesmo, pois 5 já existe.
  2. Dadas duas listas: lista1 = [1, 2, 3, 4, 5] e lista2 = [4, 5, 6, 7, 8], encontre a união e a interseção dos elementos únicos.

    ✓ Resposta:
    lista1 = [1, 2, 3, 4, 5]
    lista2 = [4, 5, 6, 7, 8]
    conj1 = set(lista1)
    conj2 = set(lista2)
    uniao = conj1 | conj2
    intersecao = conj1 & conj2
    print("União:", uniao)        # {1, 2, 3, 4, 5, 6, 7, 8}
    print("Interseção:", intersecao)  # {4, 5}
  3. Remova todas as palavras duplicadas da frase: "Python é legal e Python é poderoso e eficiente". Imprima as palavras únicas em ordem alfabética.

    ✓ Resposta:
    frase = "Python é legal e Python é poderoso e eficiente"
    palavras = frase.split()
    unicas = set(palavras)
    ordenadas = sorted(unicas)
    print(ordenadas)  # ['Python', 'eficiente', 'e', 'legal', 'poderoso', 'é']
  4. Crie um frozenset com os números 10, 20 e 30. Tente adicionar o número 40 a esse frozenset e explique o que acontece.

    ✓ Resposta:
    fs = frozenset([10, 20, 30])
    # fs.add(40)  # Isso levanta um AttributeError, pois frozenset não tem método add.
    # O frozenset é imutável, portanto não é possível modificá-lo.
    print(fs)  # frozenset({10, 20, 30})
  5. Escreva uma função que receba uma lista de números e retorne uma lista com os números que aparecem mais de uma vez (ou seja, os duplicados). Use conjuntos para resolver.

    ✓ Resposta:
    def encontra_duplicados(lista):
        vistos = set()
        duplicados = set()
        for num in lista:
            if num in vistos:
                duplicados.add(num)
            else:
                vistos.add(num)
        return list(duplicados)
    
    # Exemplo:
    lista = [1, 2, 3, 2, 4, 5, 3, 6]
    print(encontra_duplicados(lista))  # [2, 3] (ordem pode variar)