Trilha de aprendizado · Nível 14 · Tutorial 4

Reduzir buscas repetidas com índices em memória

Substitua varreduras repetidas por índices com dicionários ou conjuntos quando o ganho de consulta justificar a construção e a memória adicional.

  • Nível: Intermediário
  • Duração: 18 min
  • 8 passos
Reduzir buscas repetidas com índices em memória

O que você vai percorrer

  1. Reconhecer o trabalho repetido nas buscas Identifique quando consultas repetidas voltam a percorrer a mesma fonte e por que isso pode justificar uma estrutura auxiliar. 2 min
  2. Usar um conjunto para consultas de existência Prepare um conjunto de códigos uma vez e reutilize-o para responder se um código está presente no catálogo. 2 min
  3. Associar chaves a registros com um dicionário Crie um índice por código para recuperar registros sem repetir varreduras e defina claramente o que ocorre quando não há correspondência. 2 min
  4. Preservar duplicatas, multiplicidade e ordem Escolha uma política de indexação que mantenha o mesmo resultado observável da busca original. 3 min
  5. Manter o índice coerente com a fonte Entenda quando um índice deixa de refletir a coleção original e escolha entre atualizá-lo pontualmente ou reconstruí-lo. 2 min
  6. Avaliar construção, consultas e memória Compare o custo completo das varreduras e dos índices, incluindo preparação, resultados produzidos, manutenção e memória auxiliar. 3 min
  7. Comparar as estratégias em uma execução local Meça uma busca por grupos com varredura e com índice de listas, incluindo o custo de construção e separando uso único de reutilização. 3 min
  8. Aplicar e justificar a escolha do índice Finalize a otimização com evidências de equivalência, custo completo, memória adicional e coerência após mudanças na fonte. 3 min

O que você vai aprender

  • Reconhecer buscas repetidas que multiplicam o trabalho conforme as entradas crescem.
  • Construir um índice adequado a consultas de existência ou recuperação de registros.
  • Preservar o comportamento esperado para duplicatas, ausências e ordem dos resultados.
  • Comparar o custo completo da estratégia, incluindo construção, consultas e memória.

Antes de começar

  • Estimar tempo e memória com notação O grande
  • Medir trechos de código com timeit
  • Localizar gargalos de execução com cProfile
  • Consultar e atualizar dados com dicionários
  • Eliminar duplicatas e comparar grupos com conjuntos
  • Manter igualdade e hash coerentes

Passo 1 de 8

Reconhecer o trabalho repetido nas buscas

Identifique quando consultas repetidas voltam a percorrer a mesma fonte e por que isso pode justificar uma estrutura auxiliar.

O mesmo catálogo, muitas buscas

Uma busca simples pode virar gargalo

Imagine um catálogo com n registros de produtos. Seu programa recebe m consultas: algumas procuram um código, outras uma categoria.

Uma busca sequencial pode parecer barata isoladamente. Mas, se cada consulta percorre novamente o catálogo, a mesma coleção é lida muitas vezes. Como visto no perfilamento, uma operação pouco cara por chamada pode concentrar tempo quando é chamada em grande quantidade.

Varreduras repetidas

Cada seta representa uma consulta que começa novamente no catálogo.

Diagrama mostrando uma lista vertical de produtos sendo percorrida por quatro consultas independentes, cada uma começando no início da mesma lista.

Sem uma organização auxiliar, consultas independentes podem repetir a leitura da mesma fonte.

De onde vem O(mn)

Busca sequencial repetida

Cada chamada pode examinar o catálogo inteiro.

python
catalogo = [
    {"codigo": "A10", "categoria": "livros"},
    {"codigo": "B20", "categoria": "jogos"},
    {"codigo": "C30", "categoria": "livros"},
]

consultas = ["C30", "X99", "A10"]

def buscar_por_codigo(catalogo, codigo):
    for produto in catalogo:
        if produto["codigo"] == codigo:
            return produto
    return None

