SkillsTecnológicas
Menu
Conteúdo da trilha

Como comparar duas soluções para o mesmo problema

Compare soluções corretas por trabalho, memória, clareza, efeitos colaterais e contexto e registre uma decisão técnica revisável.

Conteúdo 50 de 52

A mesma coleção de peças de madeira segue por dois mecanismos desenhados a lápis e ambos chegam ao mesmo resultado correto

A melhor solução só existe dentro de um contexto

Dois algoritmos podem devolver a resposta correta e ainda assim serem escolhas diferentes. Um pode usar menos memória, outro executar menos comparações. Um pode ser direto para uma coleção pequena, enquanto o outro se torna mais adequado quando a entrada cresce. Um terceiro pode ser rápido, mas difícil de alterar com segurança.

Por isso, perguntar “qual código é melhor?” sem declarar o problema, as restrições e os critérios produz apenas preferência pessoal. Uma comparação técnica precisa responder:

  • as duas soluções obedecem ao mesmo contrato?
  • funcionam para os mesmos casos válidos e inválidos?
  • quais recursos cada uma consome?
  • quais efeitos colaterais produzem?
  • qual delas torna a próxima mudança menos arriscada?
  • que característica importa mais neste contexto?

Nesta aula, vamos construir uma decisão verificável. A próxima aula aprofundará como o custo cresce com a entrada; aqui usaremos contagens simples, medições cuidadosas e critérios de engenharia suficientes para escolher sem antecipar toda a análise formal.

Defina uma base comum antes de comparar

Considere o problema: informar se uma coleção contém algum valor repetido. O contrato será:

  1. receber uma coleção de valores comparáveis pela regra de igualdade adotada;
  2. devolver verdadeiro quando qualquer valor aparecer pelo menos duas vezes;
  3. devolver falso para coleção vazia, coleção com um item ou coleção sem repetições;
  4. não modificar a coleção recebida;
  5. tratar entradas fora do domínio conforme a validação já definida pelo sistema.

Essa base evita comparar soluções para problemas discretamente diferentes. Se uma versão considera "A" e "a" iguais e outra não, a divergência é de contrato. Se uma ordena a coleção original e outra preserva sua ordem, existe diferença observável mesmo que o booleano final coincida.

Use a mesma suíte para ambas, incluindo casos extremos e entradas inválidas:

CasoEntradaEsperado
C1[]falso
C2[4]falso
C3[4, 7, 2]falso
C4[4, 7, 4]verdadeiro
C5[4, 4, 7]verdadeiro
C6coleção longa sem repetiçãofalso

Só avance para velocidade, memória ou legibilidade depois que as alternativas passarem pelo mesmo contrato.

Fluxo elimina soluções que não atendem ao contrato ou aos testes e compara as restantes por critérios ligados ao contexto
Correção e restrições são portas de entrada; critérios de qualidade só diferenciam soluções que continuam candidatas.

Solução A: comparar cada par necessário

A primeira estratégia compara cada elemento com os que vêm depois dele:

função tem_repetido_por_pares(valores):
    para i de 0 até tamanho(valores) - 2:
        para j de i + 1 até tamanho(valores) - 1:
            se valores[i] = valores[j]:
                retorne verdadeiro
    retorne falso

O início de j em i + 1 evita comparar um elemento consigo mesmo e impede repetir o mesmo par em ordem inversa. A solução encerra assim que encontra uma igualdade.

Pontos favoráveis:

  • usa poucas estruturas auxiliares;
  • expressa diretamente a pergunta “algum par é igual?”;
  • funciona mesmo quando os valores não podem ser usados como chaves de um conjunto;
  • pode ser suficiente para entradas pequenas e limitadas.

Custos e riscos:

  • quando não há repetição, examina todos os pares;
  • o número de comparações cresce rapidamente com a quantidade de elementos;
  • dois laços e dois índices ampliam a superfície para erros de limite;
  • uma regra de igualdade cara será executada muitas vezes.

A familiaridade com estruturas de repetição aninhadas ajuda a prever a ordem dessas comparações.

