English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

visitante
1 / ?
voltar às lições

O Que Torna uma Lista Útil

Listas: Sua Primeira Estrutura de Dados

Uma lista (chamada de array em algumas linguagens) armazena itens em uma ordem específica. Você pode:

- Acessar qualquer item por sua posição: names[0] pega o primeiro item. O(1) — instantâneo.

- Adicionar itens ao final: names.append("Zoe"). O(1) — instantâneo.

- Percorrer cada item em ordem: for name in names. O(N) — visita cada item uma vez.

As listas preservam a ordem em que você coloca as coisas. O primeiro item permanece primeiro. O último permanece último.

# A list of pets
pets = ["cat", "dog", "cat", "fish", "dog"]

print(pets[0])   # "cat"  — O(1) instant
print(pets[3])   # "fish" — O(1) instant
print(len(pets)) # 5 — duplicates kept

Observe: "cat" aparece duas vezes. "dog" aparece duas vezes. Listas permitem duplicatas. Elas armazenam exatamente o que você dá a elas, na ordem em que você deu.

Mas as listas têm uma fraqueza. Veja o que acontece quando você pergunta: "fish" está nesta lista?

# Membership check — is "fish" in the list?
if "fish" in pets:    # scans ["cat", "dog", "cat", "fish"...] one by one
    print("found!")   # had to check 4 items before finding it

O computador verifica "cat" — não. "dog" — não. "cat" — não. "fish" — sim! Ele verificou 4 itens para encontrar a resposta. Com 1.000 itens, ele poderia verificar todos os 1.000. Essa verificação de pertencimento custa O(N).

Prever o Custo da Verificação

Você tem uma lista de 500 nomes de alunos em ordem aleatória.

Você quer verificar se "Zara" aparece na lista.

students = ["Alex", "Maria", ..., "Zara", ...]  # 500 names
if "Zara" in students:
    print("enrolled")
Qual é o melhor caso, pior caso & número médio de nomes que o computador verifica para encontrar "Zara"? Que classe de Big O descreve esta operação? Explique seu raciocínio.

Como Conjuntos Funcionam de Forma Diferente

Conjuntos: Construídos para Perguntas de Pertencimento

Um conjunto armazena itens únicos usando uma técnica chamada hash. Quando você adiciona um item, o computador calcula um hash (uma impressão digital numérica) que diz exatamente onde armazenar esse item.

Quando você verifica pertencimento, o computador calcula o mesmo hash & salta diretamente para o local certo. Sem verificação.

List vs Set: how data lives in memory — list scans, set jumps

# A set of pets
pets = {"cat", "dog", "cat", "fish", "dog"}

print(pets)      # {"cat", "dog", "fish"} — duplicates removed
print(len(pets)) # 3 — only unique items

Observe três diferenças em relação às listas:

1. Sem duplicatas. "cat" aparecia duas vezes na entrada, mas o conjunto mantém apenas uma cópia.

2. Nenhuma ordem garantida. O conjunto pode imprimir itens em qualquer arranjo.

3. Sem acesso por índice. Você não pode escrever pets[0] — conjuntos não têm posições.

A troca: você perde ordem & duplicatas. Você ganha O(1) para testar pertencimento — verificar se um item existe leva o mesmo tempo se o conjunto tem 5 itens ou 5 milhões.

# Membership check — is "fish" in the set?
if "fish" in pets:    # hash("fish") → jump to bucket → found
    print("found!")   # checked exactly 1 spot, not 4

Pertencimento em Conjunto vs Lista

Você tem 500 nomes de alunos armazenados em um conjunto em vez de uma lista.

students = {"Alex", "Maria", ..., "Zara", ...}  # 500 names
if "Zara" in students:
    print("enrolled")
Quantos itens o computador verifica para determinar se "Zara" existe em um conjunto de 500 nomes? Que classe de Big O descreve isso? Por que difere da versão da lista?

Comparação Lado a Lado

Folha de Dicas de Operações

Operation costs: list vs set — membership, add, index, dedup

Use uma lista quando você precisa de:

- Itens em uma ordem específica (uma playlist, uma receita, uma fila)

- Acesso por posição (items[3])

- Duplicatas preservadas ("cat" aparece 3 vezes = 3 gatos)

Use um conjunto quando você precisa de:

- Verificações de pertencimento rápidas ("Já vi isso antes?")

- Remoção automática de duplicatas

- Sem preocupação com ordem

Use ambos quando você precisa de ordem E consulta rápida:

seen = set()     # O(1) membership checks
result = []      # preserves insertion order
for item in data:
    if item not in seen:  # O(1) check
        seen.add(item)    # O(1) add
        result.append(item)  # O(1) append
# Total: O(N) — one pass, each step O(1)

Este padrão "conjunto + lista" oferece o melhor dos dois: resultados ordenados com detecção rápida de duplicatas.

Escolha a Estrutura Certa

Três problemas. Para cada um, decida: lista, conjunto ou ambos?

1. Você precisa armazenar uma playlist de músicas na ordem em que o usuário adicionou.