resultados = []
for codigo in consultas:
    resultados.append(buscar_por_codigo(catalogo, codigo))

print(resultados)

Duas dimensões de entrada

Aqui, n é a quantidade de produtos no catálogo e m é a quantidade de códigos consultados. No pior caso, cada uma das m consultas examina n registros: O(mn).

O trabalho efetivo varia: um produto no início encerra a busca cedo; um produto no fim exige mais comparações; uma ausência percorre toda a fonte. Ainda assim, repetir varreduras é a hipótese importante a investigar.

Hipótese: preparar para reutilizar

Trocar preparação por consultas mais diretas

Quando muitas consultas usam a mesma fonte durante um período, vale considerar uma organização auxiliar por chave criada uma vez e reutilizada nas consultas.

A intenção não é eliminar o custo: a preparação percorre a fonte e ocupa memória adicional. A possível vantagem é evitar que cada nova consulta recomece a varredura completa. Nos próximos passos, você construirá as estruturas adequadas para cada tipo de pergunta.

Dica

Pergunta de diagnóstico

Antes de otimizar, pergunte: a coleção permanece suficientemente estável e a mesma informação será consultada muitas vezes? Se a resposta for sim, um índice auxiliar é uma hipótese plausível para medir depois.

Reconheça o padrão

Qual trabalho está sendo repetido?

Um catálogo tem n produtos. Para cada um de m códigos solicitados, uma função percorre o catálogo desde o início até encontrar o código ou chegar ao fim. Qual afirmação descreve melhor o cenário?

Passo 2 de 8

Usar um conjunto para consultas de existência

Prepare um conjunto de códigos uma vez e reutilize-o para responder se um código está presente no catálogo.

Preparar um índice de presença

Uma pergunta, uma estrutura

Quando cada consulta só precisa responder “este código existe?”, extraia os códigos do catálogo para um set antes do laço de consultas. Assim, a coleção original não precisa ser percorrida novamente para cada código perguntado.

Da fonte ao conjunto reutilizável

O conjunto guarda somente as chaves distintas necessárias para responder à pergunta de presença.

Diagrama mostrando uma lista de produtos com códigos repetidos sendo transformada em um conjunto de códigos únicos; várias consultas de código apontam para o mesmo conjunto.

A fonte é percorrida na preparação; as consultas repetidas usam o conjunto auxiliar.

Conjunto construído fora do laço

Cada consulta usa in no mesmo conjunto já preparado.

python
catalogo = [
    {"codigo": "A10", "nome": "Caderno"},
    {"codigo": "B20", "nome": "Caneta"},
    {"codigo": "A10", "nome": "Caderno - outra entrada"},
]

# Preparação: uma varredura para extrair a chave de consulta.
codigos_disponiveis = {produto["codigo"] for produto in catalogo}

consultas = ["B20", "X99", "A10", "X99"]
for codigo in consultas:
    print(codigo, codigo in codigos_disponiveis)

O que o conjunto responde

Presença, não detalhes

O conjunto responde se uma chave está presente. Ele não informa quantas vezes o código apareceu nem qual produto estava associado a ele. Para este uso, a chave extraída ao construir o conjunto deve seguir o mesmo critério usado na consulta — por exemplo, o mesmo código normalizado.

Dica

Chaves adequadas

Use chaves hasháveis e estáveis durante a vida do conjunto. Strings e números são escolhas comuns. Mesmo com custo médio esperado O(1), isso depende de hashing e igualdade com custo limitado; colisões ou chaves caras impedem tratar esse custo como garantia absoluta.

Classifique a necessidade da consulta

Qual pergunta um conjunto resolve?

Associe cada pergunta ao tipo de informação que ela exige.

Toque em um item e depois no par correspondente.

Passo 3 de 8

Associar chaves a registros com um dicionário

Crie um índice por código para recuperar registros sem repetir varreduras e defina claramente o que ocorre quando não há correspondência.

Do catálogo ao índice de recuperação

