Python & AI Tutorials Logo
Programação Python

17. Sets: Trabalhando com Dados Únicos e Não Ordenados

Nos capítulos anteriores, trabalhamos com listas (coleções ordenadas e mutáveis) e dicionários (mapeamentos chave-valor). Agora vamos explorar sets, o tipo de coleção do Python projetado especificamente para armazenar itens únicos e realizar operações matemáticas de conjuntos de forma eficiente.

Sets são especialmente poderosos quando você precisa eliminar duplicatas, testar pertencimento rapidamente ou realizar operações como encontrar elementos em comum entre coleções. Diferente de listas, sets não são ordenados e não podem conter valores duplicados — tentar adicionar o mesmo item duas vezes não tem efeito.

17.1) Criando Sets e Operações Básicas

17.1.1) Criando Sets com Chaves

A forma mais comum de criar um set é usando chaves {} com valores separados por vírgula:

python
# Criando um set de linguagens de programação
languages = {"Python", "JavaScript", "Java", "C++"}
print(languages)  # Output: {'Python', 'JavaScript', 'Java', 'C++'}
print(type(languages))  # Output: <class 'set'>

Importante: A ordem dos elementos quando você imprime um set pode ser diferente da ordem em que você os inseriu. Sets são coleções não ordenadas, o que significa que o Python não mantém nenhuma sequência específica:

python
numbers = {5, 2, 8, 1, 9}
print(numbers)  # Output might be: {1, 2, 5, 8, 9} or another order

A ordem de saída pode variar entre execuções e versões do Python. Nunca confie que sets manterão uma ordem específica — se a ordem importa, use uma lista em vez disso.

17.1.2) Sets Removem Duplicatas Automaticamente

Uma das propriedades mais úteis dos sets é que eles eliminam automaticamente valores duplicados. Se você tentar criar um set com itens duplicados, apenas uma cópia de cada valor único é mantida:

python
# Criando um set com valores duplicados
student_ids = {101, 102, 103, 102, 101, 104}
print(student_ids)  # Output: {101, 102, 103, 104}
 
# Esta propriedade torna sets perfeitos para remover duplicatas
grades = [85, 90, 85, 78, 90, 92, 78, 85]
unique_grades = set(grades)
print(unique_grades)  # Output: {78, 85, 90, 92}

Essa deduplicação automática acontece porque sets usam um modelo matemático de conjunto, em que cada elemento pode aparecer apenas uma vez. Quando você adiciona um valor que já existe, o set simplesmente ignora a duplicata.

17.1.3) Criando Sets com o Construtor set()

Você pode criar sets a partir de outros iteráveis usando o construtor set(). Isso é particularmente útil para converter listas, tuplas ou strings em sets:

python
# Criando um set a partir de uma lista
colors_list = ["red", "blue", "green", "red", "yellow"]
colors_set = set(colors_list)
print(colors_set)  # Output: {'red', 'blue', 'green', 'yellow'}
 
# Criando um set a partir de uma string (cada caractere vira um elemento)
letters = set("programming")
print(letters)  # Output: {'p', 'r', 'o', 'g', 'a', 'm', 'i', 'n'}
 
# Criando um set a partir de uma tupla
coordinates = set((10, 20, 30, 20, 10))
print(coordinates)  # Output: {10, 20, 30}

Quando você cria um set a partir de uma string, cada caractere único vira um elemento separado. Isso é útil para encontrar todos os caracteres distintos em um texto:

python
text = "Mississippi"
unique_chars = set(text.lower())
print(unique_chars)  # Output: {'m', 'i', 's', 'p'}
print(f"The word contains {len(unique_chars)} unique letters")
# Output: The word contains 4 unique letters

17.1.4) Criando um Set Vazio

Aqui vai uma pegadinha crítica: você não pode criar um set vazio usando {}, porque o Python interpreta isso como um dicionário vazio. Em vez disso, você deve usar set():

python
# ERRADO - Isso cria um dicionário vazio, não um set
empty_dict = {}
print(type(empty_dict))  # Output: <class 'dict'>
 
# CORRETO - Isso cria um set vazio
empty_set = set()
print(type(empty_set))  # Output: <class 'set'>
print(empty_set)  # Output: set()

Essa distinção existe porque dicionários foram adicionados ao Python antes de sets, então {} já tinha sido reservado para dicionários vazios. Quando você imprime um set vazio, o Python o exibe como set() para evitar confusão.

Confusão comum de iniciantes: Ao criar um set com um único elemento usando uma variável, o set contém o valor da variável, não o nome da variável:

python
# Entendendo a criação de set com variáveis
x = 5
my_set = {x}  # Cria {5}, não {'x'}
print(my_set)  # Output: {5}
 
# Se você quer um set contendo a string 'x':
my_set = {'x'}
print(my_set)  # Output: {'x'}
 
# Isso se aplica a qualquer expressão
result = 10 + 5
my_set = {result}  # Cria {15}
print(my_set)  # Output: {15}

17.1.5) Propriedades e Operações Básicas de Sets

Sets suportam várias operações fundamentais que os tornam úteis para processamento de dados:

python
# Verificando o número de elementos únicos
website_visitors = {"alice", "bob", "charlie", "alice", "david"}
print(f"Unique visitors: {len(website_visitors)}")
# Output: Unique visitors: 4
 