2. Você precisa verificar rapidamente se um nome de usuário já foi utilizado.

3. Você precisa remover emails duplicados de uma lista de distribuição, mas mantê-los na ordem em que se inscreveram.

Para cada um dos três problemas, qual estrutura de dados você deve usar (lista, conjunto ou ambos)? Explique por quê para cada.

O Problema da Deduplicate

Problema: Remover Duplicatas, Manter Ordem

Você recebe uma lista de inscrições de email. Algumas pessoas se inscreveram duas vezes. Você precisa enviar um email por pessoa, na ordem em que se inscreveram primeiro.

Tentativa 1: apenas lista (lenta)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
unique = []
for email in signups:
    if email not in unique:  # O(N) scan of unique list
        unique.append(email)
# Works! But: O(N) check × N items = O(N²)

Com 100 inscrições, isso faz ~5.000 verificações. Com 10.000 inscrições: ~50.000.000 verificações.

Tentativa 2: conjunto + lista (rápido)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
seen = set()
unique = []
for email in signups:
    if email not in seen:   # O(1) hash lookup
        seen.add(email)     # O(1) add to set
        unique.append(email) # O(1) append to list
# O(1) check × N items = O(N)

Mesmo resultado. Mesma ordem. Mas: 10.000 inscrições agora requerem ~10.000 verificações em vez de ~50.000.000.

Calcular a Aceleração

A versão apenas lista é executada em O(N²). A versão conjunto+lista é executada em O(N).

Você tem 10.000 inscrições de email para deduplicate.

Quantas verificações aproximadas cada versão executa em N=10.000? Quantas vezes mais rápida é a versão conjunto+lista? Mostre seu cálculo.

Encontrando Elementos Comuns

Problema: Encontrar Itens em Ambas as Listas

Você tem duas listas de classe. Você precisa encontrar alunos inscritos em ambas as classes.

Abordagem lenta: loops aninhados O(N × M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b = ["Bob", "Diana", "Eve", "Frank"]
both = []
for student in class_a:        # N iterations
    if student in class_b:     # M scans each time
        both.append(student)
# O(N × M) — each student scanned against all of class_b

Abordagem rápida: converter uma lista para conjunto O(N + M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b)     # O(M) to build
both = []
for student in class_a:        # N iterations
    if student in class_b_set: # O(1) each time
        both.append(student)
# O(N + M) — build the set once, check N times at O(1) each

Ou ainda mais simples usando a interseção de conjuntos integrada do Python:

both = set(class_a) & set(class_b)  # O(N + M)

O operador & encontra todos os itens que aparecem em ambos os conjuntos.

Reescrever para Velocidade

Uma escola tem dois clubes com 200 membros cada. Este código encontra alunos em ambos os clubes:

chess_club = [...]   # 200 names
art_club = [...]     # 200 names
both_clubs = []
for student in chess_club:
    if student in art_club:
        both_clubs.append(student)
Qual é o Big O deste código? Reescreva-o para ser executado mais rapidamente usando um conjunto. Qual é o Big O da sua versão melhorada? Explique a diferença.

Seu Framework de Decisão

Seu Framework de Decisão de Estrutura de Dados

Toda vez que você escreve código que armazena uma coleção de itens, pergunte:

1. Preciso que os itens estejam em ordem? → lista

2. Preciso de verificações de pertencimento rápidas? → conjunto

3. Preciso de ordem E verificações rápidas? → conjunto + lista juntos

4. Preciso de duplicatas? → lista (conjuntos descartam duplicatas)

---

A percepção principal desta lição: o mesmo programa, resolvendo o mesmo problema, pode rodar 1.000× a 1.000.000× mais rápido apenas escolhendo a estrutura de dados certa. Sem truques inteligentes. Sem matemática complicada. Apenas perguntando: que operações meu código faz com mais frequência? Depois escolhendo a estrutura onde essas operações custam O(1) em vez de O(N).

Esta lição cobriu listas & conjuntos. Existem outras estruturas de dados (dicionários, árvores, heaps) que resolvem outros problemas. O framework de decisão permanece o mesmo: entenda suas operações, depois escolha a estrutura que torna as operações baratas.

---

Quer aprofundar? Nossa lição de Big O cobre taxas de crescimento & cálculos de escala em detalhes. Veja Big O: How Fast Is Fast Enough?. Nosso curso unhamming cobre o defeit real (MOAD-0001) onde exatamente essa correção de lista para conjunto produziu uma aceleração de 1.000× em software de produção. Veja The Missing Chapter: Algorithmic Complexity.

Debugar Este Código

Um aluno escreveu este código para contar quantas palavras únicas aparecem em um livro:

words = book.split()           # list of all words
unique_count = 0
checked = []
for word in words:             # N words
    if word not in checked:    # scans checked list
        checked.append(word)
        unique_count += 1
print(unique_count)

O livro tem 100.000 palavras.

Qual é o Big O deste código? Por que ele executa lentamente em um livro grande? Reescreva-o para resolver o mesmo problema em O(N). E então explique: você poderia resolver isso em uma linha usando um conjunto? Como seria?