Uma chave, um registro

Quando cada produto tem um código único, construa um dicionário que associe cada código ao seu registro. A construção percorre o catálogo uma vez; depois, várias consultas reutilizam o mesmo índice.

A chave do índice deve representar o mesmo critério da busca original. Se a busca comparava produto["codigo"] == codigo_consultado, use exatamente produto["codigo"] como chave.

Fluxo da indexação

Diagrama mostrando registros de produtos com códigos únicos alimentando um dicionário cujas chaves apontam para os respectivos registros; várias consultas saem do mesmo dicionário.

Construa uma vez a associação código → registro e consulte-a quantas vezes forem necessárias.

Construir uma vez, consultar várias

Índice por código

A compreensão cria o índice fora do laço de consultas. A hipótese de códigos únicos faz parte do contrato deste exemplo.

python
catalogo = [
    {"codigo": "A10", "nome": "Caderno", "preco": 18.0},
    {"codigo": "B20", "nome": "Caneta", "preco": 4.5},
    {"codigo": "C30", "nome": "Mochila", "preco": 120.0},
]

# Construção: uma associação por código único.
por_codigo = {produto["codigo"]: produto for produto in catalogo}

consultas = ["B20", "C30", "B20"]
for codigo in consultas:
    produto = por_codigo[codigo]
    print(produto["nome"])

# Caneta
# Mochila
# Caneta

Dica

Hipótese explícita

Este índice simples pressupõe que dois registros não compartilham o mesmo código. Se houver repetição, uma associação pode substituir outra; as políticas para duplicatas serão tratadas no próximo passo.

Definir o contrato para códigos ausentes

Ausência não é sempre erro

Uma consulta pode exigir comportamentos diferentes para um código que não está no catálogo:

  • usar por_codigo.get(codigo) para retornar um padrão, como None;
  • testar if codigo in por_codigo quando for preciso distinguir presença;
  • acessar por_codigo[codigo] quando o contrato determina que a ausência deve sinalizar KeyError.

Escolha o comportamento que a busca anterior já prometia ao restante do programa.

None pode ser um valor armazenado

Aqui, None é um valor válido do campo preco_promocional. Por isso, get sozinho não permite saber se o código existe.

python
por_codigo = {
    "A10": {"codigo": "A10", "nome": "Caderno", "preco_promocional": None},
}

codigo = "A10"
produto = por_codigo.get(codigo)

if codigo in por_codigo:
    print(produto["preco_promocional"])  # None: valor válido armazenado
else:
    print("Código não encontrado")

Verifique o contrato da consulta

Presença antes do acesso

Complete a condição para imprimir "Código não encontrado" apenas quando a chave estiver ausente:

if codigo ____ por_codigo:

Chave versus valor

Se por_codigo.get(codigo) retorna None, então o código certamente não existe no dicionário.

Passo 4 de 8

Preservar duplicatas, multiplicidade e ordem

Escolha uma política de indexação que mantenha o mesmo resultado observável da busca original.

Quando uma chave se repete

Um dicionário simples não guarda todas as ocorrências

Um índice chave → registro funciona quando a chave é única. Se ela puder se repetir, a atribuição posterior substitui a anterior. Portanto, o índice pode mudar o resultado da busca, mesmo que a consulta pareça mais rápida.

Sobrescrita versus agrupamento

Compare o efeito de indexar produtos que compartilham a mesma categoria.

Diagrama mostrando três registros de produtos em ordem na fonte, dois com a mesma categoria, um dicionário simples mantendo apenas o segundo registro da categoria repetida e um dicionário de listas mantendo os dois registros na ordem original.

Para recuperar todas as ocorrências, a chave deve apontar para um grupo, não para apenas um registro.

Atenção

Defina a política antes de construir

Para chaves repetidas, o contrato precisa escolher uma política: manter o primeiro registro, manter o último, rejeitar a duplicata ou guardar todos. Não deixe a política ser um efeito acidental de indice[chave] = registro.

