Decision::Table - decisions made easy
use Decision::Table; # A "complete" Decision::Table my $dt = Decision::Table::Compact->new( conditions => [ 'drove too fast ?', 'comsumed alcohol ?', 'Police is making controls ?', ], actions => [ 'charged admonishment', # 0 'drivers license cancellation', # 1 'nothing happened', # 2 ], # expectation rule table rules => [ [ 1, 0, 1 ] => [ 0 ], [ 1, 1, 1 ] => [ 0, 1 ], [ 0, 0, 0 ] => [ 2 ], ], ); $dt->condition_find( 1, 0, 1 ); # returns ( [ 0 ] ) $dt->condition_find( 1, 1, 1 ); # returns ( [0, 1 ] ) $dt->decide( 0, 1, 0 ); # returns undef because no condition matches $dt->decide( 1, 1, 1 ); # dispatches action ( 0, 1 ) - here it just prints @$actions->[0, 1] $dt->to_text;
When you have multiple conditions (i.e. if statements) which lead to some sort of actions, you can use a decision table. It helps you to dissect, organize and analyse your problem and keeps your code very concise. Especially complex and nested if/else/elsif paragraphs can be hard to mantain, understand and therefore predestinated to semantic and syntactic errors. But this is not the only application for decision tables, rather it can be utilized for various sorts of problems: cellular automata (Wolfram type), markov believe networks, neuronal networks and more.
This module supports the generation of:
- complete (totalistic) - limited - nested - stochastic - diagnosis score - heuristic
decision tables. It also has some ability to analyse your decision table and give hints about your design (<1>, which also inspired me to this module and some examples).
The processing of a decision table can cost some cpu-overhead. The decision table can be converted to static perl code, to solve this problem. Because the static code cannot be automatically reverse engineered (not yet, but hopefully in future), this would cost you some flexibility in modifying the decision table in place.
The term "complete" decision table means that every combination of conditions is explicitly assigned some action. The term "partial" decision table means that not every combination of conditions is explicitly assigned some action. These table have an additional attribute called else that holds the default action (if no other was found for the given combination of conditions).
else
Two general different rule table structures are use within this package. It is handy to distinguish them and it also prevents some ambigousity.
It has following data schema:
[ condition_0_expected, condition_1_expected, condition_2_expected, ... ] => [ action_id, action_id, action_id ]
as seen here
rules => [ [ 1, 0, 1 ] => [ 0 ], [ 1, 1, 1 ] => [ 0, 1 ], [ 0, 0, 0 ] => [ 2 ], ],
The "expected" means 0 for false and 1 for true (of course). So that [ 1, 0, 1 ] => [ 0 ] is tested as
[ 1, 0, 1 ] => [ 0 ]
condition_0_expected is expected true condition_1_expected is expected false condition_2_expected is expected true
the action
action_id 0
is dispatched. What "dispatch" means is dependant on the action type (text displayed, code executed, ...).
It uses condition indices and has following data schema:
[ index_condition_expected_true, index_condition_expected_true, ... ] => [ action_id, action_id, action_id ] rules => [ [ 3, 4 ] => [ 0 ], [ 1, 2 ] => [ 0, 1 ], [ 3 ] => [ 2 ], ],
Note: It is allowed to have rundadant condition rules. That means you may have different actions with same conditions.
The conditions and actions arguments take an aref with objects. The conditions take Decision::Table::Condition, actions take Decision::Table::Action objects.
Decision::Table::Condition
Decision::Table::Action
conditions => [ Decision::Table::Condition->new( text => 'drove too fast ?' ), Decision::Table::Condition->new( text => 'comsumed alcohol ?' ), Decision::Table::Condition->new( text => 'Police is making controls ?' ), ], actions => [ Decision::Table::Action->new( text => 'charged admonishment' ), # 0 Decision::Table::Action->new( text => 'drivers license cancellation' ), # 1 Decision::Table::Action->new( text => 'nothing happened' ) # 2 ],
The rules arguments takes an aref for boolean algebra. It is like a hash. Which actions should be taken when which conditions are true? It has following structure:
# Decision::Table::Conditions => Decision::Table::Actions rules => [ [ 1, 0, 1 ] => [ 0 ], [ 1, 1, 1 ] => [ 0, 1 ], [ 0, 0, 0 ] => [ 2 ], ],
The rules hold an "SERIAL RULE TABLE". The left (key) array represents the boolean combination.
[ 1, 0, 1 ]
stands for
$dt->condition->[0] must be true $dt->condition->[1] must be false $dt->condition->[2] must be true
then action is aref to a list of actions. So
[ 0 ]
$dt->action->[0]
is taken. The action list may be redundant. The order of action is preserved during calls.
After the constructor was called the
$r = $dt->rules_as_objs
attribute holds a Decision::Table::Rules object and not the aref of arefs.
Note: The rules object turns all index/serial rule tables into tables of object references.
[ 0, 1, 2 ] =>
becomes
[ $sref_cond_0, $sref_cond_1, $sref_cond_2 ] =>
This is also true for the actions part.
This is a helper method that eases access to conditions and actions.
$dt->lookup( 'actions', 0, 1, 2 );
returns action 0, 1, and 2.
Finds the actions that match exactly the condition part. Returns a list of actions aref (multiple because multiple conditions with different actions are allowed).
A global variable that controls if the genereated code by to_code is tidied up with Perl::Tidy and printed before execution.
to_code
Returns perl code that represents the logic of the decision table. Returns a list or text as tested by
wantarray ? @buffer : join "\n", @buffer;
Runs the decision table (via generation code by to_code) and evaluating. The actions get actually executed ! The method dies when the code evaluation results in a filled $@.
$@
my $h = Human->new( hairs => 'green', shorts => 'dirty' ); use Data::Dumper; print Dumper $dt->to_code_and_execute( $h );
Returns a hash containing 'pass' | 'fail'. This is the overall interpretation of the conditions. This means it will have the key 'fail' if at least one condition failed and 'pass' respectively.
'pass'
'fail'
Returns a complete table of the condition status. The format is
$condition_id => action_result
where the action result is often true | false.
{ '1' => false, '0' => true, '2' => true };
Note that you just need to reverse the hash to know if one of the tests failed.
Prints a nice table which is somehow verbosly showing the rules.
I personally differentiate between "action-oriented" and "categorizing" decision tables.
Decision::Table::Conditions-dependently actions are taken to do something. In the synopsis you see an example for this:
my $dt = Decision::Table->new( conditions => [ Decision::Table::Condition->new( text => 'drove too fast ?' ), Decision::Table::Condition->new( text => 'comsumed alcohol ?' ), Decision::Table::Condition->new( text => 'Police is making controls ?' ), ], actions => [ Decision::Table::Action->new( text => 'charged admonishment' ), # 0 Decision::Table::Action->new( text => 'drivers license cancellation' ), # 1 Decision::Table::Action->new( text => 'nothing happened' ) # 2 ], # Decision::Table::Conditions => Decision::Table::Actions rules => [ [ 1, 0, 1 ] => [ 0 ], [ 1, 1, 1 ] => [ 0, 1 ], [ 0, 0, 0 ] => [ 2 ], ], ); $dt->analyse();
Here we are making decisions about categorizing (classifying) something. The "Decision::Table::Actions" are mainly more annotating something.
my $dtp = Decision::Table->new( conditions => [ Decision::Table::Condition->new( text => '$this->hairs eq "green"' ), Decision::Table::Condition->new( text => '$this->income > 10*1000' ), Decision::Table::Condition->new( text => '$this->shorts eq "dirty"' ), ], actions => [ Decision::Table::Action->new( text => '$this->name( "freak" );' ), Decision::Table::Action->new( text => '$this->name( "dumb" )' ), Decision::Table::Action->new( text => '$this->name( "geek" )' ), Decision::Table::Action->new( text => '$this->name( "<unknown>" )' ), ], rules => [ [ 1, 1, 1 ] => [ 2, 1 ], [ 0, 0, 1 ] => [ 1 ], [ 1, 0, 1 ] => [ 0 ], [ 0, 1, 1 ] => [ 0 ], ], else => [ 3 ], );
my $dt = Decision::Table::Partial->new( conditions => [ Decision::Table::Condition->new( text => 'schnell gefahren ?' ), Decision::Table::Condition->new( text => 'Alkohol getrunken ?' ), Decision::Table::Condition->new( text => 'kontrolliert Polizei ?' ), ], actions => [ Decision::Table::Action->new( text => 'gebuehrenpflichtige Verwarnung' ), Decision::Table::Action->new( text => 'Fuehrerschein Entzug' ), Decision::Table::Action->new( text => 'nichts geschieht' ) ], rules => [ [ 0, 1 ] => [ 0 ], [ 1, 2 ] => [ 0, 1 ], [ 0, 1, 2 ] => [ 2 ], ], ); $dt->to_text();
my $dtp = Decision::Table::Partial->new( conditions => [ Decision::Table::Condition::WithCode->new( text => '$this->hairs eq "green"', cref => sub { $_[0]->hairs eq "green" } ), Decision::Table::Condition::WithCode->new( text => '$this->income > 10*1000', cref => sub { $_[0]->income > 10*1000 } ), Decision::Table::Condition::WithCode->new( text => '$this->shorts eq "dirty"', cref => sub { $_[0]->shorts eq "dirty" } ), ], actions => [ Decision::Table::Action::WithCode->new( text => '$this->name( "freak" )', cref => sub { $_[0]->name( "freak" ) } ), Decision::Table::Action::WithCode->new( text => '$this->name( "dumb" )', cref => sub { $_[0]->name( "dumb" ) } ), Decision::Table::Action::WithCode->new( text => '$this->name( "geek" )', cref => sub { $_[0]->name( "geek" ) } ), Decision::Table::Action::WithCode->new( text => '$this->name( "unknown" )', cref => sub { $_[0]->name( "unknown" ) } ), ], rules => [ [ 0, 1, 2 ] => [ 2, 1 ], [ 0, 2 ] => [ 1 ], [ 0, 1 ] => [ 0 ], [ 1, 2 ] => [ 0 ], ], else => [ 3 ], ); class 'Human', { public => { string => [qw( hairs name shorts)], integer => [qw( income )], }, default => { income => 0, }, }; my $this = Human->new( hairs => 'green', shorts => 'dirty' ); print Dumper [ $dtp->decide( $this ) ]; $this->income( 20*1000 ); print Dumper [ $dtp->decide( $this ) ];
None by default.
Murat Ünalan, <muenalan@cpan.org>
Decision::Table::Diagnostic, Decision::Table::Wheighted
<1> Book (German): M. Rammè, "Entscheidungstabellen: Entscheiden mit System" (Prentice Hall))
1 POD Error
The following errors were encountered while parsing the POD:
Non-ASCII character seen before =encoding in 'Ünalan,'. Assuming CP1252
To install Decision::Table, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Decision::Table
CPAN shell
perl -MCPAN -e shell install Decision::Table
For more information on module installation, please visit the detailed CPAN module installation guide.