Solução B: registrar valores já vistos

A segunda estratégia mantém um conjunto auxiliar:

função tem_repetido_com_conjunto(valores):
    vistos ← conjunto vazio

    para cada valor em valores:
        se valor pertence a vistos:
            retorne verdadeiro
        adicione valor a vistos

    retorne falso

Cada valor pergunta se já apareceu. Se não apareceu, passa a fazer parte do histórico. A solução também encerra no primeiro repetido.

Pontos favoráveis:

  • percorre a coleção uma vez;
  • separa claramente o estado vistos da entrada original;
  • evita recomparar todos os pares;
  • costuma se adaptar melhor a entradas maiores.

Custos e restrições:

  • o conjunto ocupa memória adicional que pode crescer com a entrada;
  • os valores precisam ser compatíveis com a estrutura e sua regra de igualdade;
  • o custo de consulta depende da implementação do conjunto;
  • a solução pode introduzir um conceito desnecessário para uma entrada muito pequena e rigidamente limitada.

Uma estrutura pronta não torna a escolha automaticamente superior. Ela troca trabalho repetido por armazenamento e por pressupostos sobre os dados.

Conte operações antes de cronometrar

Para cinco valores distintos, a solução por pares realiza no pior percurso:

4 + 3 + 2 + 1 = 10 comparações

Com n elementos sem repetição, o total de pares é n × (n - 1) ÷ 2. A solução com conjunto faz até uma consulta e uma inserção por valor: cinco consultas e cinco inserções no exemplo, considerando as operações oferecidas pela estrutura.

Uma coleção de cinco itens é processada por dez comparações entre pares na solução A e por cinco consultas a um registro de vistos na solução B
Contar o trabalho dominante explica a diferença antes de atribuí-la a uma medição de poucos milissegundos.

Essa contagem não encerra a análise. Uma consulta ao conjunto não é “gratuita”, e diferentes tipos de valor podem mudar seu custo. Ainda assim, o modelo revela por que as estratégias tendem a se comportar de maneira diferente quando a entrada aumenta.

Também observe a distribuição dos dados. Se a repetição estiver nas primeiras posições, ambas podem retornar cedo. Se ela não existir, as duas percorrem seus caminhos completos. Uma comparação justa usa tamanhos e distribuições que representam o uso real.

Correção é eliminatória, não uma pontuação

Não dê nota alta de desempenho para compensar uma resposta errada. Correção, preservação do contrato e restrições obrigatórias funcionam como filtros:

atende ao contrato?          não → eliminar
passa pela suíte comum?      não → eliminar
respeita limites obrigatórios? não → eliminar
continua candidata?         sim → comparar qualidades

Se a solução B não aceita o tipo de valor exigido, ela não recebe “menos um ponto”; deixa de ser candidata naquele contexto. Se a solução A estoura o prazo máximo para a carga prevista, ocorre o mesmo.

Essa ordem protege a decisão contra uma planilha enganosa em que várias vantagens pequenas escondem uma falha essencial.

Compare tempo e memória como uma troca

Tempo e memória frequentemente apontam em direções diferentes:

CritérioComparação por paresConjunto de vistos
Trabalho sem repetiçãocompara todos os paresconsulta cada valor uma vez
Memória auxiliarpequena e praticamente constantecresce com os valores distintos
Retorno antecipadoao encontrar um par igualao reencontrar um valor
Dependência estruturaligualdadeconjunto, igualdade e representação compatível
Entrada originalpreservadapreservada

Em um dispositivo com memória extremamente restrita e coleções de no máximo cinco itens, a solução A pode ser a escolha coerente. Em um serviço que recebe milhares de identificadores e possui memória disponível, a solução B tende a evitar muito trabalho repetido.

Não há contradição: o contexto alterou o peso dos critérios.

Verifique efeitos colaterais e pressupostos ocultos