Índice para recuperar todos os produtos de uma categoria

Agrupe em listas na ordem da fonte

Quando a busca original retorna todos os produtos de uma categoria, construa um dicionário de listas. Ao percorrer a fonte sequencialmente e usar append, cada lista preserva a ordem em que aqueles registros apareciam na fonte.

Índice de categorias

Cada categoria aponta para todos os produtos correspondentes.

python
produtos = [
    {"codigo": "A1", "categoria": "livros", "nome": "Python"},
    {"codigo": "B2", "categoria": "casa", "nome": "Luminária"},
    {"codigo": "C3", "categoria": "livros", "nome": "Algoritmos"},
]

por_categoria = {}
for produto in produtos:
    categoria = produto["categoria"]
    por_categoria.setdefault(categoria, []).append(produto)

consultas = ["livros", "inexistente", "livros"]
resultados = [por_categoria.get(categoria, []) for categoria in consultas]

print(resultados)
# [[{'codigo': 'A1', ...}, {'codigo': 'C3', ...}], [],
#  [{'codigo': 'A1', ...}, {'codigo': 'C3', ...}]]

Dica

Duas ordens diferentes importam

A lista de cada categoria segue a ordem da fonte. Já resultados segue a ordem das consultas, inclusive quando uma consulta se repete. Percorrer as chaves do dicionário não recompõe nenhuma dessas duas sequências.

O ganho da localização não elimina o custo da saída

Encontrar o grupo não é copiar seus itens

Localizar a lista de uma categoria no dicionário tem custo médio esperado constante. Porém, se a aplicação precisar percorrer, copiar ou exibir os itens encontrados, esse trabalho cresce com a quantidade de registros do grupo. Uma consulta que retorna 500 produtos ainda precisa lidar com 500 resultados.

Analise o contrato

A fonte tem dois produtos na categoria livros. As consultas são ['livros', 'ausente', 'livros'], e a busca original devolve todos os produtos encontrados por consulta, em ordem. Por que um dicionário categoria → produto altera o resultado? Qual estrutura e qual comportamento de ausência você escolheria para preservar esse contrato?

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 8

Manter o índice coerente com a fonte

Entenda quando um índice deixa de refletir a coleção original e escolha entre atualizá-lo pontualmente ou reconstruí-lo.

Um índice é uma fotografia organizada da fonte

Mudanças não se propagam sozinhas

Um dicionário usado como índice é uma estrutura auxiliar: ele não acompanha automaticamente inserções, remoções ou substituições feitas na lista de produtos.

Se você construiu por_codigo e depois alterou a fonte, faça uma pergunta antes de consultar: o índice ainda representa o estado que deve ser pesquisado?

Fonte e índice podem se separar

Diagrama mostrando uma lista de produtos e um dicionário por código; após a alteração de um código na lista, o dicionário continua apontando para a chave antiga.

Alterar a fonte não atualiza automaticamente a chave já armazenada no índice.

Atenção

Atenção à chave indexada

Se o campo usado como chave muda, o registro pode continuar existindo, mas o índice ainda o associa à chave antiga. Remova o vínculo antigo e crie o novo vínculo — ou reconstrua o índice antes das consultas atuais.

Mutar um registro não é substituí-lo

O que o índice enxerga

Quando o índice guarda referências aos mesmos dicionários da fonte, alterar um campo não indexado é visível pela consulta: a referência continua sendo a mesma.

Já substituir um item da lista por outro dicionário não troca a referência guardada no índice. E alterar a própria chave exige atualizar o mapeamento, mesmo que o objeto seja compartilhado.

Atualização incremental de uma chave única

Execute este exemplo localmente para observar o índice antes e depois da alteração.

python
produtos = [
    {"codigo": "A10", "nome": "Caderno", "preco": 18.0},
    {"codigo": "B20", "nome": "Caneta", "preco": 4.5},
]

