NAME
Decision::Table - decisions made easy
SYNOPSIS
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;
DESCRIPTION
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).
PROS AND CONS
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.
COMPLETE VS PARTIAL TABLES
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).
RULE TABLES
Two general different rule table structures are use within this package. It is handy to distinguish them and it also prevents some ambigousity.
SERIAL RULE TABLE (Decision::Table::Rule::Serial)
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
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, ...).
INDEX RULE TABLE (Decision::Table::Rule::Indexed)
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.
METHODS AND ATTRIBUTS
$dt = Decision::Table->new( conditions => [], actions => [], rules => [] );
The conditions and actions arguments take an aref with objects. The conditions take Decision::Table::Condition
, actions take Decision::Table::Action
objects.
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 ]
stands for
$dt->action->[0]
is taken. The action list may be redundant. The order of action is preserved during calls.
$dt->rules_as_objs
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.
$dt->lookup( $type )
This is a helper method that eases access to conditions and actions.
$dt->lookup( 'actions', 0, 1, 2 );
returns action 0, 1, and 2.
$dt->condition_find( $aref_conditions )
Finds the actions that match exactly the condition part. Returns a list of actions aref (multiple because multiple conditions with different actions are allowed).
$Decision::Table::Tidy (default: 0)
A global variable that controls if the genereated code by to_code
is tidied up with Perl::Tidy and printed before execution.
$dt->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;
$dt->to_code_and_execute
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 );
$dt->decide
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.
$dt->table
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.
$dt->to_text
Prints a nice table which is somehow verbosly showing the rules.
FAUNA AND FLORA OF DECISION TABLES
I personally differentiate between "action-oriented" and "categorizing" decision tables.
"action-oriented" 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();
"categorizing" decision tables
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 ],
);
EXAMPLE "Decision::Table::Action-oriented" decisions
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();
EXAMPLE "categorizing" decisions
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 ) ];
EXPORT
None by default.
AUTHOR
Murat Ünalan, <muenalan@cpan.org>
SEE ALSO
Decision::Table::Diagnostic, Decision::Table::Wheighted
REFERENCES
<1> Book (German): M. Rammè, "Entscheidungstabellen: Entscheiden mit System" (Prentice Hall))
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 815:
Non-ASCII character seen before =encoding in 'Ünalan,'. Assuming CP1252