Modello per algoritmo che fornisce una spiegazione di come si arriva a una soluzione quando necessario


14

Lo scenario seguente mi è successo più volte.

Ho programmato un algoritmo che risolve un certo problema. Funziona bene e trova le soluzioni giuste. Ora, voglio avere un'opzione per dire all'algoritmo "scrivi una spiegazione completa di come sei arrivato alla soluzione". Il mio obiettivo è essere in grado di utilizzare l'algoritmo in dimostrazioni online, lezioni di esercitazione, ecc. Voglio ancora avere un'opzione per eseguire l'algoritmo in tempo reale, senza le spiegazioni. Qual è un buon modello di design da usare?

ESEMPIO: Supponiamo che io attui questo metodo per trovare il massimo comun divisore . L'attuale metodo implementato restituisce la risposta corretta, ma senza spiegazioni. Voglio avere un'opzione per il metodo per spiegare le sue azioni, come:

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.

L'output deve essere restituito in modo tale da poter essere facilmente visualizzato sia in console che in applicazioni basate sul Web.

Qual è un buon modello per fornire spiegazioni quando necessario, senza danneggiare le prestazioni in tempo reale dell'algoritmo quando non sono necessarie spiegazioni?

Risposte:


50

Lo "schema" che stai cercando si chiama "logging", rendi le dichiarazioni di logging così dettagliate come ti servono. Utilizzando un framework di registrazione decente dovresti essere in grado di accenderlo e spegnerlo in fase di esecuzione, fornire livelli di verbosità diversi o personalizzare l'output per scopi diversi (come web vs. console).

Se ciò ha un notevole impatto sulle prestazioni (anche se la registrazione è disattivata) probabilmente dipenderà dalla lingua, dal framework e dal numero di istruzioni di registrazione necessarie nel caso specifico. Nei linguaggi compilati, se questo diventa davvero un problema, è possibile fornire uno switch del compilatore per creare una "variante di registrazione" e una "variante non di registrazione" del codice. Tuttavia, consiglio vivamente di non ottimizzare "per ogni evenienza", senza prima misurare.


2
Sebbene non siano qualcosa che si accende e si spegne come la registrazione, sembra che i commenti e il codice di auto-documentazione debbano almeno ricevere una menzione d'onore in una domanda su un "algoritmo che si spiega da solo".
candied_orange,

9
@CandiedOrange la domanda richiede specificamente una "spiegazione" con valori di runtime reali inclusi in essa. I commenti non aiuteranno molto in quel caso.
Metacubato

@metacubed oh andiamo. Non ho detto che fosse un'alternativa alla registrazione. Guarda il titolo della domanda e pensa al traffico che arriva qui.
candied_orange,

4
@CandiedOrange: Penso che il titolo della domanda sia fuorviante, hai ragione che potrebbe essere interpretato in quel modo, ma non è quello che l'OP chiede. Ma mi lascio correggere, modificherò il titolo.
Doc Brown,

1
Si noti che qualcosa come il treelog è specificamente progettato per produrre output che spiegano calcoli complessi producendo un registro completo delle chiamate di funzione.
Boris the Spider,

7

Un buon modello è Observer. https://en.wikipedia.org/wiki/Observer_pattern

Nel tuo algoritmo, in ogni punto in cui desideri produrre qualcosa, avvisi alcuni osservatori. Decidono quindi cosa fare, sia per l'output del testo sulla console, sia per inviarlo al motore HTML / Apache ecc.

A seconda del linguaggio di programmazione, potrebbero esserci diversi modi per renderlo veloce. Ad esempio, in Java (trattalo come pseudocodice, per brevità; rendendolo "corretto", con getter, setter, è lasciato al lettore):

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();

Questo è leggermente dettagliato, ma il controllo ==nulldovrebbe essere il più veloce possibile.

(Si noti che nel caso generale, observersarebbe probabilmente un Vector observersinvece consentire più di un osservatore; anche questo è ovviamente possibile e non porterà a maggiori costi generali; è comunque possibile inserire l'ottimizzazione impostata observers=nullinvece di avere un vuoto Vector.)

Ovviamente, implementeresti diversi tipi di osservatori a seconda di ciò che vuoi ottenere. Puoi anche inserire statistiche sui tempi ecc. Lì, o fare altre cose fantasiose.


5

Come un leggero miglioramento della registrazione diretta, creare una sorta di oggetto che modella un'esecuzione dell'algoritmo. Aggiungi un "passaggio" a questo oggetto contenitore ogni volta che il tuo codice fa qualcosa di interessante. Alla fine dell'algoritmo, registra i passaggi accumulati dal contenitore.

Questo ha alcuni vantaggi:

  1. È possibile registrare l'intera esecuzione come una sola voce di registro, spesso utile quando c'è la possibilità che altri thread registrino cose tra i tuoi passi algo
  2. Nella mia versione Java di questa classe (chiamata semplicemente "Debug"), non aggiungo stringhe come voci di registro, ma lambdas che producono stringhe. Questi lambda vengono valutati solo se avrà luogo la registrazione effettiva, ovvero se l'oggetto Debug rileva che il suo livello di registro è attualmente attivato. In questo modo, non vi è alcun sovraccarico prestazionale nel costruire inutilmente stringhe di registro.

EDIT: come commentato da altri, i lambda hanno un overhead, quindi dovresti fare un benchmark per assicurarti che questo overhead sia inferiore alla valutazione non necessaria del codice richiesta per costruire la stringa di registro (le voci di registro spesso non sono semplici letterali, ma implicano ottenere informazioni contestuali da oggetti partecipanti).


2
C'è, ovviamente, il sovraccarico di creare lambdas ...
Sergio Tulentsev,

1
Sergio fa luce, ma non spiega completamente la follia della tua logica. Il sovraccarico prestazionale della costruzione di stringhe di registro è un ordine di grandezza inferiore rispetto al sovraccarico prestazionale della costruzione di lambda. Hai fatto un pessimo compromesso qui
Kyeotic

2
@Tyrsius: hai un benchmark affidabile che lo dimostra? (Il benchmark si collega a è profondamente sbagliata, cf stackoverflow.com/questions/504103/... )
Meriton

1
@Tyrsius tutto dipende dalla situazione specifica. Posso anche darvi un controesempio , probabilmente, più pertinente . Puoi vedere che la versione String è un ordine di grandezza più lento di Runnable. Questo caso è più realistico, perché nel contesto di questa domanda vorrai sempre costruire le tue stringhe in modo dinamico. Ciò richiede sempre la creazione di oggetti Stringbuilder, mentre con Lambda verranno creati solo quando necessario (cioè quando la registrazione è attiva).
jhyot,

1
Lambdas ha spese generali, d'accordo. Tuttavia, il benchmark pubblicato è completamente irrilevante in questo contesto. La registrazione degli algoritmi comporta spesso la valutazione di altro codice che non sarebbe stato valutato se la registrazione fosse stata saltata (recupero di informazioni contestuali da oggetti partecipanti, ecc.). È questa valutazione che evitano le lambda. Ma hai ragione, la mia risposta sopra presuppone che il sovraccarico lambda sia inferiore a questo sovraccarico, qualcosa che non ho costantemente testato.
Cornel Masson,

0

Di solito cerco la ramificazione, nel senso che cerco dichiarazioni if. Perché questi indicano che valuto un valore, che controllerà il flusso dell'algoritmo. In ciascuna di tali occasioni (ogni condizione) posso quindi registrare il percorso scelto e perché è stato scelto.

Quindi fondamentalmente registrerei i valori di entrata (stato iniziale), ogni ramo scelto (condizionali) e i valori quando inserisco il ramo scelto (stato temporaneo).


1
questo non tenta nemmeno di rispondere alla domanda posta, di non danneggiare le prestazioni in tempo reale dell'algoritmo quando non sono necessarie spiegazioni
moscerino

Ho preso la domanda come più generale di così e ho risposto a livello di progettazione. Ma se questo è un problema, aggiungi al condizionale un flag da impostare se vuoi stampare per accedere o meno. Imposta questo flag come parametro all'avvio.
Richard Tyregrim,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.