por_codigo = {produto["codigo"]: produto for produto in produtos}

# Campo não indexado: a referência compartilhada mostra o novo preço.
produtos[0]["preco"] = 20.0
print(por_codigo["A10"]["preco"])  # 20.0

# Campo indexado: atualize também os vínculos do índice.
produto = produtos[1]
codigo_anterior = produto["codigo"]
novo_codigo = "B21"

del por_codigo[codigo_anterior]
produto["codigo"] = novo_codigo
por_codigo[novo_codigo] = produto

print(por_codigo.get("B20"))  # None
print(por_codigo["B21"])      # registro da Caneta

Dica

Respeite o contrato de duplicatas

A atualização incremental só é simples quando a mudança é conhecida e a política de duplicatas está clara. Por exemplo, antes de inserir uma nova chave em um índice de chaves únicas, valide se ela já existe; em um índice de listas, atualize o grupo correto sem perder sua ordem.

Atualizar ou reconstruir?

Escolha pelo período de validade

Atualize incrementalmente quando cada alteração é conhecida, pequena e simples de refletir no índice. Reconstrua quando houve um lote de mudanças, remoções e substituições, ou quando conferir todos os vínculos seria mais complexo e arriscado.

Defina um período de validade: construa ou atualize o índice antes do conjunto de consultas que precisa enxergar dados atuais. Um índice pode ser reutilizado apenas enquanto sua fonte permanecer compatível com ele.

Atualize uma mudança de código

Um índice por_codigo associa códigos únicos aos registros. Ordene as ações para trocar o código de um produto de B20 para B21 e manter o índice coerente.

  1. Associar `B21` ao registro no índice.
  2. Guardar o código atual do registro em uma variável, como `codigo_anterior`.
  3. Alterar o campo `codigo` do registro para `B21`.
  4. Remover do índice a entrada associada a `codigo_anterior`.

Decida a estratégia

Você recebeu um lote que removeu produtos, substituiu vários registros e alterou códigos. Quando reconstruir o índice tende a ser mais seguro do que atualizá-lo pontualmente? Justifique.

Escreva pelo menos 40 caracteres (0/40).

Passo 6 de 8

Avaliar construção, consultas e memória

Compare o custo completo das varreduras e dos índices, incluindo preparação, resultados produzidos, manutenção e memória auxiliar.

O custo não termina na consulta

Duas estratégias, dois custos completos

Considere n registros na fonte e m consultas exatas.

  • Sem índice, cada consulta pode varrer até n registros: O(mn).
  • Com conjunto ou dicionário, construa o índice uma vez: O(n) esperado; depois faça m consultas: O(m) esperado. Total: O(n + m) esperado.

Isso pressupõe chaves com hashing e igualdade de custo limitado, além de uma tabela hash com comportamento médio esperado. O índice não elimina o custo de produzir a resposta.

Varreduras repetidas versus índice reutilizado

Diagrama comparando várias consultas percorrendo repetidamente uma lista de produtos com a construção única de um índice hash reutilizado por várias consultas.

A preparação troca uma passagem inicial e memória auxiliar por evitar novas varreduras completas.

Exemplo

Recuperar um registro não é recuperar um grupo

Para consultar um produto por código único, o resultado tem tamanho limitado: a análise O(n + m) descreve construção e consultas.

Para consultar categorias, uma consulta pode devolver vários produtos. Se, somando todas as respostas, forem produzidos r registros, o custo esperado passa a ser O(n + m + r).

Mesmo que localizar a lista da categoria seja esperado como constante, percorrer ou materializar 80 produtos retornados custa trabalho proporcional a esses 80.

Reutilização e memória auxiliar

Quando o investimento pode compensar

A construção só é amortizada pelas consultas feitas enquanto o índice continua válido. Em um uso único, o custo de construir — e talvez manter — o índice pode não compensar uma varredura simples. Em muitas consultas sobre a mesma fonte estável, esse custo inicial é dividido entre elas.