# Verificando pertencimento com 'in' (muito rápido em sets)
if "alice" in website_visitors:
    print("Alice visited the website")
# Output: Alice visited the website
 
# Verificando não pertencimento
if "eve" not in website_visitors:
    print("Eve has not visited yet")
# Output: Eve has not visited yet

Testar pertencimento com in é uma das principais vantagens de sets. Para coleções grandes, verificar se um item existe em um set é muito mais rápido do que verificar em uma lista. Vamos explorar por que isso importa na Seção 17.5.

17.2) Adicionando e Removendo Elementos de Sets

Diferente de tuplas (que são imutáveis), sets são mutáveis — você pode adicionar e remover elementos após a criação. No entanto, os próprios elementos devem ser tipos imutáveis (vamos explorar essa restrição na Seção 17.7).

17.2.1) Adicionando Elementos Únicos com add()

Adicionar elementos individuais a um set é direto com o método add(). Se o elemento já existir, o set permanece inalterado — nenhum erro é gerado e nenhuma duplicata é criada:

python
# Montando um set de tarefas concluídas
completed_tasks = {"task1", "task2"}
print(completed_tasks)  # Output: {'task1', 'task2'}
 
# Adicionando uma nova tarefa
completed_tasks.add("task3")
print(completed_tasks)  # Output: {'task1', 'task2', 'task3'}
 
# Adicionar uma duplicata não tem efeito
completed_tasks.add("task1")
print(completed_tasks)  # Output: {'task1', 'task2', 'task3'}

Esse comportamento torna sets ideais para rastrear ocorrências únicas. Você pode chamar add() com segurança sem verificar se o elemento já existe — o set lida com duplicatas automaticamente.

17.2.2) Adicionando Vários Elementos com update()

Para adicionar vários elementos de uma vez, use update(), que aceita qualquer iterável (lista, tupla, outro set etc.) e adiciona todos os seus elementos ao set:

python
# Começando com um set pequeno de habilidades
skills = {"Python", "SQL"}
print(skills)  # Output: {'Python', 'SQL'}
 
# Adicionando várias habilidades a partir de uma lista
new_skills = ["JavaScript", "Docker", "Python"]
skills.update(new_skills)
print(skills)  # Output: {'Python', 'SQL', 'JavaScript', 'Docker'}

Repare que "Python" apareceu tanto no set original quanto na lista que está sendo adicionada, mas o set ainda contém apenas uma cópia. O método update() pode aceitar múltiplos iteráveis como argumentos:

python
# Combinando habilidades de várias fontes
current_skills = {"Python"}
course_skills = ["JavaScript", "HTML"]
job_requirements = {"SQL", "Python", "Docker"}
 
current_skills.update(course_skills, job_requirements)
print(current_skills)
# Output: {'Python', 'JavaScript', 'HTML', 'SQL', 'Docker'}

17.2.3) Removendo Elementos com remove()

Remover elementos exige cuidado. O método remove() apaga um elemento de um set, mas levanta um KeyError se o elemento não existir:

python
# Gerenciando usuários ativos
active_users = {"alice", "bob", "charlie", "david"}
 
# Removendo um usuário que fez logout
active_users.remove("bob")
print(active_users)  # Output: {'alice', 'charlie', 'david'}
 
# Tentar remover um elemento inexistente causa um erro
# active_users.remove("eve")  # Raises: KeyError: 'eve'

Como remove() gera erro para elementos ausentes, é melhor usá-lo quando você tem certeza de que o elemento existe, ou quando você quer capturar o erro caso não exista:

python
# Remoção segura com tratamento de erro (vamos aprender mais sobre try/except no Capítulo 28)
users = {"alice", "bob", "charlie"}
user_to_remove = "david"
 
if user_to_remove in users:
    users.remove(user_to_remove)
    print(f"Removed {user_to_remove}")
else:
    print(f"{user_to_remove} was not in the set")
# Output: david was not in the set

17.2.4) Removendo Elementos com Segurança com discard()

Para uma remoção mais segura que não gera erros, discard() fornece uma alternativa mais tolerante. Ele remove o elemento se estiver presente, mas não faz nada se o elemento não existir:

python
# Gerenciando um carrinho de compras
cart_items = {"apple", "banana", "orange"}
 
# Removendo itens com segurança (sem erro se o item não existir)
cart_items.discard("banana")
print(cart_items)  # Output: {'apple', 'orange'}
 
cart_items.discard("grape")  # No error, even though grape isn't in the set
print(cart_items)  # Output: {'apple', 'orange'}

Use discard() quando você quer garantir que um elemento não esteja no set, independentemente de ele estar lá inicialmente. Use remove() quando a ausência do elemento indica uma condição de erro que você quer capturar.

17.2.5) Removendo e Retornando um Elemento Arbitrário com pop()

O método pop() remove e retorna um elemento arbitrário do set. Como sets não são ordenados, você não consegue prever qual elemento será removido:

python
# Processando uma fila de tarefas pendentes (ordem não importa)
pending_tasks = {"email", "report", "meeting", "review"}
 
# Processa uma tarefa (não nos importamos qual)
task = pending_tasks.pop()
print(f"Processing: {task}")  # Output: Processing: email (or another task)
print(f"Remaining: {pending_tasks}")
# Output: Remaining: {'report', 'meeting', 'review'} (without the popped task)

