Padrão para o algoritmo que fornece uma explicação de como chega a uma solução quando necessário


14

O cenário a seguir me aconteceu várias vezes.

Programei um algoritmo que resolve um determinado problema. Funciona bem e encontra as soluções corretas. Agora, quero ter uma opção para informar ao algoritmo "escreva uma explicação completa de como você chegou à solução". Meu objetivo é poder usar o algoritmo em demonstrações online, aulas tutoriais, etc. Ainda quero ter a opção de executar o algoritmo em tempo real, sem as explicações. O que é um bom padrão de design para usar?

EXEMPLO: Suponha que eu implemente esse método para encontrar o maior divisor comum . O método implementado atual retorna a resposta correta, mas sem explicações. Quero ter uma opção para o método explicar suas ações, como:

Initially, a=6 and b=4. The number of 2-factors, d, is initialized to 0.
a and b are both even, so we divide them by 2 and increment d by 1.
Now, a=3 and b=2.
a is odd but b is even, so we divide b by 2.
Now, a=3 and b=1.
a and b are both odd, so we replace a by (a-b)/2 = 1.
Now, a=1 and b=1.
a=b, so the GCD is a*2^d = 2.

A saída deve ser retornada para que possa ser facilmente exibida no console e em aplicativos baseados na Web.

Qual é um bom padrão para fornecer explicações quando necessário, sem prejudicar o desempenho em tempo real do algoritmo quando as explicações não são necessárias?

Respostas:


50

O "padrão" que você está procurando é chamado "log", basta fazer as instruções de log tão detalhadas quanto você precisar. Ao usar uma estrutura de log decente, você poderá ativá-lo e desativá-lo em tempo de execução, fornecer níveis de verbosidade diferentes ou adaptar a saída para diferentes finalidades (como Web x console).

Se isso tiver um impacto significativo no desempenho (mesmo que o log esteja desativado) provavelmente dependerá do idioma, da estrutura e do número de instruções de log necessárias no caso específico. Em linguagens compiladas, se isso realmente se tornar um problema, você poderá fornecer uma opção de compilador para criar "variante de log" e uma "variante que não seja de log" do seu código. No entanto, eu recomendo fortemente a otimização "just in case", sem medir primeiro.


2
Embora eles não sejam algo que você ligue e desligue como o log, parece que os comentários e o código de auto-documentação devem receber pelo menos uma menção honrosa em uma pergunta sobre um "algoritmo que se explica".
Candied_orange

9
@CandiedOrange a pergunta pede especificamente uma "explicação" com valores reais de tempo de execução incluídos nela. Comentários não ajudarão muito nesse caso.
metacubed

@metacubed oh vamos lá. Eu não disse que era uma alternativa ao log. Veja o título da pergunta e pense no tráfego vindo por aqui.
Candied_orange 11/05

4
@CandiedOrange: Eu acho que o título da pergunta é enganoso, você está correto, poderia ser interpretado dessa maneira, mas não é o que o OP está pedindo. Mas deixe-me corrigir isso, vou editar o título.
Doc Brown

1
Observe que algo como treelog foi projetado especificamente para produzir uma saída que explica cálculos complexos, produzindo um registro completo de chamadas de função.
Boris the Spider

7

Um bom padrão é o Observer. https://en.wikipedia.org/wiki/Observer_pattern

No seu algoritmo, em cada ponto em que você deseja gerar algo, você notifica alguns observadores. Eles então decidem o que fazer, seja para enviar seu texto no console ou enviá-lo para o mecanismo HTML / Apache etc.

Dependendo da sua linguagem de programação, pode haver diferentes maneiras de torná-lo rápido. Por exemplo, em Java (trate-o como pseudocódigo, por questões de brevidade; torná-lo "correto", com getters, setters, é deixado para o leitor):

interface AlgoLogObserver {
   public void observe(String message);
}

class AlgorithmXyz {   
   AlgoLogObserver observer = null;
   void runCalculation() {   
       if (observer!=null) { oberserver.observe("Hello"); }
       ...
   }   
}

...
algo = new AlgorithmXyz();
algo.observer = new ConsoleLoggingObserver();  // yes, yes make a 
                                               // setter instead, or use Ruby :-)