Não existe um número universal de consultas a partir do qual o índice vence: depende de n, m, distribuição das buscas, tamanho das respostas, custo das chaves, manutenção e memória disponível.

Conte estruturas, não bytes

Se u é o número de chaves distintas:

  • Um conjunto de chaves ou um dicionário com um registro por chave usa memória auxiliar estrutural O(u).
  • Um dicionário de listas que agrupa todos os n registros usa O(u + n): há chaves/grupos e referências para os registros em cada grupo.

A fonte e o índice coexistem. Guardar uma referência ao registro não cria uma cópia integral dele, mas a tabela, as listas e as próprias referências ainda ocupam memória. Essas ordens e contagens não informam, sozinhas, o total real em bytes.

Dica

Inclua manutenção na decisão

Se a fonte muda durante o período de uso, some o custo de atualizar ou reconstruir o índice. Compare o cenário real: construção + consultas + manutenção + memória auxiliar.

Escolha pelo cenário completo

Uso único ou reutilização?

Um catálogo pequeno tem n = 20 produtos. O programa fará apenas uma consulta por código e descartará o catálogo em seguida. Qual decisão é mais justificável antes de medir?

Justifique uma decisão

Analise dois cenários

Cenário A: 20 registros e 1 consulta exata. Cenário B: 200 mil registros, milhares de consultas por categoria, fonte estável durante o lote e grupos com resultados que precisam ser percorridos. Qual estratégia você tenderia a escolher em cada caso? Justifique incluindo construção, consultas, saída e memória.

Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 8

Comparar as estratégias em uma execução local

Meça uma busca por grupos com varredura e com índice de listas, incluindo o custo de construção e separando uso único de reutilização.

Defina fronteiras comparáveis

O que será medido

Use uma carga sintética determinística: o catálogo contém produtos repetidos por categoria, e as consultas incluem categorias presentes, ausentes e repetidas. Primeiro, confirme que as duas estratégias produzem exatamente o mesmo resultado.

Depois, compare quatro medidas: varrer o lote inteiro; construir e consultar no mesmo lote; consultar um índice já pronto; e construir isoladamente. A terceira medida representa reutilização. Sozinha, ela não responde se o investimento de construir o índice compensou para um único lote.

Duas fronteiras de medição

Diagrama comparando várias consultas que percorrem um catálogo inteiro com a construção única de um índice de grupos seguida de várias consultas diretas.

A construção pertence ao cenário de uso único; ela pode ser amortizada quando o mesmo índice atende a novos lotes de consultas.

Execute o experimento completo

Copie para um arquivo local

Crie um arquivo chamado comparar_indices.py, copie o código abaixo e execute python comparar_indices.py. Ele usa somente a biblioteca padrão. O assert vem antes das medições porque tempo menor não serve se o contrato de resultado mudou.

comparar_indices.py

python
import timeit

# Ajuste estes quatro controles e execute novamente.
N = 10_000             # registros na fonte
M = 600                # consultas no lote
U = 40                 # categorias distintas (cardinalidade de chaves)
FRACAO_AUSENTES = 0.25 # parte das consultas sem correspondência
REPETICOES = 5
NUMERO = 3


def criar_catalogo(n, u):
    u = min(u, n)
    return [
        {
            "id": i,
            "codigo": f"P{i:06d}",
            "categoria": f"categoria-{i % u}",
        }
        for i in range(n)
    ]


def criar_consultas(m, u, fracao_ausentes):
    presentes = round(m * (1 - fracao_ausentes))
    consultas_presentes = [f"categoria-{i % u}" for i in range(presentes)]
    consultas_ausentes = [f"ausente-{i % 7}" for i in range(m - presentes)]
    return consultas_presentes + consultas_ausentes


def buscar_varrendo(catalogo, consultas):
    # Uma tupla por consulta preserva inclusive consultas repetidas e ausências.
    return tuple(
        tuple(produto for produto in catalogo if produto["categoria"] == categoria)
        for categoria in consultas
    )


