File Coverage

blib/lib/FLAT/DFA.pm
Criterion Covered Total %
statement 224 307 72.9
branch 46 68 67.6
condition 6 9 66.6
subroutine 25 31 80.6
pod 7 19 36.8
total 308 434 70.9


line stmt bran cond sub pod time code
1             package FLAT::DFA;
2              
3 6     6   35 use strict;
  6         11  
  6         186  
4 6     6   28 use warnings;
  6         10  
  6         194  
5 6     6   26 use parent qw(FLAT::NFA);
  6         10  
  6         31  
6 6     6   290 use Storable qw(dclone);
  6         14  
  6         308  
7 6     6   2758 use FLAT::DFA::Minimal;
  6         13  
  6         168  
8 6     6   34 use Carp;
  6         10  
  6         16522  
9              
10             sub set_starting {
11 292     292 1 567 my $self = shift;
12 292         1071 $self->SUPER::set_starting(@_);
13              
14 292         939 my $num = () = $self->get_starting;
15 292 50       952 confess "DFA must have exactly one starting state"
16             if $num != 1;
17             }
18              
19             sub complement {
20 6     6 1 36 my $self = $_[0]->clone;
21              
22 6         39 for my $s ( $self->get_states ) {
23 80 100       133 $self->is_accepting($s)
24             ? $self->unset_accepting($s)
25             : $self->set_accepting($s);
26             }
27              
28 6         28 return $self;
29             }
30              
31 1520     1520   3603 sub _TUPLE_ID { join "\0", @_ }
32              
33             sub _uniq {
34 6     6   13 my %seen;
35 6         13 grep { !$seen{$_}++ } @_;
  72         117  
36             }
37              
38             ## this method still needs more work..
39             sub intersect {
40 6     6 1 19 my @dfas = map { $_->as_dfa } @_;
  12         55  
41              
42 6         29 my $return = FLAT::DFA->new;
43 6         9 my %newstates;
44 6         19 my @alpha = _uniq( map { $_->alphabet } @dfas );
  12         32  
45              
46 6         40 $_->_extend_alphabet(@alpha) for @dfas;
47              
48 6         12 my @start = map { $_->get_starting } @dfas;
  12         31  
49 6         25 my $start = $newstates{ _TUPLE_ID(@start) } = $return->add_states(1);
50 6         27 $return->set_starting($start);
51             $return->set_accepting($start)
52 6 50       23 if !grep { !$dfas[$_]->is_accepting( $start[$_] ) } 0 .. $#dfas;
  12         26  
53              
54 6         19 my @queue = ( \@start );
55 6         17 while (@queue) {
56 80         113 my @tuple = @{ shift @queue };
  80         182  
57              
58 80         161 for my $char (@alpha) {
59 480         816 my @next = map { $dfas[$_]->successors( $tuple[$_], $char ) } 0 .. $#dfas;
  960         1975  
60              
61             #warn "[@tuple] --> [@next] via $char\n";
62              
63 480 100       942 if ( not exists $newstates{ _TUPLE_ID(@next) } ) {
64 74         208 my $s = $newstates{ _TUPLE_ID(@next) } = $return->add_states(1);
65             $return->set_accepting($s)
66 74 50       241 if !grep { !$dfas[$_]->is_accepting( $next[$_] ) } 0 .. $#dfas;
  148         348  
67 74         167 push @queue, \@next;
68             }
69              
70 480         783 $return->add_transition( $newstates{ _TUPLE_ID(@tuple) }, $newstates{ _TUPLE_ID(@next) }, $char );
71             }
72             }
73              
74 6         842 return $return;
75             }
76              
77             # this is meant to enforce 1 starting state for a DFA, but it is getting us into trouble
78             # when a DFA object calls unset_starting
79             sub unset_starting {
80 0     0 1 0 my $self = shift;
81 0         0 $self->SUPER::unset_starting(@_);
82 0         0 my $num = () = $self->unset_starting;
83 0 0       0 croak "DFA must have exactly one starting state"
84             if $num != 1;
85             }
86              
87             #### transformations
88              
89             sub trim_sinks {
90 125     125 1 282 my $self = shift;
91 125         529 my $result = $self->clone();
92 125         664 foreach my $state ( $self->array_complement( [ $self->get_states() ], [ $self->get_accepting() ] ) ) {
93 580         1418 my @ret = $self->successors( $state, [ $self->alphabet ] );
94 580 50       1454 if (@ret) {
95 580 100       1536 if ( $ret[0] == $state ) {
96 138 100       377 $result->delete_states($state) if ( $result->is_state($state) );
97             }
98             }
99             }
100 125         730 return $result;
101             }
102              
103             # classical DFA minimization using equivalence classes
104             sub as_min_dfa {
105 137     137 1 634 my $self = shift()->clone;
106 137         759 my $N = $self->num_states;
107 137         451 my @alphabet = $self->alphabet;
108 137         670 my ($start) = $self->get_starting;
109 137         441 my %final = map { $_ => 1 } $self->get_accepting;
  220         704  
110 137         2082 my @equiv = map [ (0) x ( $_ + 1 ), (1) x ( $N - $_ - 1 ) ], 0 .. $N - 1;
111 137         237 while (1) {
112 645         841 my $changed = 0;
113 645         1599 for my $s1 ( 0 .. $N - 1 ) {
114 5342         8274 for my $s2 ( grep { $equiv[$s1][$_] } 0 .. $N - 1 ) {
  93150         100797  
115 14808 100       49281 if ( 1 == grep defined, @final{ $s1, $s2 } ) {
116 2183         2615 $changed = 1;
117 2183         2794 $equiv[$s1][$s2] = 0;
118 2183         4817 next;
119             }
120 12625         18281 for my $char (@alphabet) {
121 67171         155437 my @t = sort { $a <=> $b } $self->successors( [ $s1, $s2 ], $char );
  30430         86852  
122 67171 100       157483 next if @t == 1;
123              
124 30430 100       71762 if ( not $equiv[ $t[0] ][ $t[1] ] ) {
125 6487         8117 $changed = 1;
126 6487         13625 $equiv[$s1][$s2] = 0;
127             }
128             }
129             }
130             }
131 645 100       1376 last if !$changed;
132             }
133 137         878 my $result = FLAT::DFA::Minimal->new;
134 137         258 my %newstate;
135             my @classes;
136 137         379 for my $s ( 0 .. $N - 1 ) {
137 1009 100       1791 next if exists $newstate{$s};
138              
139 803         1548 my @c = ( $s, grep { $equiv[$s][$_] } 0 .. $N - 1 );
  7619         9015  
140 803         1528 push @classes, \@c;
141              
142 803         1861 @newstate{@c} = ( $result->add_states(1) ) x @c;
143             }
144 137         332 for my $c (@classes) {
145 803         1716 my $s = $c->[0];
146 803         1204 for my $char (@alphabet) {
147 3057         6159 my ($next) = $self->successors( $s, $char );
148 3057         7665 $result->add_transition( $newstate{$s}, $newstate{$next}, $char );
149             }
150             }
151 137         763 $result->set_starting( $newstate{$start} );
152 137         504 $result->set_accepting( $newstate{$_} ) for $self->get_accepting;
153 137         806 $result->set_equivalence_classes( \@classes );
154 137         5598 return $result;
155             }
156              
157             # the validity of a given string <-- executes symbols over DFA
158             # if there is not transition for given state and symbol, it fails immediately
159             # if the current state we're in is not final when symbols are exhausted, then it fails
160              
161             sub is_valid_string {
162 1689     1689 1 5551 my $self = shift;
163 1689         2323 my $string = shift;
164 1689         2350 chomp $string;
165 1689         2047 my $OK = undef;
166 1689         4778 my @stack = split( '', $string );
167              
168             # this is confusing all funcs return arrays
169 1689         3679 my @current = $self->get_starting();
170 1689         2562 my $current = pop @current;
171 1689         2573 foreach (@stack) {
172 11084         16584 my @next = $self->successors( $current, $_ );
173 11084 50       17486 if ( !@next ) {
174 0         0 return $OK; #<--returns undef bc no transition found
175             }
176 11084         16032 $current = $next[0];
177             }
178 1689 50       3362 $OK++ if ( $self->is_accepting($current) );
179 1689         7339 return $OK;
180             }
181              
182             # Need to implement! http://www.ianab.com/hyper/
183             # DFA hyper-minimization using equivalence classes
184             sub as_hyper_min_dfa {
185 0     0 0 0 my $self = shift()->clone;
186             }
187              
188             # DFT stuff in preparation for DFA pump stuff;
189             sub as_node_list {
190 250     250 0 404 my $self = shift;
191 250         398 my %node = ();
192 250         599 for my $s1 ( $self->get_states ) {
193 1148         2247 $node{$s1} = {}; # initialize
194 1148         2029 for my $s2 ( $self->get_states ) {
195 6480         9389 my $t = $self->get_transition( $s1, $s2 );
196 6480 100       9903 if ( defined $t ) {
197              
198             # array of symbols that $s1 will go to $s2 on...
199 1236         1352 push( @{ $node{$s1}{$s2} }, split( ',', $t->as_string ) );
  1236         3646  
200             }
201             }
202             }
203 250         1258 return %node;
204             }
205              
206             sub as_acyclic_strings {
207 0     0 0 0 my $self = shift;
208 0         0 my %dflabel = (); # lookup table for dflable
209 0         0 my %backtracked = (); # lookup table for backtracked edges
210 0         0 my $lastDFLabel = 0;
211 0         0 my @string = ();
212 0         0 my %nodes = $self->as_node_list();
213              
214             # output format is the actual PRE followed by all found strings
215 0         0 $self->acyclic( $self->get_starting(), \%dflabel, $lastDFLabel, \%nodes, \@string );
216             }
217              
218             sub acyclic {
219 0     0 0 0 my $self = shift;
220 0         0 my $startNode = shift;
221 0         0 my $dflabel_ref = shift;
222 0         0 my $lastDFLabel = shift;
223 0         0 my $nodes = shift;
224 0         0 my $string = shift;
225              
226             # tree edge detection
227 0 0       0 if ( !exists( $dflabel_ref->{$startNode} ) ) {
228 0         0 $dflabel_ref->{$startNode} = ++$lastDFLabel; # the order inwhich this link was explored
229 0         0 foreach my $adjacent ( keys( %{ $nodes->{$startNode} } ) ) {
  0         0  
230 0 0       0 if ( !exists( $dflabel_ref->{$adjacent} ) ) { # initial tree edge
231 0         0 foreach my $symbol ( @{ $nodes->{$startNode}{$adjacent} } ) {
  0         0  
232 0         0 push( @{$string}, $symbol );
  0         0  
233 0         0 $self->acyclic( $adjacent, \%{$dflabel_ref}, $lastDFLabel, \%{$nodes}, \@{$string} );
  0         0  
  0         0  
  0         0  
234 0 0       0 if ( $self->array_is_subset( [$adjacent], [ $self->get_accepting() ] ) ) { #< proof of concept
235 0         0 printf( "%s\n", join( '', @{$string} ) );
  0         0  
236             }
237 0         0 pop( @{$string} );
  0         0  
238             }
239             }
240             }
241             }
242              
243             # remove startNode entry to facilitate acyclic path determination
244 0         0 delete( $dflabel_ref->{$startNode} );
245              
246             #$lastDFLabel--;
247 0         0 return;
248             }
249              
250             sub as_dft_strings {
251 0     0 0 0 my $self = shift;
252 0         0 my $depth = 1;
253 0 0       0 $depth = shift if ( 1 < $_[0] );
254 0         0 my %dflabel = (); # scoped lookup table for dflable
255 0         0 my %nodes = $self->as_node_list();
256 0         0 foreach ( keys(%nodes) ) {
257 0         0 $dflabel{$_} = []; # initialize container (array) for multiple dflables for each node
258             }
259 0         0 my $lastDFLabel = 0;
260 0         0 my @string = ();
261 0         0 $self->dft( $self->get_starting(), [ $self->get_accepting() ], \%dflabel, $lastDFLabel, \%nodes, \@string, $depth );
262             }
263              
264             sub dft {
265 0     0 0 0 my $self = shift;
266 0         0 my $startNode = shift;
267 0         0 my $goals_ref = shift;
268 0         0 my $dflabel_ref = shift;
269 0         0 my $lastDFLabel = shift;
270 0         0 my $nodes = shift;
271 0         0 my $string = shift;
272 0         0 my $DEPTH = shift;
273              
274             # add start node to path
275 0         0 my $c1 = @{ $dflabel_ref->{$startNode} }; # get number of elements
  0         0  
276 0 0       0 if ( $DEPTH >= $c1 ) {
277 0         0 push( @{ $dflabel_ref->{$startNode} }, ++$lastDFLabel );
  0         0  
278 0         0 foreach my $adjacent ( keys( %{ $nodes->{$startNode} } ) ) {
  0         0  
279 0         0 my $c2 = @{ $dflabel_ref->{$adjacent} };
  0         0  
280 0 0       0 if ( $DEPTH > $c2 ) { # "initial" tree edge
281 0         0 foreach my $symbol ( @{ $nodes->{$startNode}{$adjacent} } ) {
  0         0  
282 0         0 push( @{$string}, $symbol );
  0         0  
283 0         0 $self->dft( $adjacent, [ @{$goals_ref} ], $dflabel_ref, $lastDFLabel, $nodes, [ @{$string} ], $DEPTH );
  0         0  
  0         0  
284              
285             # assumes some base path found
286 0 0       0 if ( $self->array_is_subset( [$adjacent], [ @{$goals_ref} ] ) ) {
  0         0  
287 0         0 printf( "%s\n", join( '', @{$string} ) );
  0         0  
288             }
289 0         0 pop( @{$string} );
  0         0  
290             }
291             }
292             } # remove startNode entry to facilitate acyclic path determination
293 0         0 pop( @{ $dflabel_ref->{$startNode} } );
  0         0  
294 0         0 $lastDFLabel--;
295             }
296             }
297              
298             #
299             # String gen using iterators, supports delimter; default is q{}
300             #
301              
302             sub get_acyclic_sub {
303 816     816 0 974 my $self = shift;
304 816         1591 my ( $start, $nodelist_ref, $dflabel_ref, $string_ref, $accepting_ref, $lastDFLabel, $delimiter ) = @_;
305 816   100     1658 $delimiter //= q{};
306 816         1173 my @ret = ();
307 816         863 foreach my $adjacent ( keys( %{ $nodelist_ref->{$start} } ) ) {
  816         2142  
308 754         859 $lastDFLabel++;
309 754 100       1275 if ( !exists( $dflabel_ref->{$adjacent} ) ) {
310 667         901 $dflabel_ref->{$adjacent} = $lastDFLabel;
311 667         758 foreach my $symbol ( @{ $nodelist_ref->{$start}{$adjacent} } ) {
  667         1190  
312 691         776 push( @{$string_ref}, $symbol );
  691         1014  
313 691         8320 my $string_clone = dclone($string_ref);
314 691         5548 my $dflabel_clone = dclone($dflabel_ref);
315 691     691   2644 push( @ret, sub { return $self->get_acyclic_sub( $adjacent, $nodelist_ref, $dflabel_clone, $string_clone, $accepting_ref, $lastDFLabel, $delimiter ); });
  691         1529  
316 691         939 pop @{$string_ref};
  691         1541  
317             }
318             }
319              
320             }
321              
322             # returns a complex data structure in the form of a hash reference
323             return {
324             substack => [@ret],
325             lastDFLabel => $lastDFLabel,
326 816 100       2784 string => ( $self->array_is_subset( [$start], $accepting_ref ) ? join( $delimiter, @{$string_ref} ) : undef )
  248         1077  
327             };
328             }
329              
330             sub init_acyclic_iterator {
331 125     125 0 342 my ($self, $delimiter) = @_;
332 125         268 my %dflabel = ();
333 125         231 my @string = ();
334 125         206 my $lastDFLabel = 0;
335 125         621 my %nodelist = $self->as_node_list();
336 125         387 my @accepting = $self->get_accepting();
337              
338             # initialize
339 125         260 my @substack = ();
340 125         293 my $r = $self->get_acyclic_sub( $self->get_starting(), \%nodelist, \%dflabel, \@string, \@accepting, $lastDFLabel, $delimiter );
341 125         266 push( @substack, @{ $r->{substack} } );
  125         299  
342             return sub {
343 367     367   104800 while (1) {
344 816 100       1598 if ( !@substack ) {
345 125         329 return undef;
346             }
347 691         892 my $s = pop @substack;
348 691         1073 my $r = $s->();
349 691         958 push( @substack, @{ $r->{substack} } );
  691         974  
350 691 100       2537 if ( $r->{string} ) {
351 242         1581 return $r->{string};
352             }
353             }
354             }
355 125         777 }
356              
357             sub new_acyclic_string_generator {
358 125     125 0 11035 my ($self,$delim) = @_;
359 125         506 my $iterator = $self->init_acyclic_iterator($delim);
360 125         391 return $iterator;
361             }
362              
363             sub get_deepdft_sub {
364 2418     2418 0 2609 my $self = shift;
365 2418         4612 my ( $start, $nodelist_ref, $dflabel_ref, $string_ref, $accepting_ref, $lastDFLabel, $MAXLEVEL, $delimiter ) = @_;
366 2418         2927 my @ret = ();
367 2418 100       4585 my $c1 = ( ref $dflabel_ref eq 'HASH' ) ? @{ $dflabel_ref->{$start} } : 0;
  2413         3602  
368 2418 100 66     7157 if ( $MAXLEVEL and $c1 < $MAXLEVEL ) {
369 2413         2914 push( @{ $dflabel_ref->{$start} }, ++$lastDFLabel );
  2413         3701  
370 2413         2845 foreach my $adjacent ( keys( %{ $nodelist_ref->{$start} } ) ) {
  2413         5066  
371 2364         2545 my $c2 = @{ $dflabel_ref->{$adjacent} };
  2364         2844  
372 2364 100       3924 if ( $c2 < $MAXLEVEL ) {
373 1508         1741 foreach my $symbol ( @{ $nodelist_ref->{$start}{$adjacent} } ) {
  1508         2529  
374 2293         2171 push( @{$string_ref}, $symbol );
  2293         3703  
375 2293         30213 my $string_clone = dclone($string_ref);
376 2293         23413 my $dflabel_clone = dclone($dflabel_ref);
377             push(
378             @ret,
379             sub {
380 2293     2293   4677 return $self->get_deepdft_sub( $adjacent, $nodelist_ref, $dflabel_clone, $string_clone, $accepting_ref, $lastDFLabel, $MAXLEVEL, $delimiter );
381             }
382 2293         8623 );
383 2293         2904 pop @{$string_ref};
  2293         4859  
384             }
385             }
386             }
387             }
388             return {
389             substack => [@ret],
390             lastDFLabel => $lastDFLabel,
391 2418 100       7768 string => ( $self->array_is_subset( [$start], $accepting_ref ) ? join( $delimiter, @{$string_ref} ) : undef )
  1453         7262  
392             };
393             }
394              
395             sub init_deepdft_iterator {
396 125     125 0 298 my ($self, $MAXLEVEL, $delimiter) = @_;
397 125   50     325 $MAXLEVEL //= 1;
398 125   50     560 $delimiter //= q{};
399 125         161 my %dflabel = ();
400 125         241 my @string = ();
401 125         183 my $lastDFLabel = 0;
402 125         387 my %nodelist = $self->as_node_list();
403 125         477 foreach my $node ( keys(%nodelist) ) {
404 574         903 $dflabel{$node} = []; # initializes anonymous arrays for all nodes
405             }
406 125         453 my @accepting = $self->get_accepting();
407              
408             # initialize
409 125         251 my @substack = ();
410 125         306 my $r = $self->get_deepdft_sub( $self->get_starting(), \%nodelist, \%dflabel, \@string, \@accepting, $lastDFLabel, $MAXLEVEL, $delimiter );
411 125         363 push( @substack, @{ $r->{substack} } );
  125         263  
412             return sub {
413 1572     1572   463974 while (1) {
414 2418 100       4890 if ( !@substack ) {
415 125         346 return undef;
416             }
417 2293         3172 my $s = pop @substack;
418 2293         3734 my $r = $s->();
419 2293         3353 push( @substack, @{ $r->{substack} } );
  2293         3300  
420 2293 100       6169 if ( $r->{string} ) {
421 1447         9148 return $r->{string};
422             }
423             }
424             }
425 125         1300 }
426              
427             sub new_deepdft_string_generator {
428 125     125 0 873 my ($self, $MAXLEVEL, $delimiter) = @_;
429 125         559 return $self->init_deepdft_iterator($MAXLEVEL, $delimiter);
430             }
431              
432             1;
433              
434             __END__