algo.runCalculation();

Isso é um pouco detalhado, mas a verificação de ==null deve ser o mais rápida possível.

(Observe que, no caso geral, observerprovavelmente seria uma Vector observersalternativa para permitir mais de um observador; isso também é possível e obviamente não levará a mais sobrecarga; você ainda pode colocar a otimização que definiu em observers=nullvez de ter um esvaziarVector .)

Obviamente, você implementaria diferentes tipos de observadores, dependendo do que você deseja alcançar. Você também pode inserir estatísticas de tempo etc., ou fazer outras coisas sofisticadas.


5

Como uma ligeira melhora no registro direto, crie algum tipo de objeto que modele uma execução do algoritmo. Adicione uma "etapa" a esse objeto de contêiner toda vez que seu código fizer algo interessante. No final do algoritmo, registre as etapas acumuladas no contêiner.

Isso tem algumas vantagens:

  1. Você pode registrar a execução completa como uma entrada de log, geralmente útil quando houver a possibilidade de outros threads registrarem coisas entre suas etapas de algo
  2. Na minha versão Java desta classe (chamada simplesmente "Debug"), não adiciono strings como entradas de log, mas lambdas que produzem strings. Essas lambdas são avaliadas apenas se o log real ocorrer, ou seja, se o objeto Debug descobrir que seu nível de log está ativado no momento. Dessa forma, não há sobrecarga de desempenho na construção desnecessária de seqüências de log.

EDIT: Como comentado por outros, as lambdas têm sobrecarga, portanto, você teria que fazer um benchmark para garantir que essa sobrecarga seja menor que a avaliação desnecessária do código necessário para construir a cadeia de log (as entradas de log geralmente não são literais simples, mas envolvem a obtenção de informações contextuais. objetos participantes).


2
Há, é claro, a sobrecarga de criar lambdas ...
Sergio Tulentsev

1
Sergio deixa claro, mas não explica completamente a loucura de sua lógica. A sobrecarga de desempenho na construção de seqüências de log é uma ordem de magnitude inferior à sobrecarga de desempenho na construção de lambdas. Você fez um muito pobre troca aqui
Kyeotic

2
@Tyrsius: Você tem um benchmark confiável para provar isso? (O benchmark você link para está profundamente falho, cf stackoverflow.com/questions/504103/... )
meriton

1
@ Tyrsius tudo depende da situação específica. Também posso fornecer um contra-exemplo , sem dúvida, mais relevante . Você pode ver que a versão String é uma ordem de magnitude mais lenta que a Runnable. Este caso é mais realista, porque no contexto desta pergunta você sempre desejará construir suas seqüências dinamicamente. Isso sempre exige a criação de objetos Stringbuilder, enquanto que com o Lambda eles serão criados apenas quando necessário (ou seja, quando o log estiver ativado).
jhyot

1
Lambdas têm despesas gerais, concordaram. No entanto, o benchmark publicado é completamente irrelevante nesse contexto. O registro de algoritmos geralmente envolve a avaliação de outro código que não teria sido avaliado se o registro fosse ignorado (recuperação de informações contextuais dos objetos participantes, etc.). É essa avaliação que os lambdas evitam. Mas você está certo, minha resposta acima assume que a sobrecarga lambda é menor que essa sobrecarga, algo que eu não testei consistentemente.
Cornel Masson

0

Geralmente procuro a ramificação, o que significa que procuro instruções if. Porque estes indicam que eu avalio um valor, que controlará o fluxo do algoritmo. Em cada ocorrência (cada condição), posso registrar o caminho escolhido e por que ele foi escolhido.

Então, basicamente, eu registraria os valores de entrada (estado inicial), todos os ramos escolhidos (condicionais) e os valores ao inserir o ramo escolhido (estado temporário).


1
este nem sequer tentar resolver a pergunta, sobre não ferir o desempenho em tempo real do algoritmo quando as explicações são não precisava
mosquito

Tomei a pergunta como mais geral do que isso e respondi em nível de design. Mas se isso for uma preocupação, adicione ao sinalizador condicional para definir se você deseja imprimir para registrar ou não. Defina esse sinalizador como um parâmetro ao iniciar.
Richard Tyregrim
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.