def construir_indice(catalogo):
    indice = {}
    for produto in catalogo:
        indice.setdefault(produto["categoria"], []).append(produto)
    return indice


def buscar_no_indice(indice, consultas):
    # tuple() evita expor a lista interna e mantém o mesmo formato da varredura.
    return tuple(tuple(indice.get(categoria, ())) for categoria in consultas)


def consumir(resultados):
    # Percorre também os grupos retornados: não mede só a localização da chave.
    return sum(produto["id"] for grupo in resultados for produto in grupo)


def melhor_por_execucao(comando):
    tempos = timeit.Timer(comando, globals=globals()).repeat(
        repeat=REPETICOES,
        number=NUMERO,
    )
    return min(tempos) / NUMERO


catalogo = criar_catalogo(N, U)
consultas = criar_consultas(M, U, FRACAO_AUSENTES)
indice_pronto = construir_indice(catalogo)

# Equivalência: grupos, ordem da fonte, ausências e ordem/repetições das consultas.
resultado_varredura = buscar_varrendo(catalogo, consultas)
resultado_indice = buscar_no_indice(indice_pronto, consultas)
assert resultado_varredura == resultado_indice
assert consumir(resultado_varredura) == consumir(resultado_indice)


def medir_varreduras():
    return consumir(buscar_varrendo(catalogo, consultas))


def medir_construcao_e_consultas():
    indice = construir_indice(catalogo)
    return consumir(buscar_no_indice(indice, consultas))


def medir_consultas_com_indice_pronto():
    return consumir(buscar_no_indice(indice_pronto, consultas))


def medir_construcao():
    return len(construir_indice(catalogo))


print(f"n={N}, m={M}, u={len(indice_pronto)}, ausentes={FRACAO_AUSENTES:.0%}")
print(f"estruturas mantidas: 1 dicionário e {len(indice_pronto)} listas de grupos")
print(f"varreduras do lote:          {melhor_por_execucao('medir_varreduras()'):.6f} s")
print(f"construção + consultas:      {melhor_por_execucao('medir_construcao_e_consultas()'):.6f} s")
print(f"consultas, índice pronto:    {melhor_por_execucao('medir_consultas_com_indice_pronto()'):.6f} s")
print(f"construção isolada:          {melhor_por_execucao('medir_construcao()'):.6f} s")

Varie uma condição por vez

Dica

Como interpretar sem prometer um ganho fixo

Anote os quatro tempos junto com n, m, u e a fração de ausentes. Altere uma variável por execução: aumente M para avaliar reutilização, altere U para mudar a cardinalidade e modifique FRACAO_AUSENTES para mudar a distribuição das consultas.

As contagens de chaves, listas e registros descrevem as estruturas auxiliares; elas não são uma medição em bytes. Para um lote único, compare principalmente “varreduras do lote” com “construção + consultas”. Para vários lotes durante a validade do índice, compare as consultas com índice pronto, sem esquecer que a construção ocorreu antes.

Mantenha o contrato

Não troque a carga de consultas entre versões para favorecer uma estratégia. Ambas precisam processar as mesmas ausências, repetir as mesmas consultas e consumir todos os registros de cada grupo retornado. Se você alterar a fonte, reconstrua ou atualize o índice antes de reutilizá-lo.

Registre sua decisão provisória

O que suas medições mostram?

Execute o script com uma configuração sua. Registre n, m, u, a fração de ausentes e os quatro tempos. Em seguida, explique por que o tempo de consultas com o índice pronto, isoladamente, não decide se a indexação compensa para um único lote.

Escreva pelo menos 180 caracteres (0/180).

Passo 8 de 8

Aplicar e justificar a escolha do índice

Finalize a otimização com evidências de equivalência, custo completo, memória adicional e coerência após mudanças na fonte.

Decida pelo contrato, não apenas pelo tempo

Síntese da escolha

