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)) |