Se você chamar pop() em um set vazio, ele levanta um KeyError:

python
empty_set = set()
# empty_set.pop()  # Raises: KeyError: 'pop from an empty set'

O método pop() é útil quando você precisa processar todos os elementos de um set, mas não se importa com a ordem:

python
# Processando todos os itens em um set
items_to_process = {"item1", "item2", "item3"}
 
while items_to_process:
    item = items_to_process.pop()
    print(f"Processing {item}")
    # Process the item...
 
print("All items processed")
# Output:
# Processing item1
# Processing item2
# Processing item3
# All items processed

17.2.6) Removendo Todos os Elementos com clear()

O método clear() remove todos os elementos de um set, deixando-o vazio:

python
# Resetando os dados de uma sessão
session_data = {"user_id", "timestamp", "ip_address"}
print(session_data)  # Output: {'user_id', 'timestamp', 'ip_address'}
 
session_data.clear()
print(session_data)  # Output: set()
print(len(session_data))  # Output: 0

Isso é mais eficiente do que criar um novo set vazio se você quiser reutilizar o mesmo objeto set.

Métodos de Modificação de Set

Adicionando Elementos

Removendo Elementos

add element: Item único

update iterable: Vários itens

remove element: Erro se faltar

discard element: Sem erro se faltar

pop: Remove um elemento arbitrário

clear: Remove todos os elementos

Use quando o elemento deve existir

Use quando não tem certeza se existe

Use quando a ordem não importa

17.3) Operações com Sets: União, Interseção, Diferença e Diferença Simétrica

Sets suportam operações matemáticas de conjuntos que permitem combinar, comparar e analisar coleções de forma eficiente. Essas operações são fundamentais na teoria dos conjuntos e têm muitas aplicações práticas no processamento de dados.

17.3.1) União: Combinando Sets

Vamos começar com um cenário prático para entender por que a união importa. Imagine que você está gerenciando matrículas de alunos em diferentes cursos:

python
# Alunos matriculados em diferentes cursos
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
 
# Encontrando todos os alunos fazendo qualquer um dos cursos (ou ambos)
all_students = python_students | javascript_students
print(all_students)
# Output: {'alice', 'bob', 'charlie', 'david', 'eve'}

A união de dois sets contém todos os elementos que aparecem em qualquer um dos sets (ou em ambos). O Python fornece duas formas de calcular uniões: o operador | (mostrado acima) e o método union():

python
# Mesmo resultado usando o método union()
all_students = python_students.union(javascript_students)
print(all_students)
# Output: {'alice', 'bob', 'charlie', 'david', 'eve'}

O método union() pode aceitar múltiplos sets como argumentos, o que o torna conveniente para combinar dados de muitas fontes:

python
# Alunos em três cursos diferentes
python_students = {"alice", "bob"}
javascript_students = {"bob", "charlie"}
sql_students = {"charlie", "david"}
 
# Todos os alunos em todos os cursos
all_students = python_students.union(javascript_students, sql_students)
print(all_students)
# Output: {'alice', 'bob', 'charlie', 'david'}

Outro exemplo de união é combinar listas de e-mail de diferentes departamentos:

python
# Combinando listas de e-mail de diferentes departamentos
marketing_contacts = {"alice@company.com", "bob@company.com"}
sales_contacts = {"bob@company.com", "charlie@company.com"}
support_contacts = {"david@company.com", "alice@company.com"}
 
# Todos os contatos únicos entre departamentos
all_contacts = marketing_contacts | sales_contacts | support_contacts
print(f"Total unique contacts: {len(all_contacts)}")
# Output: Total unique contacts: 4

17.3.2) Interseção: Encontrando Elementos em Comum

Entender quais elementos aparecem em múltiplos sets é crucial para muitas tarefas de análise de dados. A operação de interseção responde à pergunta: "O que esses sets têm em comum?"

python
# Encontrando clientes que compraram ambos os produtos
customers_product_a = {101, 102, 103, 104, 105}
customers_product_b = {103, 104, 105, 106, 107}
 
# Clientes que compraram ambos os produtos
both_products = customers_product_a & customers_product_b
print(f"Bought both: {both_products}")
# Output: Bought both: {103, 104, 105}

A interseção contém apenas elementos que aparecem nos dois sets. Você também pode usar o método intersection(), que aceita múltiplos sets:

python
# Encontrando alunos matriculados nos três cursos
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "charlie", "david"}
sql_students = {"charlie", "eve", "bob"}
 
# Alunos fazendo os três cursos
all_three = python_students.intersection(javascript_students, sql_students)
print(all_three)  # Output: {'bob', 'charlie'}

Aqui vai um caso de uso prático para encontrar produtos disponíveis em múltiplos depósitos:

python
# Encontrando produtos disponíveis em múltiplos depósitos
warehouse_a = {"laptop", "mouse", "keyboard", "monitor"}
warehouse_b = {"mouse", "keyboard", "printer", "scanner"}
warehouse_c = {"keyboard", "monitor", "mouse", "desk"}
 
# Produtos disponíveis em todos os depósitos
available_everywhere = warehouse_a & warehouse_b & warehouse_c
print(f"Available in all locations: {available_everywhere}")
# Output: Available in all locations: {'mouse', 'keyboard'}