A estrutura deve reproduzir o contrato da consulta antes de buscar velocidade:

  • Varredura: adequada para poucas consultas, fonte pequena ou quando a preparação não se paga.
  • Conjunto (set): responde apenas se uma chave existe.
  • Dicionário simples (dict): recupera um registro por chave, quando a política para chaves repetidas está definida.
  • Dicionário de listas: recupera todos os registros de uma chave repetida, preservando a ordem de inserção da fonte em cada grupo.

A decisão final inclui o tempo de construção, as consultas durante a validade do índice, a memória auxiliar e o comportamento para duplicatas, ausências e ordem.

Contrato da consulta e estrutura

Compare o que cada estrutura consegue responder sem perder informação necessária.

Diagrama comparando uma lista percorrida, um conjunto de códigos, um dicionário de código para produto e um dicionário de categoria para lista ordenada de produtos.

A pergunta define a estrutura: existência, um registro ou todos os registros de um grupo.

Dica

Critério de aceitação

Adote o índice somente se ele preservar o resultado exigido e se a troca de memória por tempo for adequada à carga medida. Um resultado mais rápido que muda duplicatas, ausências ou ordem não é uma otimização correta.

Valide uma mudança antes de reutilizar o índice

Fonte alterada, índice revisado

No seu script local do step anterior, escolha uma configuração de carga que você mediu. Em seguida, altere a fonte e atualize ou reconstrua o índice antes de repetir a consulta. O exemplo abaixo usa reconstrução: uma opção simples e segura após um lote de alterações.

Recriar o índice de grupos

Adapte os nomes das chaves ao seu catálogo local.

python
from collections import defaultdict

# A fonte pode ter sido alterada por um lote de operações.
produtos.append({"codigo": "P-900", "categoria": "casa", "nome": "Luminária"})

# Reconstrua quando o lote terminar, antes das consultas atuais.
por_categoria = defaultdict(list)
for produto in produtos:
    por_categoria[produto["categoria"]].append(produto)

consultas = ["casa", "inexistente", "casa"]
resultado_indexado = [list(por_categoria.get(categoria, [])) for categoria in consultas]
resultado_varrido = [
    [produto for produto in produtos if produto["categoria"] == categoria]
    for categoria in consultas
]

assert resultado_indexado == resultado_varrido
print(resultado_indexado)

Atenção

Não consulte um índice vencido

Alterar a lista de origem não atualiza automaticamente o dicionário auxiliar. Se você optar por atualização incremental em vez de reconstrução, aplique a mesma política de duplicatas e de ordem usada na construção original.

Registre e defenda sua decisão

Aplicação final

Com base no experimento local, registre sua decisão: manter varredura ou usar qual índice? Inclua: (1) o contrato da consulta; (2) como verificou duplicatas, ausências, repetições e ordem; (3) quais tempos comparou, incluindo ou separando a construção; (4) a memória auxiliar estrutural; e (5) a política após mudanças na fonte.

Escreva pelo menos 220 caracteres (0/220).

Resumo

Checklist final

Use este checklist ao otimizar buscas repetidas.

  • Comece pelo contrato: existência pede conjunto; um registro por chave pode pedir dicionário; grupos pedem dicionário de listas; poucas consultas podem justificar varredura.
  • Confirme equivalência funcional antes de comparar tempos: duplicatas, ausências, ordem e consultas repetidas importam.
  • Compare cenários coerentes: varreduras, construção mais consultas e reutilização do índice já construído.
  • Conte a memória auxiliar como estrutura adicional; referências aos registros não eliminam o custo de tabelas, listas e referências.
  • Defina quando atualizar incrementalmente ou reconstruir, e faça isso antes de consultas que exigem dados atuais.

Tutorial concluído

Parabéns! Você concluiu: Reduzir buscas repetidas com índices em memória

Muito bem! Você sabe avaliar quando substituir varreduras repetidas por índices em memória, preservando o comportamento e considerando construção, reutilização, memória e atualização.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso