File Coverage

blib/lib/Decision/Table.pm
Criterion Covered Total %
statement 14 17 82.3
branch 0 2 0.0
condition n/a
subroutine 6 6 100.0
pod n/a
total 20 25 80.0


line stmt bran cond sub pod time code
1 7     7   91404 use 5.006;
  7         24  
  7         301  
2              
3 7     7   36 use strict;
  7         13  
  7         207  
4 7     7   36 use warnings;
  7         16  
  7         645  
5              
6             BEGIN
7             {
8             # this is the worst damned warning ever, so SHUT UP ALREADY!
9 7 0   7   176 $SIG{__WARN__} = sub { warn @_ unless $_[0] =~ /used only once/ };
  0         0  
10             }
11              
12 7     7   7312 use Class::Maker;
  7         92402  
  7         177  
13              
14 7     7   10831 use Class::Maker::Types::Array;
  0            
  0            
15              
16             use IO::Extended qw(:all);
17              
18             use Math::Matrix;
19              
20             use Tie::RefHash;
21              
22             use IO::Extended qw(:all);
23              
24             use Data::Dump;
25              
26             use Data::Iter qw(:all);
27              
28             use Generator::Perl;
29              
30             use Decision::Table::Rule;
31             use Decision::Table::Context;
32             use Decision::Table::Partial;
33             use Decision::Table::Condition;
34             use Decision::Table::Action;
35             use Decision::Table::Rules;
36             use Decision::Table::RulesArray;
37             use Decision::Table::Rule::Indexed;
38             use Decision::Table::Rule::Serial;
39             use Decision::Table::Rule::Combination::PlusMinus;
40             use Decision::Table::Diagnostic::Combinations;
41             use Decision::Table::Compact;
42              
43             package Decision::Table;
44              
45             use IO::Extended qw(:all);
46              
47             use Data::Dump qw(dump);
48              
49             our $VERSION = '0.02_02';
50            
51             sub DEBUG { 0 }
52              
53             =head1 NAME
54              
55             Decision::Table - decisions made easy
56              
57             =head1 SYNOPSIS
58              
59             use Decision::Table;
60              
61             # A "complete" Decision::Table
62              
63             my $dt = Decision::Table::Compact->new(
64            
65             conditions =>
66             [
67             'drove too fast ?',
68             'comsumed alcohol ?',
69             'Police is making controls ?',
70             ],
71            
72             actions =>
73             [
74             'charged admonishment', # 0
75             'drivers license cancellation', # 1
76             'nothing happened', # 2
77             ],
78            
79             # expectation rule table
80            
81             rules =>
82             [
83             [ 1, 0, 1 ] => [ 0 ],
84             [ 1, 1, 1 ] => [ 0, 1 ],
85             [ 0, 0, 0 ] => [ 2 ],
86             ],
87             );
88              
89             $dt->condition_find( 1, 0, 1 ); # returns ( [ 0 ] )
90              
91             $dt->condition_find( 1, 1, 1 ); # returns ( [0, 1 ] )
92              
93             $dt->decide( 0, 1, 0 ); # returns undef because no condition matches
94              
95             $dt->decide( 1, 1, 1 ); # dispatches action ( 0, 1 ) - here it just prints @$actions->[0, 1]
96              
97             $dt->to_text;
98              
99             =head1 DESCRIPTION
100              
101             When you have multiple conditions (i.e. if statements) which lead to some sort of actions, you can use
102             a decision table. It helps you to dissect, organize and analyse your problem and keeps your code very concise.
103             Especially complex and nested if/else/elsif paragraphs can be hard to mantain, understand and therefore predestinated
104             to semantic and syntactic errors. But this is not the only application for decision tables, rather it can be utilized
105             for various sorts of problems: cellular automata (Wolfram type), markov believe networks, neuronal networks and more.
106              
107             This module supports the generation of:
108              
109             - complete (totalistic)
110             - limited
111             - nested
112             - stochastic
113             - diagnosis score
114             - heuristic
115              
116             decision tables. It also has some ability to analyse your decision table and give hints about your design (<1>, which
117             also inspired me to this module and some examples).
118              
119             =head1 PROS AND CONS
120              
121             The processing of a decision table can cost some cpu-overhead. The decision table can be converted to static perl code, to
122             solve this problem. Because the static code cannot be automatically reverse engineered (not yet, but hopefully in future),
123             this would cost you some flexibility in modifying the decision table in place.
124              
125             =head1 COMPLETE VS PARTIAL TABLES
126              
127             The term "complete" decision table means that every combination of conditions is explicitly assigned some action.
128             The term "partial" decision table means that not every combination of conditions is explicitly assigned some action. These table have an additional attribute called C<else> that holds the default action (if no other was found for the given combination of conditions).
129              
130             =head1 RULE TABLES
131              
132             Two general different rule table structures are use within this package. It is handy to distinguish them and it also prevents some ambigousity.
133              
134             =head2 SERIAL RULE TABLE (Decision::Table::Rule::Serial)
135              
136             It has following data schema:
137              
138             [ condition_0_expected, condition_1_expected, condition_2_expected, ... ] => [ action_id, action_id, action_id ]
139              
140             as seen here
141              
142             rules =>
143             [
144             [ 1, 0, 1 ] => [ 0 ],
145             [ 1, 1, 1 ] => [ 0, 1 ],
146             [ 0, 0, 0 ] => [ 2 ],
147             ],
148              
149             The "expected" means 0 for false and 1 for true (of course). So that C<[ 1, 0, 1 ] =E<gt> [ 0 ]> is tested as
150              
151             condition_0_expected is expected true
152             condition_1_expected is expected false
153             condition_2_expected is expected true
154              
155             the action
156              
157             action_id 0
158              
159             is dispatched. What "dispatch" means is dependant on the action type (text displayed, code executed, ...).
160              
161             =head2 INDEX RULE TABLE (Decision::Table::Rule::Indexed)
162              
163             It uses condition indices and has following data schema:
164              
165             [ index_condition_expected_true, index_condition_expected_true, ... ] => [ action_id, action_id, action_id ]
166              
167             rules =>
168             [
169             [ 3, 4 ] => [ 0 ],
170             [ 1, 2 ] => [ 0, 1 ],
171             [ 3 ] => [ 2 ],
172             ],
173              
174             Note: It is allowed to have rundadant condition rules. That means you may have different actions with same conditions.
175              
176             =head1 METHODS AND ATTRIBUTS
177              
178             =cut
179              
180             Class::Maker::class
181             {
182             public =>
183             {
184             array => [qw( conditions actions else )],
185            
186             obj => [qw( rules )],
187              
188             bool => [qw( rules_serial )],
189             },
190             };
191              
192             sub _postinit : method
193             {
194             my $this = shift;
195              
196             for( qw( rules conditions actions ) )
197             {
198             my $what = $this->$_;
199              
200             for( Data::Iter::iter $what )
201             {
202             $_->VALUE->id( $_->COUNTER );
203             }
204             }
205              
206             return $this;
207             }
208              
209             =head2 $dt = Decision::Table->new( conditions => [], actions => [], rules => [] );
210              
211             The conditions and actions arguments take an aref with objects. The conditions take C<Decision::Table::Condition>, actions take C<Decision::Table::Action> objects.
212              
213             conditions =>
214             [
215             Decision::Table::Condition->new( text => 'drove too fast ?' ),
216             Decision::Table::Condition->new( text => 'comsumed alcohol ?' ),
217             Decision::Table::Condition->new( text => 'Police is making controls ?' ),
218             ],
219              
220             actions =>
221             [
222             Decision::Table::Action->new( text => 'charged admonishment' ), # 0
223             Decision::Table::Action->new( text => 'drivers license cancellation' ), # 1
224             Decision::Table::Action->new( text => 'nothing happened' ) # 2
225             ],
226              
227             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:
228              
229             # Decision::Table::Conditions => Decision::Table::Actions
230              
231             rules =>
232             [
233             [ 1, 0, 1 ] => [ 0 ],
234             [ 1, 1, 1 ] => [ 0, 1 ],
235             [ 0, 0, 0 ] => [ 2 ],
236             ],
237              
238             The rules hold an L<SERIAL RULE TABLE>. The left (key) array represents the boolean combination.
239              
240             [ 1, 0, 1 ]
241              
242             stands for
243              
244             $dt->condition->[0] must be true
245             $dt->condition->[1] must be false
246             $dt->condition->[2] must be true
247              
248             then action is aref to a list of actions. So
249              
250             [ 0 ]
251              
252             stands for
253              
254             $dt->action->[0]
255              
256             is taken. The action list may be redundant. The order of action is preserved during calls.
257              
258             =head2 $dt->rules_as_objs
259              
260             After the constructor was called the
261              
262             $r = $dt->rules_as_objs
263              
264             attribute holds a L<Decision::Table::Rules> object and not the aref of arefs.
265              
266             Note: The rules object turns all index/serial rule tables into tables of object references.
267              
268             [ 0, 1, 2 ] =>
269              
270             becomes
271              
272             [ $sref_cond_0, $sref_cond_1, $sref_cond_2 ] =>
273              
274             This is also true for the actions part.
275              
276             =cut
277              
278             sub rules_as_objs : method
279             {
280             my $this = shift;
281            
282             # as_objects return Decision::Table::Rule->new( condition => , action => ) objects
283              
284             my $class_rules = 'Decision::Table::Rule::Indexed';
285            
286             $class_rules ='Decision::Table::Rule::Serial' if $this->rules_serial;
287            
288             # ln STDERR "rules_as_objs: ", scalar @{ $this->rules }, " of objects $class_rules\n";
289              
290             if( $this->rules->[0]->isa( 'UNIVERSAL' ) )
291             {
292              
293             if( 0 )
294             {
295             ln STDERR "HERE WE ARE: "
296             , dump( scalar $this->rules )
297             , "Rules Array: "
298             , dump Decision::Table::RulesArray->new( rules => scalar $this->rules )->as_objects;
299             }
300            
301             return Decision::Table::RulesArray->new( rules => scalar $this->rules )->as_objects;
302             }
303              
304             return Decision::Table::Rules->new( rules => scalar $this->rules )->as_objects( $class_rules );
305             }
306              
307              
308             =head2 $dt->lookup( $type )
309              
310             This is a helper method that eases access to conditions and actions.
311              
312             $dt->lookup( 'actions', 0, 1, 2 );
313              
314             returns action 0, 1, and 2.
315              
316             =cut
317              
318             sub lookup : method
319             {
320             my $this = shift;
321            
322             my $type = shift;
323            
324             return @{ $this->$type }[ @_ ];
325             }
326              
327             =head2 $dt->condition_find( $aref_conditions )
328            
329             Finds the actions that match B<exactly> the condition part. Returns a list of actions aref (multiple because multiple conditions with different actions are allowed).
330              
331             =cut
332              
333             sub condition_find : method
334             {
335             my $this = shift;
336              
337              
338             my @result;
339            
340             foreach my $r ( @{ $this->rules_as_objs } )
341             {
342             if( $r->true_false_compare( scalar @{ $this->conditions }, @_ )->[0] == 1 )
343             {
344             if( DEBUG() )
345             {
346             ln STDERR "TRUE_FALSE_COMPARE: ", Data::Dump::dump $r->true_false_compare( scalar @{ $this->conditions }, @_ );
347             }
348              
349             push @result, $r; #scalar $r->actions;
350             }
351             }
352            
353             return @result;
354             }
355              
356             our $Tidy = 0;
357              
358             =head2 $Decision::Table::Tidy (default: 0)
359              
360             A global variable that controls if the genereated code by C<to_code> is tidied up with Perl::Tidy and printed before execution.
361              
362             =head2 $dt->to_code
363            
364             Returns perl code that represents the logic of the decision table. Returns a list or text as tested by
365              
366             wantarray ? @buffer : join "\n", @buffer;
367              
368             =cut
369            
370             sub to_code : method
371             {
372             my $this = shift;
373              
374             # for( qw(true false) )
375             # {
376             my $mtx = Math::PermMatrix->new( dimension => scalar @{ $this->conditions } );
377            
378            
379             my $g = bless [], 'Generator::Perl';
380            
381             my $code;
382            
383             $code .= 'unless( ref( $this ) ){ die "throw an exception"; }';
384            
385             # transpose through all possibilities of the dimension
386              
387             foreach my $line ( @{ $mtx->transposed } )
388             {
389             #::printfln "Matrix line %s", join( '', @$line );
390              
391             if( DEBUG() )
392             {
393             ln STDERR "CONDITION FIND ", dump( $line );
394             }
395              
396             # return rules that match the combination
397             if( my @rules = $this->condition_find( true => $line ) )
398             {
399             foreach my $rule ( @rules )
400             {
401             if( DEBUG() )
402             {
403             ln STDERR "\tFOUND: ", dump( $rule, $rule->actions_as_objs($this) );
404             }
405              
406             my @terms;
407              
408             if( DEBUG() )
409             {
410             ln STDERR "\tTRANSPOSE: ", dump( \@$line );
411             }
412            
413             foreach ( Data::Iter::iter \@$line )
414             {
415             my $pass_or_fail = $_->VALUE ? 'enclose' : 'not';
416            
417             push @terms, $g->$pass_or_fail( $this->conditions->[ $_->COUNTER ]->text );
418             }
419              
420             # lnf STDERR "\CODEGEN: TERMS %s", dump( \@terms );
421              
422             # lnf STDERR "\CODEGEN ELSIF: %s", $g->elsif( $g->and( @terms ) );
423              
424             $code .= $g->elsif( $g->and( @terms ) );
425              
426             # lnf STDERR "\CODEGEN ACTIONS: %s", join( ';', map { $_->text } $rule->actions_as_objs($this) );
427              
428             $code .= $g->block( join( ';', map { $_->text } $rule->actions_as_objs($this) ) );
429             }
430             }
431             }
432            
433             $code .= $g->else;
434            
435             $code .= $g->block( map { $_->text.';' } $this->lookup( 'actions', @{ $this->else } ) );
436            
437             # }
438              
439             if( 1 ) #$Tidy )
440             {
441             use Perl::Tidy;
442            
443             Perl::Tidy::perltidy(
444             source => \$code,
445             destination => \$code,
446             );
447             }
448              
449             return $code;
450             }
451            
452             =head2 $dt->to_code_and_execute
453              
454             Runs the decision table (via generation code by C<to_code>) and evaluating. The actions get actually executed ! The method dies when the code evaluation results in a filled C<$@>.
455              
456             my $h = Human->new( hairs => 'green', shorts => 'dirty' );
457              
458             use Data::Dumper;
459              
460             print Dumper $dt->to_code_and_execute( $h );
461              
462             =cut
463              
464             sub to_code_and_execute
465             {
466             my $this = shift;
467            
468             my $candidate = shift;
469            
470             my $code = $this->to_code();
471            
472             eval $code;
473            
474             die $@ if $@;
475            
476             if( $Tidy )
477             {
478             use Perl::Tidy;
479            
480             Perl::Tidy::perltidy(
481             source => \$code,
482             destination => \$code,
483             );
484             }
485            
486             return $candidate, $code;
487             }
488              
489             =head2 $dt->decide
490              
491             Returns a hash containing C<'pass'> | C<'fail'>. This is the overall interpretation of the conditions. This means it will have the key C<'fail'> if at least one condition failed and C<'pass'> respectively.
492              
493             =cut
494              
495             sub decide : method
496             {
497             my $this = shift;
498              
499             return { reverse %{ $this->table( @_ ) } };
500             }
501              
502             =head2 $dt->table
503              
504             Returns a complete table of the condition status. The format is
505              
506             $condition_id => action_result
507              
508             where the action result is often true | false.
509              
510             {
511             '1' => false,
512             '0' => true,
513             '2' => true
514             };
515              
516             Note that you just need to reverse the hash to know if B<one> of the tests failed.
517              
518             =cut
519              
520             sub table : method
521             {
522             my $this = shift;
523              
524             my @args = @_;
525            
526             my $result = {};
527            
528             foreach my $r ( $this->rules_as_objs )
529             {
530             foreach my $cobj ( $r->conditions_as_objs( $this ) )
531             {
532             Carp::croak "conditions must be Decision::Table::Condition::WithCode" unless $cobj->isa( 'Decision::Table::Condition::WithCode' );
533              
534             $result->{ $cobj->text } = $cobj->execute( @args );
535             }
536             }
537            
538             # unless( exists $result->{ '' } || exists $result->{ 0 } )
539             # {
540             # $r->execute( @args ) for $this->lookup( 'actions', @$actions );
541             # }
542              
543             return $result;
544             }
545              
546             =head2 $dt->to_text
547              
548             Prints a nice table which is somehow verbosly showing the rules.
549              
550             =cut
551              
552             sub rule_to_text : method
553             {
554             my $this = shift || Carp::croak;
555              
556             my $rule = shift;
557              
558             my $out = "";
559              
560             for( Data::Iter::iter [ $rule->conditions_as_objs( $this ) ] )
561             {
562             $out .= IO::Extended::sprintfl "if condition (#%d) with id=%d is as expected=[%s] : text=%S [last result: %S]\n",
563             $_->COUNTER,
564             $_->VALUE->id,
565             $_->VALUE->expected ? 'true' : 'false',
566             $_->VALUE->text,
567             defined $rule->_true_false_results->[$_->COUNTER] ? $rule->_true_false_results->[$_->COUNTER] : "undef"
568             ;
569             }
570              
571             $out .= "=="x70;
572              
573             $out .= "\n";
574            
575             $out .= IO::Extended::sprintfln "TRUE: %S",$rule->results_last_all_true?'yes':'no';
576             $out .= "then do actions: ".$_->text."\n" for $rule->actions_as_objs( $this );
577             $out .= "\n";
578              
579             return $out;
580             }
581            
582             sub to_text
583             {
584             my $this = shift;
585              
586            
587             my $out = '';
588            
589             foreach my $r ( $this->rules_as_objs )
590             {
591             # print Data::Dump::dump $r, "\n";
592              
593             $out .= $this->rule_to_text( $r );
594             }
595              
596             return $out;
597             }
598              
599             package Math::PermMatrix;
600              
601             Class::Maker::class
602             {
603             public =>
604             {
605             int => [qw( dimension )],
606             }
607             };
608              
609             sub transposed
610             {
611             my $this = shift;
612              
613             return _genmatrix( $this->dimension )->transpose;
614             }
615              
616             sub _genmatrix
617             {
618             my $rulcnt = shift;
619              
620             my @matrix;
621              
622             foreach my $counter ( reverse ( 1 .. $rulcnt ) )
623             {
624             my @re = _gencomb( $counter, 3 );
625              
626             for( my $c=0; $c<2**$rulcnt/@re; $c++ )
627             {
628             push @{ $matrix[$counter-1] }, @re;
629             }
630             }
631              
632             return Math::Matrix->new(@matrix);
633             }
634              
635             sub _gencomb
636             {
637             my ( $lauf, $wdh ) = @_;
638              
639             my $cnt;
640              
641             my @result;
642              
643             foreach ( 1..$wdh-1 )
644             {
645             my $ja = ( $cnt++ % 2 );
646              
647             foreach (1..2**($lauf-1))
648             {
649             push @result, $ja ? '0' : '1';
650             }
651             }
652              
653             return @result;
654             }
655            
656             1;
657              
658             __END__
659              
660             =head1 FAUNA AND FLORA OF DECISION TABLES
661              
662             I personally differentiate between "action-oriented" and "categorizing" decision tables.
663              
664             =head2 "action-oriented" decision tables
665              
666             Decision::Table::Conditions-dependently actions are taken to do something. In the synopsis you see an example for this:
667              
668             my $dt = Decision::Table->new(
669              
670             conditions =>
671             [
672             Decision::Table::Condition->new( text => 'drove too fast ?' ),
673             Decision::Table::Condition->new( text => 'comsumed alcohol ?' ),
674             Decision::Table::Condition->new( text => 'Police is making controls ?' ),
675             ],
676              
677             actions =>
678             [
679             Decision::Table::Action->new( text => 'charged admonishment' ), # 0
680             Decision::Table::Action->new( text => 'drivers license cancellation' ), # 1
681             Decision::Table::Action->new( text => 'nothing happened' ) # 2
682             ],
683             # Decision::Table::Conditions => Decision::Table::Actions
684              
685             rules =>
686             [
687             [ 1, 0, 1 ] => [ 0 ],
688             [ 1, 1, 1 ] => [ 0, 1 ],
689             [ 0, 0, 0 ] => [ 2 ],
690             ],
691             );
692              
693             $dt->analyse();
694              
695             =head2 "categorizing" decision tables
696              
697             Here we are making decisions about categorizing (classifying) something. The "Decision::Table::Actions" are mainly more
698             annotating something.
699              
700             my $dtp = Decision::Table->new(
701              
702             conditions =>
703             [
704             Decision::Table::Condition->new( text => '$this->hairs eq "green"' ),
705             Decision::Table::Condition->new( text => '$this->income > 10*1000' ),
706             Decision::Table::Condition->new( text => '$this->shorts eq "dirty"' ),
707             ],
708              
709             actions =>
710             [
711             Decision::Table::Action->new( text => '$this->name( "freak" );' ),
712             Decision::Table::Action->new( text => '$this->name( "dumb" )' ),
713             Decision::Table::Action->new( text => '$this->name( "geek" )' ),
714             Decision::Table::Action->new( text => '$this->name( "<unknown>" )' ),
715             ],
716              
717             rules =>
718             [
719             [ 1, 1, 1 ] => [ 2, 1 ],
720             [ 0, 0, 1 ] => [ 1 ],
721             [ 1, 0, 1 ] => [ 0 ],
722             [ 0, 1, 1 ] => [ 0 ],
723             ],
724              
725             else => [ 3 ],
726             );
727              
728             =head1 EXAMPLE "Decision::Table::Action-oriented" decisions
729              
730             my $dt = Decision::Table::Partial->new(
731              
732             conditions =>
733             [
734             Decision::Table::Condition->new( text => 'schnell gefahren ?' ),
735             Decision::Table::Condition->new( text => 'Alkohol getrunken ?' ),
736             Decision::Table::Condition->new( text => 'kontrolliert Polizei ?' ),
737             ],
738            
739             actions =>
740             [
741             Decision::Table::Action->new( text => 'gebuehrenpflichtige Verwarnung' ),
742             Decision::Table::Action->new( text => 'Fuehrerschein Entzug' ),
743             Decision::Table::Action->new( text => 'nichts geschieht' )
744             ],
745            
746             rules =>
747             [
748             [ 0, 1 ] => [ 0 ],
749             [ 1, 2 ] => [ 0, 1 ],
750             [ 0, 1, 2 ] => [ 2 ],
751             ],
752             );
753              
754             $dt->to_text();
755              
756             =head1 EXAMPLE "categorizing" decisions
757              
758             my $dtp = Decision::Table::Partial->new(
759            
760             conditions =>
761             [
762             Decision::Table::Condition::WithCode->new( text => '$this->hairs eq "green"', cref => sub { $_[0]->hairs eq "green" } ),
763             Decision::Table::Condition::WithCode->new( text => '$this->income > 10*1000', cref => sub { $_[0]->income > 10*1000 } ),
764             Decision::Table::Condition::WithCode->new( text => '$this->shorts eq "dirty"', cref => sub { $_[0]->shorts eq "dirty" } ),
765             ],
766            
767             actions =>
768             [
769             Decision::Table::Action::WithCode->new( text => '$this->name( "freak" )', cref => sub { $_[0]->name( "freak" ) } ),
770             Decision::Table::Action::WithCode->new( text => '$this->name( "dumb" )', cref => sub { $_[0]->name( "dumb" ) } ),
771             Decision::Table::Action::WithCode->new( text => '$this->name( "geek" )', cref => sub { $_[0]->name( "geek" ) } ),
772             Decision::Table::Action::WithCode->new( text => '$this->name( "unknown" )', cref => sub { $_[0]->name( "unknown" ) } ),
773             ],
774            
775             rules =>
776             [
777             [ 0, 1, 2 ] => [ 2, 1 ],
778             [ 0, 2 ] => [ 1 ],
779             [ 0, 1 ] => [ 0 ],
780             [ 1, 2 ] => [ 0 ],
781             ],
782            
783             else => [ 3 ],
784             );
785              
786             class 'Human',
787             {
788             public =>
789             {
790             string => [qw( hairs name shorts)],
791            
792             integer => [qw( income )],
793             },
794            
795             default =>
796             {
797             income => 0,
798             },
799             };
800              
801             my $this = Human->new( hairs => 'green', shorts => 'dirty' );
802            
803             print Dumper [ $dtp->decide( $this ) ];
804              
805             $this->income( 20*1000 );
806            
807             print Dumper [ $dtp->decide( $this ) ];
808              
809             =head2 EXPORT
810              
811             None by default.
812              
813             =head1 AUTHOR
814              
815             Murat Ünalan, E<lt>muenalan@cpan.orgE<gt>
816              
817             =head1 SEE ALSO
818              
819             L<Decision::Table::Diagnostic>, L<Decision::Table::Wheighted>
820              
821             =head1 REFERENCES
822              
823             <1> Book (German): M. Rammè, "Entscheidungstabellen: Entscheiden mit System" (Prentice Hall))