The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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