Creazione di un enorme albero decisionale

5

Devo scrivere un correlatore di eventi. Una parte fondamentale del sistema sarà un albero decisionale che riconosce l'origine dell'errore basandosi su stati registrati e file di registro.

Spesso molti incidenti differiscono con dettagli minori e molte decisioni prese saranno basate su dati confusi, incompleti o inaffidabili, ma la maggior parte di queste decisioni può essere scritta come funzioni binarie di logica.

Il fatto è che ce ne saranno molti. Mi aspetto almeno 100 nodi nell'albero decisionale, e potrei sottovalutare il valore di un ordine di grandezza. E soprattutto, quando emergono nuovi schemi imprevisti, i fallimenti imprevisti si verificano lasciando nuove tracce o con l'estensione del sistema diventano valide le nuove modalità di errore, l'albero delle decisioni dovrà essere mantenuto.

(e non sarà sempre una struttura ad albero pura - alcuni difetti dello stesso effetto hanno due o più modi di apparire, alcuni di questi si diramano in diverse modalità, ad esempio l'evento A significa fallimento X, l'evento B significa: controlla l'evento C. Se C è vero, è anche l'errore X, ma in caso contrario, si tratta di un errore Y. Sebbene possa sempre normalizzarlo in X1 e X2, che sono tecnicamente identici ma diversi dal punto di vista dell'albero.) Inoltre, il l'albero sarà spesso abbastanza profondamente annidato quindi temo che una semplice serie di if annidati svanirà rapidamente fuori controllo.

Ora la mia domanda è: come archiviare / scrivere / costruire quell'albero in modo che possa essere compilato in qualcosa che la macchina può digerire, ma comunque mantenibile per gli sviluppatori?

Si noti che questo è per un sistema embedded, quindi le soluzioni pesanti come JBoss non si adattano veramente a meno che non compaiano solo sul lato del compilatore, e il sistema finale esegue un set di regole compilato in qualcosa di molto più amichevole.

(il sistema è scritto in C ++, fa anche ampio uso di JSON, e funzionerà su una CPU ARM9 se questo è di qualche aiuto.)

    
posta SF. 09.01.2013 - 14:02
fonte

2 risposte

4

Ecco un'idea per tale DSL. Supponiamo tu abbia 6 variabili di input (i tuoi "eventi"). Supponiamo che quelle variabili siano binarie (l'idea può essere generalizzata a variabili non binarie). Ora il tuo DSL dovrebbe contenere istruzioni come

(0|*|1|0|1|1) => Action_A()
(0|0|1|0|0|*) => Action_B()
(1|1|1|0|*|*) => Action_C()
default       => ActionDefault()

Ogni riga contiene una precondizione (la parte lasciata a => ), che dice quale variabile di input deve avere quale valore. La parte destra per => è l'azione da applicare. Un "*" significa che la variabile di input corrispondente può essere 0 o 1. La semantica può essere confrontata con la meccanica di corrispondenza del modello di awk, perl o xslt.

In che modo ti aiuta? Bene, ora puoi fare due cose

  • scrivi un semplice validatore che verifica che tutte le tue precondizioni siano mutualmente esclusive. Se non vuoi o hai bisogno di un'azione "predefinita", puoi farlo e lasciare che il tuo validatore verifichi ulteriormente che non hai dimenticato un caso.

  • scrivi un generatore di codice che traduce quella DSL in codice C ++, che potrebbe quindi assomigliare a questa:

    if(event_1==0 && event_3==1 && event_4==0 && event_5==1 && event_6==1)
        Action_A();
    

Il validatore ti aiuterà a mantenere le cose manutenibili, e il codice C ++ generato dovrebbe essere compilato in qualcosa di "facile da usare", come richiesto.

Se le tue variabili di input non sono binarie (100 nodi con una profondità di 2-3 decisioni non è possibile solo tramite input binari), dovrai estendere tale DSL, dovrebbe essere semplice come farlo. Ad esempio, utilizza (0,1,4|*|*) per una precondizione (event_1==0 || event_1==1||event_1==4) .

