File Coverage

blib/lib/FLAT/DFA.pm
Criterion Covered Total %
statement 224 307 72.9
branch 47 70 67.1
condition 4 5 80.0
subroutine 25 31 80.6
pod 7 19 36.8
total 307 432 71.0


line stmt bran cond sub pod time code
1             package FLAT::DFA;
2              
3 6     6   44 use strict;
  6         16  
  6         215  
4 6     6   29 use warnings;
  6         12  
  6         221  
5 6     6   28 use parent qw(FLAT::NFA);
  6         12  
  6         41  
6 6     6   403 use Storable qw(dclone);
  6         13  
  6         387  
7 6     6   3543 use FLAT::DFA::Minimal;
  6         23  
  6         193  
8 6     6   39 use Carp;
  6         10  
  6         17501  
9              
10             sub set_starting {
11 292     292 1 502 my $self = shift;
12 292         1123 $self->SUPER::set_starting(@_);
13              
14 292         890 my $num = () = $self->get_starting;
15 292 50       1017 confess "DFA must have exactly one starting state"
16             if $num != 1;
17             }
18              
19             sub complement {
20 6     6 1 38 my $self = $_[0]->clone;
21              
22 6         34 for my $s ( $self->get_states ) {
23 80 100       140 $self->is_accepting($s)
24             ? $self->unset_accepting($s)
25             : $self->set_accepting($s);
26             }
27              
28 6         40 return $self;
29             }
30              
31 1520     1520   4164 sub _TUPLE_ID { join "\0", @_ }
32              
33             sub _uniq {
34 6     6   14 my %seen;
35 6         15 grep { !$seen{$_}++ } @_;
  72         129  
36             }
37              
38             ## this method still needs more work..
39             sub intersect {
40 6     6 1 18 my @dfas = map { $_->as_dfa } @_;
  12         64  
41              
42 6         38 my $return = FLAT::DFA->new;
43 6         15 my %newstates;
44 6         16 my @alpha = _uniq( map { $_->alphabet } @dfas );
  12         40  
45              
46 6         51 $_->_extend_alphabet(@alpha) for @dfas;
47              
48 6         16 my @start = map { $_->get_starting } @dfas;
  12         34  
49 6         26 my $start = $newstates{ _TUPLE_ID(@start) } = $return->add_states(1);
50 6         30 $return->set_starting($start);
51             $return->set_accepting($start)
52 6 50       19 if !grep { !$dfas[$_]->is_accepting( $start[$_] ) } 0 .. $#dfas;
  12         37  
53              
54 6         24 my @queue = ( \@start );
55 6         25 while (@queue) {
56 80         118 my @tuple = @{ shift @queue };
  80         222  
57              
58 80         179 for my $char (@alpha) {
59 480         965 my @next = map { $dfas[$_]->successors( $tuple[$_], $char ) } 0 .. $#dfas;
  960         2223  
60              
61             #warn "[@tuple] --> [@next] via $char\n";
62              
63 480 100       1164 if ( not exists $newstates{ _TUPLE_ID(@next) } ) {
64 74         232 my $s = $newstates{ _TUPLE_ID(@next) } = $return->add_states(1);
65             $return->set_accepting($s)
66 74 50       198 if !grep { !$dfas[$_]->is_accepting( $next[$_] ) } 0 .. $#dfas;
  148         362  
67 74         168 push @queue, \@next;
68             }
69              
70 480         958 $return->add_transition( $newstates{ _TUPLE_ID(@tuple) }, $newstates{ _TUPLE_ID(@next) }, $char );
71             }
72             }
73              
74 6         906 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 273 my $self = shift;
91 125         573 my $result = $self->clone();
92 125         616 foreach my $state ( $self->array_complement( [ $self->get_states() ], [ $self->get_accepting() ] ) ) {
93 577         1329 my @ret = $self->successors( $state, [ $self->alphabet ] );
94 577 50       1337 if (@ret) {
95 577 100       1445 if ( $ret[0] == $state ) {
96 131 100       303 $result->delete_states($state) if ( $result->is_state($state) );
97             }
98             }
99             }
100 125         582 return $result;
101             }
102              
103             # classical DFA minimization using equivalence classes
104             sub as_min_dfa {
105 137     137 1 592 my $self = shift()->clone;
106 137         713 my $N = $self->num_states;
107 137         383 my @alphabet = $self->alphabet;
108 137         589 my ($start) = $self->get_starting;
109 137         492 my %final = map { $_ => 1 } $self->get_accepting;
  217         634  
110 137         2230 my @equiv = map [ (0) x ( $_ + 1 ), (1) x ( $N - $_ - 1 ) ], 0 .. $N - 1;
111 137         353 while (1) {
112 655         954 my $changed = 0;
113 655         1697 for my $s1 ( 0 .. $N - 1 ) {
114 5314         9744 for my $s2 ( grep { $equiv[$s1][$_] } 0 .. $N - 1 ) {
  92206         100637  
115 14617 100       50502 if ( 1 == grep defined, @final{ $s1, $s2 } ) {
116 2158         2672 $changed = 1;
117 2158         3115 $equiv[$s1][$s2] = 0;
118 2158         4688 next;
119             }
120 12459         17537 for my $char (@alphabet) {
121 66689         162986 my @t = sort { $a <=> $b } $self->successors( [ $s1, $s2 ], $char );
  29924         91247  
122 66689 100       159352 next if @t == 1;
123              
124 29924 100       74657 if ( not $equiv[ $t[0] ][ $t[1] ] ) {
125 6301         7571 $changed = 1;
126 6301         13134 $equiv[$s1][$s2] = 0;
127             }
128             }
129             }
130             }
131 655 100       1217 last if !$changed;
132             }
133 137         850 my $result = FLAT::DFA::Minimal->new;
134 137         280 my %newstate;
135             my @classes;
136 137         364 for my $s ( 0 .. $N - 1 ) {
137 991 100       1825 next if exists $newstate{$s};
138              
139 800         1568 my @c = ( $s, grep { $equiv[$s][$_] } 0 .. $N - 1 );
  7475         8645  
140 800         1404 push @classes, \@c;
141              
142 800         1617 @newstate{@c} = ( $result->add_states(1) ) x @c;
143             }
144 137         367 for my $c (@classes) {
145 800         1625 my $s = $c->[0];
146 800         1152 for my $char (@alphabet) {
147 3043         6568 my ($next) = $self->successors( $s, $char );
148 3043         7540 $result->add_transition( $newstate{$s}, $newstate{$next}, $char );
149             }
150             }
151 137         643 $result->set_starting( $newstate{$start} );
152 137         508 $result->set_accepting( $newstate{$_} ) for $self->get_accepting;
153 137         725 $result->set_equivalence_classes( \@classes );
154 137         5558 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 1651     1651 1 5961 my $self = shift;
163 1651         2010 my $string = shift;
164 1651         2601 chomp $string;
165 1651         2096 my $OK = undef;
166 1651         4626 my @stack = split( '', $string );
167              
168             # this is confusing all funcs return arrays
169 1651         4443 my @current = $self->get_starting();
170 1651         2399 my $current = pop @current;
171 1651         2263 foreach (@stack) {
172 10972         17244 my @next = $self->successors( $current, $_ );
173 10972 50       16216 if ( !@next ) {
174 0         0 return $OK; #<--returns undef bc no transition found
175             }
176 10972         15839 $current = $next[0];
177             }
178 1651 50       3378 $OK++ if ( $self->is_accepting($current) );
179 1651         6417 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 391 my $self = shift;
191 250         418 my %node = ();
192 250         555 for my $s1 ( $self->get_states ) {
193 1152         2266 $node{$s1} = {}; # initialize
194 1152         1947 for my $s2 ( $self->get_states ) {
195 6504         9792 my $t = $self->get_transition( $s1, $s2 );
196 6504 100       9859 if ( defined $t ) {
197              
198             # array of symbols that $s1 will go to $s2 on...
199 1176         1269 push( @{ $node{$s1}{$s2} }, split( ',', $t->as_string ) );
  1176         3699  
200             }
201             }
202             }
203 250         1221 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 782     782 0 881 my $self = shift;
304 782         1379 my ( $start, $nodelist_ref, $dflabel_ref, $string_ref, $accepting_ref, $lastDFLabel, $delimiter ) = @_;
305 782   100     3517 $delimiter //= q{};
306 782         912 my @ret = ();
307 782         843 foreach my $adjacent ( keys( %{ $nodelist_ref->{$start} } ) ) {
  782         1925  
308 696         789 $lastDFLabel++;
309 696 100       1395 if ( !exists( $dflabel_ref->{$adjacent} ) ) {
310 649         847 $dflabel_ref->{$adjacent} = $lastDFLabel;
311 649         641 foreach my $symbol ( @{ $nodelist_ref->{$start}{$adjacent} } ) {
  649         1397  
312 657         621 push( @{$string_ref}, $symbol );
  657         1055  
313 657         8001 my $string_clone = dclone($string_ref);
314 657         5251 my $dflabel_clone = dclone($dflabel_ref);
315 657     657   3310 push( @ret, sub { return $self->get_acyclic_sub( $adjacent, $nodelist_ref, $dflabel_clone, $string_clone, $accepting_ref, $lastDFLabel, $delimiter ); });
  657         1241  
316 657         907 pop @{$string_ref};
  657         1430  
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 782 100       2596 string => ( $self->array_is_subset( [$start], $accepting_ref ) ? join( $delimiter, @{$string_ref} ) : undef )
  235         911  
327             };
328             }
329              
330             sub init_acyclic_iterator {
331 125     125 0 277 my ($self, $delimiter) = @_;
332 125         241 my %dflabel = ();
333 125         195 my @string = ();
334 125         173 my $lastDFLabel = 0;
335 125         425 my %nodelist = $self->as_node_list();
336 125         412 my @accepting = $self->get_accepting();
337              
338             # initialize
339 125         215 my @substack = ();
340 125         327 my $r = $self->get_acyclic_sub( $self->get_starting(), \%nodelist, \%dflabel, \@string, \@accepting, $lastDFLabel, $delimiter );
341 125         320 push( @substack, @{ $r->{substack} } );
  125         333  
342             return sub {
343 356     356   98481 while (1) {
344 782 100       1643 if ( !@substack ) {
345 125         291 return undef;
346             }
347 657         951 my $s = pop @substack;
348 657         1029 my $r = $s->();
349 657         846 push( @substack, @{ $r->{substack} } );
  657         969  
350 657 100       2403 if ( $r->{string} ) {
351 231         1470 return $r->{string};
352             }
353             }
354             }
355 125         645 }
356              
357             sub new_acyclic_string_generator {
358 125     125 0 10702 my ($self,$delim) = @_;
359 125         430 my $iterator = $self->init_acyclic_iterator($delim);
360 125         328 return $iterator;
361             }
362              
363             sub get_deepdft_sub {
364 2025     2025 0 2282 my $self = shift;
365 2025         3493 my ( $start, $nodelist_ref, $dflabel_ref, $string_ref, $accepting_ref, $lastDFLabel, $max ) = @_;
366 2025         2533 my @ret = ();
367 2025 100       4000 my $c1 = ( ref $dflabel_ref eq 'HASH' ) ? @{ $dflabel_ref->{$start} } : 0;
  2023         2855  
368 2025 100 66     6037 if ( $max and $c1 < $max ) {
369 2023         2339 push( @{ $dflabel_ref->{$start} }, ++$lastDFLabel );
  2023         3466  
370 2023         2146 foreach my $adjacent ( keys( %{ $nodelist_ref->{$start} } ) ) {
  2023         4282  
371 1935         2150 my $c2 = @{ $dflabel_ref->{$adjacent} };
  1935         2499  
372 1935 100       3306 if ( $c2 < $max ) {
373 1369         1433 foreach my $symbol ( @{ $nodelist_ref->{$start}{$adjacent} } ) {
  1369         2290  
374 1900         1932 push( @{$string_ref}, $symbol );
  1900         2666  
375 1900         22317 my $string_clone = dclone($string_ref);
376 1900         19361 my $dflabel_clone = dclone($dflabel_ref);
377             push(
378             @ret,
379             sub {
380 1900     1900   3800 return $self->get_deepdft_sub( $adjacent, $nodelist_ref, $dflabel_clone, $string_clone, $accepting_ref, $lastDFLabel, $max );
381             }
382 1900         7553 );
383 1900         2415 pop @{$string_ref};
  1900         4168  
384             }
385             }
386             }
387             }
388             return {
389             substack => [@ret],
390             lastDFLabel => $lastDFLabel,
391 2025 100       6809 string => ( $self->array_is_subset( [$start], $accepting_ref ) ? join( '', @{$string_ref} ) : undef )
  1424         6044  
392             };
393             }
394              
395             sub init_deepdft_iterator {
396 125     125 0 209 my $self = shift;
397 125         176 my $MAXLEVEL = shift;
398 125         219 my %dflabel = ();
399 125         178 my @string = ();
400 125         139 my $lastDFLabel = 0;
401 125         403 my %nodelist = $self->as_node_list();
402 125         424 foreach my $node ( keys(%nodelist) ) {
403 576         850 $dflabel{$node} = []; # initializes anonymous arrays for all nodes
404             }
405 125         418 my @accepting = $self->get_accepting();
406              
407             # initialize
408 125         232 my @substack = ();
409 125         260 my $r = $self->get_deepdft_sub( $self->get_starting(), \%nodelist, \%dflabel, \@string, \@accepting, $lastDFLabel, $MAXLEVEL );
410 125         301 push( @substack, @{ $r->{substack} } );
  125         209  
411             return sub {
412 1545     1545   372241 while (1) {
413 2025 100       4343 if ( !@substack ) {
414 125         306 return undef;
415             }
416 1900         2853 my $s = pop @substack;
417 1900         2797 my $r = $s->();
418 1900         2585 push( @substack, @{ $r->{substack} } );
  1900         2898  
419 1900 100       4577 if ( $r->{string} ) {
420 1420         8820 return $r->{string};
421             }
422             }
423             }
424 125         1214 }
425              
426             sub new_deepdft_string_generator {
427 125     125 0 757 my $self = shift;
428 125 50       294 my $MAXLEVEL = ( @_ ? shift : 1 );
429 125         399 return $self->init_deepdft_iterator($MAXLEVEL);
430             }
431              
432             1;
433              
434             __END__