Uma terceira ideia seria ordenar a coleção e procurar vizinhos iguais. Ela pode parecer atraente, mas abre perguntas novas:

  • todos os valores possuem uma ordem definida?
  • ordenar modifica a entrada ou trabalha sobre uma cópia?
  • criar a cópia consome memória aceitável?
  • a ordem original tem significado para quem chamou a função?
  • a função de comparação representa a mesma igualdade usada para detectar duplicados?

Esse exemplo mostra por que a saída principal não é a única observação. Mutação, exceções, dependências e pré-condições fazem parte do comportamento da solução.

Registre explicitamente:

entrada modificada: não
estrutura auxiliar: nenhuma / conjunto
pressuposto sobre valores: comparáveis por igualdade / aceitos pelo conjunto
falhas possíveis: limites de índice / incompatibilidade com a estrutura

Avalie clareza com tarefas concretas

“Mais legível” não deve significar apenas “mais parecido com o código que eu costumo escrever”. Teste a clareza por ações que outra pessoa precisa realizar:

  • explicar a ideia sem executar cada linha;
  • localizar onde a repetição é detectada;
  • alterar a regra de igualdade;
  • adicionar um teste sem conhecer detalhes irrelevantes;
  • prever quais dados auxiliares existem;
  • revisar os limites dos laços;
  • descobrir por que a decisão foi tomada meses depois.

A orientação de revisão de código do Google recomenda basear decisões em fatos e princípios de engenharia, preservando compreensibilidade e manutenibilidade em vez de buscar uma perfeição abstrata. Nomes, fluxo simples e comentários que explicam o porquê ajudam mais que comentários que traduzem instruções confusas.

Para entradas pequenas, a solução por pares pode ser entendida imediatamente por quem domina índices. Para uma equipe familiarizada com conjuntos, a segunda expressa com mais proximidade a ideia “já vi este valor?”. Clareza também depende do repertório legítimo do projeto, mas não deve congelar a equipe em uma solução inadequada.

Considere manutenção e mudança provável

Imagine três mudanças futuras:

  1. devolver qual valor se repetiu;
  2. contar quantos valores distintos se repetem;
  3. processar dados que chegam progressivamente.

O conjunto de vistos oferece uma base natural para as três, embora cada mudança ainda exija contrato e testes próprios. A solução por pares pode continuar válida, mas tende a repetir comparações ou acumular condições.

Agora imagine outra mudança: aceitar objetos que não possuem representação compatível com o conjunto, mas oferecem uma função de igualdade específica. Nesse cenário, a solução A pode exigir menos adaptação.

Manutenibilidade não é adivinhar todos os futuros. É observar a direção provável do produto, a estabilidade dos requisitos e o custo de mudar sem introduzir defeitos. A divisão do algoritmo em partes também ajuda quando a igualdade ou a estratégia de armazenamento precisa variar isoladamente.

Meça quando a decisão depender de desempenho

Depois de formular uma hipótese, faça uma medição controlada. Use:

  • a mesma linguagem, versão e ambiente;
  • implementações equivalentes em correção;
  • entradas iguais para cada execução;
  • tamanhos pequenos, típicos e grandes;
  • coleções sem repetição, com repetição precoce e tardia;
  • várias repetições, não uma execução isolada;
  • tempo e memória quando ambos importarem;
  • preparação dos dados separada do trecho medido.

Ferramentas como timeit, documentada pelo Python, existem porque medir pequenos trechos contém armadilhas. O sistema operacional, aquecimento, coleta de memória e trabalho de preparação podem distorcer resultados. Uma diferença mínima em um microteste também pode ser irrelevante para o produto completo.

Meça com dados representativos e registre o ambiente. Se a restrição for “responder em até 100 ms para 50 mil itens”, compare contra essa exigência, não contra o desejo genérico de ser rápido.

Use uma matriz sem fabricar precisão

Uma matriz organiza evidências, mas os pesos precisam vir do contexto. Considere duas situações:

CritérioDispositivo: até 5 itensServiço: até 100 mil itens
Correçãoobrigatóriaobrigatória
Memória auxiliarpeso altopeso médio
Trabalho quando a entrada crescepeso baixopeso alto
Compatibilidade dos valorespeso altopeso alto
Simplicidade para a equipepeso médiopeso médio

No dispositivo, A pode vencer porque a quantidade é pequena, o limite é garantido e memória é crítica. No serviço, B pode vencer porque evitar pares domina a decisão e o conjunto é compatível com os identificadores.

Dois contextos atribuem pesos diferentes a memória e crescimento do trabalho e levam a escolhas distintas entre as soluções A e B
A matriz não encontra uma vencedora universal; ela torna visível por que o contexto mudou a decisão.

Evite somar notas como se 4,2 fosse uma verdade científica. Escalas ajudam a ordenar uma conversa, mas não substituem restrições, medições nem justificativas. Se uma pequena mudança de peso inverte a escolha, declare que a decisão é sensível e planeje reavaliá-la.

Registre uma decisão que possa ser revisada

Uma decisão técnica curta pode seguir este modelo:

problema: detectar valores repetidos sem alterar a entrada
alternativas: comparar pares; registrar valores vistos
contexto: até 100 mil identificadores compatíveis com conjunto
critérios obrigatórios: correção, preservação da entrada, limite de tempo
evidências: mesma suíte; contagem de operações; medição no ambiente-alvo
decisão: conjunto de vistos
troca aceita: memória auxiliar para reduzir comparações repetidas
gatilho de revisão: mudança no tipo dos valores ou no limite de memória

O registro preserva o raciocínio que o código sozinho não mostra. Ele também impede que uma escolha contextual vire regra eterna: se o tamanho máximo, o hardware ou o tipo dos dados mudar, o gatilho indica que a comparação precisa ser refeita.

Evite atalhos que parecem critérios

Alguns argumentos são insuficientes quando aparecem sozinhos:

  • “tem menos linhas”: concisão não garante clareza nem menor custo;
  • “usa uma estrutura avançada”: sofisticação não prova adequação;
  • “foi mais rápido uma vez”: uma execução não controla variação;
  • “tem a melhor complexidade”: o modelo não remove constantes, memória, restrições ou efeitos;
  • “é o padrão da internet”: falta o contexto que originou a recomendação;
  • “sempre fizemos assim”: consistência importa, mas não justifica perpetuar um problema;
  • “pode ser necessário no futuro”: otimização especulativa precisa de risco concreto.

Prefira uma frase verificável: “para coleções de até cinco itens, A passou pela suíte e pelo limite medido usando menos memória auxiliar” ou “para 100 mil identificadores, B cumpriu o prazo e A não cumpriu”.

Pratique com duas formas de encontrar interseção

Compare duas soluções para responder se duas coleções compartilham algum valor:

solução A: comparar cada item da primeira com cada item da segunda
solução B: registrar os itens da menor coleção e consultar os da maior

Siga esta sequência:

  1. escreva um único contrato, inclusive para vazios e tipos incompatíveis;
  2. crie uma suíte comum com ausência e presença de interseção;
  3. prove que nenhuma solução modifica as entradas;
  4. conte comparações, consultas e itens armazenados;
  5. avalie coleções de tamanhos muito diferentes;
  6. declare quais tipos podem participar do conjunto;
  7. escolha dois contextos com restrições opostas;
  8. produza uma decisão e um gatilho de revisão para cada contexto.

Se você não consegue explicar por que a vencedora muda, os critérios ainda estão vagos.

O que você deve guardar

Compare soluções em camadas. Primeiro fixe o contrato e use a mesma suíte. Elimine alternativas incorretas ou incompatíveis com restrições obrigatórias. Depois avalie trabalho, memória, efeitos colaterais, clareza, manutenção e capacidade de mudança.

Conte operações antes de medir e, quando o desempenho importar, use entradas representativas, repetições e ambiente controlado. Uma matriz organiza evidências, mas seus pesos vêm do uso real. A decisão final precisa registrar a troca aceita e o evento que exigirá nova avaliação.

O próximo passo é estudar a introdução à eficiência de algoritmos para descrever formalmente como trabalho e memória crescem com a entrada.

Referências