17.3.3) Diferença: Encontrando Elementos em um Set mas Não em Outro

Às vezes você precisa identificar o que é exclusivo de uma coleção. A operação de diferença encontra elementos que estão no primeiro set, mas não no segundo:

python
# Gerenciamento de inventário: encontrando discrepâncias
expected_items = {"item001", "item002", "item003", "item004"}
actual_items = {"item001", "item003", "item005"}
 
# Itens faltando no inventário
missing = expected_items - actual_items
print(f"Missing items: {missing}")
# Output: Missing items: {'item002', 'item004'}
 
# Itens inesperados no inventário
unexpected = actual_items - expected_items
print(f"Unexpected items: {unexpected}")
# Output: Unexpected items: {'item005'}

Você também pode usar o método difference():

python
# Alunos apenas no curso de Python (não no de JavaScript)
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
 
python_only = python_students.difference(javascript_students)
print(python_only)  # Output: {'alice', 'charlie'}

Importante: A operação de diferença não é comutativa — a ordem importa:

python
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
 
# Alunos em Python mas não em JavaScript
python_only = python_students - javascript_students
print(f"Python only: {python_only}")
# Output: Python only: {'alice', 'charlie'}
 
# Alunos em JavaScript mas não em Python
javascript_only = javascript_students - python_students
print(f"JavaScript only: {javascript_only}")
# Output: JavaScript only: {'david', 'eve'}

17.3.4) Diferença Simétrica: Elementos em Qualquer Set, mas Não em Ambos

A diferença simétrica encontra elementos que estão em qualquer um dos sets, mas não em ambos. Essa operação é particularmente útil para identificar mudanças entre duas versões:

python
# Comparando duas versões de uma configuração
old_settings = {"debug", "logging", "cache", "compression"}
new_settings = {"logging", "cache", "monitoring", "security"}
 
# Configurações que mudaram (adicionadas ou removidas)
changes = old_settings ^ new_settings
print(f"Changed settings: {changes}")
# Output: Changed settings: {'debug', 'compression', 'monitoring', 'security'}
 
# Para ver especificamente o que foi adicionado vs removido:
removed = old_settings - new_settings
added = new_settings - old_settings
print(f"Removed: {removed}")  # Output: Removed: {'debug', 'compression'}
print(f"Added: {added}")  # Output: Added: {'monitoring', 'security'}

Você também pode usar o método symmetric_difference():

python
# Alunos em exatamente um curso (não em ambos)
python_students = {"alice", "bob", "charlie"}
javascript_students = {"bob", "david", "eve"}
 
one_course_only = python_students.symmetric_difference(javascript_students)
print(one_course_only)
# Output: {'alice', 'charlie', 'david', 'eve'}

Diferente da diferença, a diferença simétrica é comutativa — a ordem não importa:

python
result1 = python_students ^ javascript_students
result2 = javascript_students ^ python_students
print(result1 == result2)  # Output: True

Operações com Sets

União: A | B

Interseção: A & B

Diferença: A - B

Diferença Simétrica: A ^ B

Todos os elementos em qualquer set

Apenas elementos em ambos os sets

Elementos em A, mas não em B

Elementos em qualquer um, mas não em ambos

17.4) Relações de Subconjunto e Superconjunto (issubset, issuperset, isdisjoint)

Além de combinar sets, muitas vezes precisamos entender as relações entre eles. O Python fornece métodos para testar se um set está contido em outro, se contém outro, ou se não compartilha elementos com outro.

17.4.1) Testando Subconjuntos com issubset() e <=

Um set A é um subconjunto do set B se todo elemento de A também estiver em B. Em outras palavras, B contém todos os elementos de A (e possivelmente mais).

python
# Pré-requisitos de curso
basic_skills = {"reading", "writing"}
intermediate_skills = {"reading", "writing", "analysis"}
 
# Verifica se habilidades básicas são subconjunto das habilidades intermediárias
print(basic_skills.issubset(intermediate_skills))  # Output: True
print(basic_skills <= intermediate_skills)  # Output: True (same result)

Um set é sempre um subconjunto de si mesmo:

python
skills = {"Python", "SQL", "JavaScript"}
print(skills.issubset(skills))  # Output: True
print(skills <= skills)  # Output: True

Se você quiser testar um subconjunto próprio (A é subconjunto de B, mas não é igual a B), use o operador <:

python
basic_skills = {"reading", "writing"}
intermediate_skills = {"reading", "writing", "analysis"}
 
# Subconjunto próprio: basic é subconjunto de intermediate E eles não são iguais
print(basic_skills < intermediate_skills)  # Output: True
 
# Não é subconjunto próprio de si mesmo (eles são iguais)
print(basic_skills < basic_skills)  # Output: False

Um exemplo prático de teste de subconjunto é verificar permissões ou requisitos:

python
# Sistema de permissões de usuário
required_permissions = {"read", "write"}
user_permissions = {"read", "write", "delete", "admin"}
 
# Verifica se o usuário tem todas as permissões necessárias
if required_permissions.issubset(user_permissions):
    print("Access granted")
else:
    print("Access denied - missing permissions")
# Output: Access granted
 
# Outro usuário com permissões insuficientes
limited_user = {"read"}
if required_permissions.issubset(limited_user):
    print("Access granted")
else:
    missing = required_permissions - limited_user
    print(f"Access denied - missing: {missing}")
# Output: Access denied - missing: {'write'}

17.4.2) Testando Superconjuntos com issuperset() e >=

Um set A é um superconjunto do set B se A contiver todos os elementos de B. Essa é a relação inversa de subconjunto — se A é um subconjunto de B, então B é um superconjunto de A.

python
# Níveis de habilidades
basic_skills = {"reading", "writing"}
advanced_skills = {"reading", "writing", "analysis", "research"}
 
# Verifica se advanced_skills é um superconjunto de basic_skills
print(advanced_skills.issuperset(basic_skills))  # Output: True
print(advanced_skills >= basic_skills)  # Output: True (same result)

Assim como subconjuntos, um set é sempre um superconjunto de si mesmo:

python
skills = {"Python", "SQL"}
print(skills.issuperset(skills))  # Output: True

Para um superconjunto próprio (A é superconjunto de B, mas não é igual a B), use o operador >:

python
basic_skills = {"reading", "writing"}
advanced_skills = {"reading", "writing", "analysis"}
 
# Superconjunto próprio: advanced contém tudo de basic E tem mais
print(advanced_skills > basic_skills)  # Output: True
 
# Não é superconjunto próprio de si mesmo
print(advanced_skills > advanced_skills)  # Output: False

17.4.3) Testando Sets Disjuntos com isdisjoint()

Dois sets são disjuntos se não tiverem elementos em comum — a interseção deles é vazia. O método isdisjoint() retorna True se os sets não compartilharem elementos:

python
# Verificando conflitos em agendamento
morning_classes = {"math", "english", "history"}
afternoon_classes = {"science", "art", "music"}
 
# Verifica se há conflitos (mesma aula em ambos os períodos)
if morning_classes.isdisjoint(afternoon_classes):
    print("No scheduling conflicts")
else:
    conflicts = morning_classes & afternoon_classes
    print(f"Conflicts: {conflicts}")
# Output: No scheduling conflicts

Quando sets não são disjuntos:

python
morning_classes = {"math", "english", "history"}
afternoon_classes = {"science", "math", "music"}
 
if morning_classes.isdisjoint(afternoon_classes):
    print("No scheduling conflicts")
else:
    conflicts = morning_classes & afternoon_classes
    print(f"Conflicts: {conflicts}")
# Output: Conflicts: {'math'}

Sets vazios são disjuntos com todos os sets (inclusive outros sets vazios):

python
empty = set()
numbers = {1, 2, 3}
 
print(empty.isdisjoint(numbers))  # Output: True
print(empty.isdisjoint(empty))  # Output: True

17.5) Quando Usar Sets em vez de Listas

Entender quando usar sets versus listas é crucial para escrever código Python eficiente. Embora ambos armazenem coleções de itens, eles têm características diferentes que tornam cada um adequado para tarefas diferentes.

17.5.1) Use Sets para Teste Rápido de Pertencimento

Uma das vantagens mais significativas de sets é a velocidade no teste de pertencimento. Verificar se um item existe em um set é muito mais rápido do que verificar em uma lista, especialmente para coleções grandes:

python
# Verificando se um usuário está em uma coleção grande
active_users_list = []
for i in range(10000):
    active_users_list.append("user" + str(i))
 
# Com uma lista (lento para coleções grandes)
print("user5000" in active_users_list)  # Checks each element until found
 
active_users_set = set()
for i in range(10000):
    active_users_set.add("user" + str(i))
 
# Com um set (rápido independente do tamanho)
print("user5000" in active_users_set)  # Direct lookup

Embora ambos produzam o mesmo resultado, a versão com set é dramaticamente mais rápida para coleções grandes. Isso acontece porque sets usam uma tabela hash internamente, permitindo buscas quase instantâneas independentemente do tamanho, enquanto listas precisam verificar cada elemento sequencialmente.

17.5.2) Use Sets para Eliminar Duplicatas

Quando você precisa remover duplicatas de uma coleção, converter para um set é a abordagem mais simples:

python
# Removendo entradas duplicadas de input do usuário
survey_responses = [
    "yes", "no", "yes", "maybe", "yes", "no", "maybe", "yes"
]
 
# Obter respostas únicas
unique_responses = set(survey_responses)
print(unique_responses)  # Output: {'yes', 'no', 'maybe'}
 
# Se você precisa de volta uma lista (com duplicatas removidas)
unique_list = list(unique_responses)
print(unique_list)  # Output: ['yes', 'no', 'maybe'] (order may vary)

17.5.3) Use Sets para Operações Matemáticas de Conjuntos

Quando você precisa encontrar elementos em comum, diferenças ou uniões entre coleções, sets fornecem operações claras e eficientes:

python
# Analisando padrões de compra de clientes
customers_product_a = {101, 102, 103, 104, 105}
customers_product_b = {103, 104, 105, 106, 107}
 
# Clientes que compraram ambos os produtos
both_products = customers_product_a & customers_product_b
print(f"Bought both: {both_products}")
# Output: Bought both: {103, 104, 105}
 
# Clientes que compraram apenas o produto A
only_a = customers_product_a - customers_product_b
print(f"Only product A: {only_a}")
# Output: Only product A: {101, 102}
 
# Todos os clientes que compraram pelo menos um produto
all_customers = customers_product_a | customers_product_b
print(f"Total customers: {len(all_customers)}")
# Output: Total customers: 7

17.5.4) Use Listas Quando a Ordem Importa

Sets são não ordenados, então se a sequência dos elementos é importante, você deve usar uma lista:

python
# ERRADO - A ordem não é preservada com sets
task_order = {"wake up", "breakfast", "work", "lunch", "work", "dinner"}
print(task_order)  # Order is unpredictable and "work" appears only once
 
# CORRETO - Use uma lista quando a ordem importa
task_order = ["wake up", "breakfast", "work", "lunch", "work", "dinner"]
print(task_order)
# Output: ['wake up', 'breakfast', 'work', 'lunch', 'work', 'dinner']

17.5.5) Use Listas Quando Duplicatas São Significativas

Se valores duplicados carregam informação (como frequência ou múltiplas ocorrências), use uma lista:

python
# Registrando notas de um quiz (duplicatas mostram quantos alunos tiraram cada nota)
quiz_scores = [85, 90, 85, 78, 90, 92, 85, 88]
 
# Com uma lista, dá para contar ocorrências
score_85_count = quiz_scores.count(85)
print(f"Students who scored 85: {score_85_count}")
# Output: Students who scored 85: 3
 
# Com um set, perderíamos essa informação
unique_scores = set(quiz_scores)
print(unique_scores)  # Output: {78, 85, 88, 90, 92}
# We can't tell how many students got each score

17.5.6) Use Listas Quando Você Precisa de Indexação

Sets não suportam indexação porque não são ordenados. Se você precisa acessar elementos por posição, use uma lista:

python
# ERRADO - Sets não suportam indexação
colors = {"red", "blue", "green"}
# first_color = colors[0]  # Raises: TypeError: 'set' object is not subscriptable
 
# CORRETO - Use uma lista para acesso por índice
colors = ["red", "blue", "green"]
first_color = colors[0]
print(first_color)  # Output: red

Vantagens de Sets

Teste rápido de pertencimento

Deduplicação automática

Operações de conjunto

Vantagens de Listas

Preserva ordem

Permite duplicatas

Suporta indexação

17.6) Frozensets e Sets Imutáveis

Até aqui, trabalhamos com sets comuns, que são mutáveis — você pode adicionar e remover elementos após a criação. O Python também fornece frozensets, que são versões imutáveis de sets. Depois de criado, um frozenset não pode ser modificado.

17.6.1) Criando Frozensets

Você cria um frozenset usando o construtor frozenset(), de forma semelhante a como cria um set comum com set():

python
# Criando um frozenset a partir de uma lista
colors = frozenset(["red", "blue", "green"])
print(colors)  # Output: frozenset({'red', 'blue', 'green'})
print(type(colors))  # Output: <class 'frozenset'>
 
# Criando um frozenset a partir de uma tupla
numbers = frozenset((1, 2, 3, 4, 5))
print(numbers)  # Output: frozenset({1, 2, 3, 4, 5})
 
# Criando um frozenset vazio
empty = frozenset()
print(empty)  # Output: frozenset()

Assim como sets comuns, frozensets eliminam duplicatas automaticamente:

python
# Duplicatas são removidas
values = frozenset([1, 2, 2, 3, 3, 3, 4])
print(values)  # Output: frozenset({1, 2, 3, 4})

17.6.2) Frozensets São Imutáveis

Depois de criado, você não pode modificar um frozenset. Métodos como add(), remove(), discard(), pop() e clear() não existem para frozensets:

python
# Criando um frozenset
languages = frozenset(["Python", "JavaScript", "Java"])
 
# Tentar modificar gera um erro
# languages.add("C++")  # AttributeError: 'frozenset' object has no attribute 'add'
# languages.remove("Java")  # AttributeError: 'frozenset' object has no attribute 'remove'

Essa imutabilidade é a característica definidora de frozensets. Se você precisa "modificar" um frozenset, você deve criar um novo:

python
# Frozenset original
original = frozenset([1, 2, 3])
 
# Criando um novo frozenset com um elemento adicional
modified = frozenset(list(original) + [4])
print(original)  # Output: frozenset({1, 2, 3})
print(modified)  # Output: frozenset({1, 2, 3, 4})

17.6.3) Operações de Set Funcionam com Frozensets

Frozensets suportam as mesmas operações de conjunto que sets comuns (união, interseção, diferença etc.):

python
# Operações de conjunto com frozensets
set_a = frozenset([1, 2, 3, 4])
set_b = frozenset([3, 4, 5, 6])
 
# União
print(set_a | set_b)  # Output: frozenset({1, 2, 3, 4, 5, 6})
 
# Interseção
print(set_a & set_b)  # Output: frozenset({3, 4})
 
# Diferença
print(set_a - set_b)  # Output: frozenset({1, 2})
 
# Diferença simétrica
print(set_a ^ set_b)  # Output: frozenset({1, 2, 5, 6})

Você também pode misturar sets comuns e frozensets em operações:

python
regular_set = {1, 2, 3}
frozen_set = frozenset([3, 4, 5])
 
# Operações entre set comum e frozenset
result = regular_set | frozen_set
print(result)  # Output: {1, 2, 3, 4, 5}
print(type(result))  # Output: <class 'set'> (result is a regular set)

17.6.4) Por Que Usar Frozensets?

O principal motivo para usar frozensets é que eles podem ser usados como chaves de dicionário ou como elementos em outros sets, o que sets comuns não podem:

python
# ERRADO - Sets comuns não podem ser chaves de dicionário
# regular_set = {1, 2, 3}
# my_dict = {regular_set: "value"}  # TypeError: unhashable type: 'set'
 
# CORRETO - Frozensets podem ser chaves de dicionário
frozen_set = frozenset([1, 2, 3])
my_dict = {frozen_set: "value"}
print(my_dict)  # Output: {frozenset({1, 2, 3}): 'value'}
print(my_dict[frozen_set])  # Output: value

Um exemplo prático usando frozensets como chaves de dicionário:

python
# Armazenando informações sobre pares de coordenadas
# Cada coordenada é um frozenset de valores (x, y)
location_data = {
    frozenset([0, 0]): "origin",
    frozenset([1, 0]): "east",
    frozenset([1, 1]): "northeast"
}
 
# Consultando uma localização
point = frozenset([1, 0])
print(location_data[point])  # Output: east

Frozensets também podem ser elementos em outros sets:

python
# ERRADO - Sets comuns não podem ser elementos de sets
# set_of_sets = {{1, 2}, {3, 4}}  # TypeError: unhashable type: 'set'
 
# CORRETO - Frozensets podem ser elementos de sets
set_of_frozensets = {
    frozenset([1, 2]),
    frozenset([3, 4]),
    frozenset([5, 6])
}
print(set_of_frozensets)
# Output: {frozenset({1, 2}), frozenset({3, 4}), frozenset({5, 6})}

Um exemplo prático representando grupos:

python
# Representando times em que cada time é um frozenset de IDs de jogadores
tournament_teams = {
    frozenset([101, 102, 103]),  # Team A
    frozenset([201, 202, 203]),  # Team B
    frozenset([301, 302, 303])   # Team C
}
 
# Verifica se um time específico está registrado
team_to_check = frozenset([101, 102, 103])
if team_to_check in tournament_teams:
    print("Team is registered")
else:
    print("Team not found")
# Output: Team is registered

17.6.5) Convertendo Entre Sets e Frozensets

Você pode converter facilmente entre sets comuns e frozensets:

python
# Convertendo um set comum em um frozenset
regular = {1, 2, 3, 4}
frozen = frozenset(regular)
print(frozen)  # Output: frozenset({1, 2, 3, 4})
 
# Convertendo um frozenset em um set comum
frozen = frozenset([5, 6, 7, 8])
regular = set(frozen)
print(regular)  # Output: {5, 6, 7, 8}
 
# Agora podemos modificar o set comum
regular.add(9)
print(regular)  # Output: {5, 6, 7, 8, 9}

Tipos de Set

Set comum: Mutável

Frozenset: Imutável

Pode adicionar/remover elementos

Não pode ser chave de dict

Não pode ser elemento de set

Não pode modificar após criar

Pode ser chave de dict

Pode ser elemento de set

17.7) Tipos Hashable e Unhashable: O Que Pode Ser Chave de Dicionário ou Elemento de Set (e uma Breve Nota sobre Hashing)

Ao longo deste capítulo, vimos que sets podem conter alguns tipos de objetos, mas não outros. Por exemplo, você pode criar um set de inteiros ou strings, mas não um set de listas. Essa restrição existe porque elementos de set (e chaves de dicionário, como aprendemos no Capítulo 16) precisam ser hashable.

17.7.1) O Que Significa "Hashable"?

Um objeto hashable é aquele que tem um valor de hash que nunca muda durante sua vida útil. O Python calcula esse valor de hash usando uma função embutida chamada hash():

python
# Tipos hashable têm um valor de hash
print(hash(42))  # Output: 42
print(hash("Python"))  # Output: (some large integer)
print(hash((1, 2, 3)))  # Output: (some large integer)

O valor de hash é um inteiro que o Python usa internamente para localizar objetos rapidamente em sets e dicionários. Pense nisso como um endereço ou índice que ajuda o Python a encontrar coisas de forma eficiente.

Propriedade-chave: Para um objeto ser hashable, seu valor de hash deve permanecer constante durante toda sua vida útil. Isso significa que o próprio objeto precisa ser imutável — se o objeto pudesse mudar, seu valor de hash também precisaria mudar, o que quebraria sets e dicionários.

17.7.2) Tipos Imutáveis São Hashable

Todos os tipos embutidos imutáveis do Python são hashable e podem ser usados como elementos de set ou chaves de dicionário:

python
# Inteiros são hashable
numbers = {1, 2, 3, 4, 5}
print(numbers)  # Output: {1, 2, 3, 4, 5}
 
# Strings são hashable
words = {"apple", "banana", "cherry"}
print(words)  # Output: {'apple', 'banana', 'cherry'}
 
# Tuplas são hashable (se contiverem apenas elementos hashable)
coordinates = {(0, 0), (1, 1), (2, 2)}
print(coordinates)  # Output: {(0, 0), (1, 1), (2, 2)}
 