EDIT: se hai molte variabili di input e il requisito per controllarne solo alcune, questo potrebbe essere adattato usando "named parameters", ad esempio

    (var_1==[0,1,4]|var_3==0|var_25==[1-10]) => Action_A()

Naturalmente, potresti anche codificarlo direttamente in C ++, che non diventerà molto più lungo, ma il vantaggio di usare un DSL piccolo e formale è che ora hai la possibilità di scrivere un validatore per questo.

    
risposta data 09.01.2013 - 17:39
fonte
1

Non sono uno sviluppatore C ++, ma penso che le dichiarazioni delle regole DSL possano essere implementate esclusivamente in C ++.

Innanzitutto, esiste un oggetto richiesta, contenente tutti i parametri di input (ad esempio una mappa). Questo oggetto può anche memorizzare nella cache una condizione già verificata, una traccia di controllo delle regole ed eventualmente alcuni dati intermedi.

class Request {
public:
    template<T> Value<T> get(Parameter<T>); // Returns the given parameter value.
    /* Returns a 3-state status for the given condition:
       - not checked yet
       - condition succeed
       - condition failed */
    ConditionCheckStatus getCheckStatus(Condition);
    void setCheckStatus(Condition, bool); // Caches the condition check status.
}

Tale oggetto dovrebbe essere costruito una volta per richiesta e dovrebbe essere passato a una regola più in alto.

È possibile esprimere una condizione utilizzando una sottoclasse di classe Condition . Il Condition controlla se è già stato valutato. In caso contrario, valuta se stesso e memorizza nella cache lo stato del controllo di valutazione. Per creare condizioni complesse, è opportuno sostituire gli operatori & , | e ! :

class Condition {
public:
    inline bool check(Request &request) {
        switch (request->getCheckStatus(this)) {
        case COND_SUCCESS:
            return true;
        case COND_FAILURE:
            return false;
        }
        bool result = this->checkCondition(request);
        request.setCheckStatus(this, result);
        return result;
    }
    inline Condition &operator & (Condition &other) {return *new AndCondition(*this, other)}
    inline Condition &operator | (Condition &other) {return *new OrCondition(*this, other)}
    inline Condition &operator ! () {return *new NotCondition(*this)}
protected:
    virtual bool checkCondition(Request &) = 0;
}

Una condizione concreta può essere dichiarata come costante globale e può essere combinata con operatori logici per costruirne una più complessa.

Il modello Parameter può essere utilizzato per accedere a parametri Request in un modo sicuro per tipo. Un parametro concreto può essere dichiarato come costante globale. I parametri possono anche eseguire l'override degli operatori per costruire Condition s in questo modo:

// Declare a "counter" input parameter.
const Parameter<int> Counter;

// Construct a "more than once" condition.    
const Condition &MoreThanOnce = Counter > 1;

Un set di regole può essere dichiarato usando Rule oggetti (anche dichiarati come costanti globali).

const Rule FailIfMoreThanOnce(
    MoreThanOnce, /* The condition */
    Failure,      /* The rule invoked if condition succeed */
    OtherRule     /* The rule invoked if condition failed */);


// ... Other rule definitions

const Rule TopRule(SomeCondition1 | SomeCondition2, SomeRule1, SomeRule2);

Una valutazione dell'albero decisionale sarebbe simile a questa:

Request request;
// Fill the request.
bool result = TopRule.check(request);
// Handle the result.

Tieni presente che tutti Parameter s, Condition s e Rule s sono senza stato. L'unico oggetto mutabile è Request , che viene costruito una volta per richiesta e raccoglie i dati valutati.

Le regole, i parametri e le condizioni possono avere nomi significativi, possono essere costruiti usando operatori. Non c'è quasi nessun sovraccarico di sintassi per la loro dichiarazione. Le dichiarazioni ancora più brevi possono essere fatte usando le macro.

    
risposta data 10.01.2013 - 08:57
fonte

Leggi altre domande sui tag