# Frozensets são hashable
frozen_sets = {frozenset([1, 2]), frozenset([3, 4])}
print(frozen_sets)  # Output: {frozenset({1, 2}), frozenset({3, 4})}
 
# Booleanos e None são hashable
mixed = {True, False, None, 42, "text"}
print(mixed)  # Output: {False, True, None, 42, 'text'}

17.7.3) Tipos Mutáveis Não São Hashable

Tipos mutáveis como listas, sets comuns e dicionários não são hashable porque seu conteúdo pode mudar:

python
# Listas NÃO são hashable
# my_set = {[1, 2, 3]}  # TypeError: unhashable type: 'list'
 
# Sets comuns NÃO são hashable
# set_of_sets = {{1, 2}, {3, 4}}  # TypeError: unhashable type: 'set'
 
# Dicionários NÃO são hashable
# my_set = {{"key": "value"}}  # TypeError: unhashable type: 'dict'

Por que a mutabilidade importa? Considere o que aconteceria se pudéssemos adicionar uma lista a um set:

python
# Cenário hipotético (isso não funciona de verdade)
# my_list = [1, 2, 3]
# my_set = {my_list}  # Imagine que isso funcionasse
# 
# # O Python calcula o hash com base em [1, 2, 3]
# # Agora nós modificamos a lista:
# my_list.append(4)  # Agora é [1, 2, 3, 4]
# 
# # O valor de hash estaria errado! O set seria corrompido.

É por isso que o Python impede objetos mutáveis de estarem em sets ou de serem usados como chaves de dicionário — isso quebraria a estrutura de dados interna.

Confusão comum de iniciantes: Mesmo que sets em si sejam mutáveis (você pode adicionar e remover elementos), os elementos precisam ser imutáveis. Iniciantes às vezes tentam modificar objetos depois de adicioná-los a sets, sem perceber essa distinção conceitual:

python
# Confusão comum: set é mutável, mas os elementos precisam ser imutáveis
# Set é mutável - você pode alterar seu conteúdo
fruits = {'apple', 'banana'}
fruits.add('orange')     # ✓ Works
fruits.remove('apple')   # ✓ Works
 
# Mas os elementos precisam ser imutáveis - eles não podem ser alterados
my_list = [1, 2, 3]
# my_set = {my_list}  # ✗ TypeError: unhashable type: 'list'
# Por quê? Se você pudesse modificar my_list depois de adicioná-la, a estrutura interna
# do set seria corrompida.
 
# Isso funciona porque tuplas são imutáveis
my_tuple = (1, 2, 3)
my_set = {my_tuple}  # ✓ Works - tuples can't be modified

17.7.4) O Caso Especial das Tuplas

Tuplas são hashable apenas se todos os seus elementos forem hashable. Uma tupla contendo objetos mutáveis não é hashable:

python
# Tupla com apenas elementos imutáveis - hashable
good_tuple = (1, 2, "three")
my_set = {good_tuple} # Works: good_tuple is hashable
print(my_set)  # Output: {(1, 2, 'three')}
 
# Tupla contendo uma lista - NÃO é hashable
bad_tuple = (1, 2, [3, 4])
# my_set = {bad_tuple}  # TypeError: unhashable type: 'list'

Isso faz sentido: mesmo que a tupla em si seja imutável (você não pode mudar quais objetos ela contém), se um desses objetos for mutável, o "valor" geral da tupla pode mudar:

python
# Demonstrando por que tuplas com elementos mutáveis não podem ser hasheadas
inner_list = [1, 2]
my_tuple = (inner_list, 3)
 
# A estrutura da tupla é fixa, mas a lista dentro pode mudar
inner_list.append(3)  # Now inner_list is [1, 2, 3]
# A tupla agora "contém" dados diferentes, mas é o mesmo objeto tupla

17.7.5) Testando Hashability

Você pode testar se um objeto é hashable tentando calcular seu hash:

python
# Testando hashability
def is_hashable(obj):
    """Check if an object is hashable."""
    try:
        hash(obj)
        return True
    except TypeError:
        return False
 
# Testando vários tipos
print(is_hashable(42))  # Output: True
print(is_hashable("text"))  # Output: True
print(is_hashable((1, 2, 3)))  # Output: True
print(is_hashable([1, 2, 3]))  # Output: False
print(is_hashable({1, 2, 3}))  # Output: False
print(is_hashable({"key": "value"}))  # Output: False

17.7.6) Resumo de Tipos Hashable

Hashable (podem ser elementos de set ou chaves de dict):

  • Inteiros: 42
  • Floats: 3.14
  • Strings: "text"
  • Tuplas (se todos os elementos forem hashable): (1, 2, "three")
  • Frozensets: frozenset([1, 2, 3])
  • Booleanos: True, False
  • None: None

Não hashable (não podem ser elementos de set ou chaves de dict):

  • Listas: [1, 2, 3]
  • Sets comuns: {1, 2, 3}
  • Dicionários: {"key": "value"}
  • Tuplas contendo elementos não hashable: (1, [2, 3])

Entender hashability ajuda você a escolher as estruturas de dados certas e evitar erros comuns ao trabalhar com sets e dicionários. O princípio-chave é simples: se um objeto pode mudar, ele não pode ser hasheado; se ele não pode ser hasheado, ele não pode estar em um set nem ser usado como chave de dicionário.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai