File Coverage

blib/lib/FLAT/DFA.pm
Criterion Covered Total %
statement 223 306 72.8
branch 47 70 67.1
condition 2 3 66.6
subroutine 25 31 80.6
pod 7 19 36.8
total 304 429 70.8


line stmt bran cond sub pod time code
1             package FLAT::DFA;
2              
3 6     6   48 use strict;
  6         12  
  6         195  
4 6     6   34 use warnings;
  6         12  
  6         217  
5 6     6   33 use parent qw(FLAT::NFA);
  6         13  
  6         43  
6 6     6   347 use Storable qw(dclone);
  6         13  
  6         338  
7 6     6   3050 use FLAT::DFA::Minimal;
  6         15  
  6         187  
8 6     6   38 use Carp;
  6         9  
  6         17619  
9              
10             sub set_starting {
11 292     292 1 582 my $self = shift;
12 292         1198 $self->SUPER::set_starting(@_);
13              
14 292         943 my $num = () = $self->get_starting;
15 292 50       1204 confess "DFA must have exactly one starting state"
16             if $num != 1;
17             }
18              
19             sub complement {
20 6     6 1 37 my $self = $_[0]->clone;
21              
22 6         40 for my $s ( $self->get_states ) {
23 80 100       161 $self->is_accepting($s)
24             ? $self->unset_accepting($s)
25             : $self->set_accepting($s);
26             }
27              
28 6         34 return $self;
29             }
30              
31 1520     1520   4269 sub _TUPLE_ID { join "\0", @_ }
32              
33             sub _uniq {
34 6     6   11 my %seen;
35 6         13 grep { !$seen{$_}++ } @_;
  72         142  
36             }
37              
38             ## this method still needs more work..
39             sub intersect {
40 6     6 1 22 my @dfas = map { $_->as_dfa } @_;
  12         64  
41              
42 6         38 my $return = FLAT::DFA->new;
43 6         18 my %newstates;
44 6         18 my @alpha = _uniq( map { $_->alphabet } @dfas );
  12         37  
45              
46 6         57 $_->_extend_alphabet(@alpha) for @dfas;
47              
48 6         18 my @start = map { $_->get_starting } @dfas;
  12         50  
49 6         22 my $start = $newstates{ _TUPLE_ID(@start) } = $return->add_states(1);
50 6         33 $return->set_starting($start);
51             $return->set_accepting($start)
52 6 50       19 if !grep { !$dfas[$_]->is_accepting( $start[$_] ) } 0 .. $#dfas;
  12         40  
53              
54 6         31 my @queue = ( \@start );
55 6         28 while (@queue) {
56 80         175 my @tuple = @{ shift @queue };
  80         212  
57              
58 80         174 for my $char (@alpha) {
59 480         1025 my @next = map { $dfas[$_]->successors( $tuple[$_], $char ) } 0 .. $#dfas;
  960         2330  
60              
61             #warn "[@tuple] --> [@next] via $char\n";
62              
63 480 100       1097 if ( not exists $newstates{ _TUPLE_ID(@next) } ) {
64 74         220 my $s = $newstates{ _TUPLE_ID(@next) } = $return->add_states(1);
65             $return->set_accepting($s)
66 74 50       200 if !grep { !$dfas[$_]->is_accepting( $next[$_] ) } 0 .. $#dfas;
  148         378  
67 74         167 push @queue, \@next;
68             }
69              
70 480         954 $return->add_transition( $newstates{ _TUPLE_ID(@tuple) }, $newstates{ _TUPLE_ID(@next) }, $char );
71             }
72             }
73              
74 6         871 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 310 my $self = shift;
91 125         545 my $result = $self->clone();
92 125         972 foreach my $state ( $self->array_complement( [ $self->get_states() ], [ $self->get_accepting() ] ) ) {
93 552         1526 my @ret = $self->successors( $state, [ $self->alphabet ] );
94 552 50       1607 if (@ret) {
95 552 100       1640 if ( $ret[0] == $state ) {
96 135 100       382 $result->delete_states($state) if ( $result->is_state($state) );
97             }
98             }
99             }
100 125         659 return $result;
101             }
102              
103             # classical DFA minimization using equivalence classes
104             sub as_min_dfa {
105 137     137 1 718 my $self = shift()->clone;
106 137         741 my $N = $self->num_states;
107 137         507 my @alphabet = $self->alphabet;
108 137         646 my ($start) = $self->get_starting;
109 137         596 my %final = map { $_ => 1 } $self->get_accepting;
  216         758  
110 137         2472 my @equiv = map [ (0) x ( $_ + 1 ), (1) x ( $N - $_ - 1 ) ], 0 .. $N - 1;
111 137         345 while (1) {
112 648         1039 my $changed = 0;
113 648         1766 for my $s1 ( 0 .. $N - 1 ) {
114 5197         9813 for my $s2 ( grep { $equiv[$s1][$_] } 0 .. $N - 1 ) {
  90393         118660  
115 14355 100       53339 if ( 1 == grep defined, @final{ $s1, $s2 } ) {
116 2157         3131 $changed = 1;
117 2157         3180 $equiv[$s1][$s2] = 0;
118 2157         5283 next;
119             }
120 12198         19208 for my $char (@alphabet) {
121 65390         182546 my @t = sort { $a <=> $b } $self->successors( [ $s1, $s2 ], $char );
  29109         97874  
122 65390 100       178589 next if @t == 1;
123              
124 29109 100       80723 if ( not $equiv[ $t[0] ][ $t[1] ] ) {
125 6132         7943 $changed = 1;
126 6132         13652 $equiv[$s1][$s2] = 0;
127             }
128             }
129             }
130             }
131 648 100       1415 last if !$changed;
132             }
133 137         963 my $result = FLAT::DFA::Minimal->new;
134 137         303 my %newstate;
135             my @classes;
136 137         427 for my $s ( 0 .. $N - 1 ) {
137 983 100       2237 next if exists $newstate{$s};
138              
139 776         1669 my @c = ( $s, grep { $equiv[$s][$_] } 0 .. $N - 1 );
  7152         10014  
140 776         1652 push @classes, \@c;
141              
142 776         1851 @newstate{@c} = ( $result->add_states(1) ) x @c;
143             }
144 137         475 for my $c (@classes) {
145 776         1804 my $s = $c->[0];
146 776         1511 for my $char (@alphabet) {
147 2940         7448 my ($next) = $self->successors( $s, $char );
148 2940         8826 $result->add_transition( $newstate{$s}, $newstate{$next}, $char );
149             }
150             }
151 137         789 $result->set_starting( $newstate{$start} );
152 137         511 $result->set_accepting( $newstate{$_} ) for $self->get_accepting;
153 137         821 $result->set_equivalence_classes( \@classes );
154 137         5811 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 1603     1603 1 6771 my $self = shift;
163 1603         2123 my $string = shift;
164 1603         2765 chomp $string;
165 1603         2055 my $OK = undef;
166 1603         5415 my @stack = split( '', $string );
167              
168             # this is confusing all funcs return arrays
169 1603         4425 my @current = $self->get_starting();
170 1603         2755 my $current = pop @current;
171 1603         2896 foreach (@stack) {
172 10752         20757 my @next = $self->successors( $current, $_ );
173 10752 50       19844 if ( !@next ) {
174 0         0 return $OK; #<--returns undef bc no transition found
175             }
176 10752         18792 $current = $next[0];
177             }
178 1603 50       3692 $OK++ if ( $self->is_accepting($current) );
179 1603         7586 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 522 my $self = shift;
191 250         447 my %node = ();
192 250         764 for my $s1 ( $self->get_states ) {
193 1096         2585 $node{$s1} = {}; # initialize
194 1096         2302 for my $s2 ( $self->get_states ) {
195 5676         10156 my $t = $self->get_transition( $s1, $s2 );
196 5676 100       10491 if ( defined $t ) {
197              
198             # array of symbols that $s1 will go to $s2 on...
199 1110         1359 push( @{ $node{$s1}{$s2} }, split( ',', $t->as_string ) );
  1110         3681  
200             }
201             }
202             }
203 250         1420 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 (still experimental)
300             #
301              
302             sub get_acyclic_sub {
303 715     715 0 1060 my $self = shift;
304 715         1490 my ( $start, $nodelist_ref, $dflabel_ref, $string_ref, $accepting_ref, $lastDFLabel ) = @_;
305 715         1026 my @ret = ();
306 715         1020 foreach my $adjacent ( keys( %{ $nodelist_ref->{$start} } ) ) {
  715         2118  
307 652         873 $lastDFLabel++;
308 652 100       1478 if ( !exists( $dflabel_ref->{$adjacent} ) ) {
309 572         994 $dflabel_ref->{$adjacent} = $lastDFLabel;
310 572         786 foreach my $symbol ( @{ $nodelist_ref->{$start}{$adjacent} } ) {
  572         1135  
311 590         790 push( @{$string_ref}, $symbol );
  590         1016  
312 590         7861 my $string_clone = dclone($string_ref);
313 590         5385 my $dflabel_clone = dclone($dflabel_ref);
314 590     590   2619 push( @ret, sub { return $self->get_acyclic_sub( $adjacent, $nodelist_ref, $dflabel_clone, $string_clone, $accepting_ref, $lastDFLabel ); });
  590         1270  
315 590         919 pop @{$string_ref};
  590         1478  
316             }
317             }
318              
319             }
320              
321             # returns a complex data structure in the form of a hash reference
322             return {
323             substack => [@ret],
324             lastDFLabel => $lastDFLabel,
325 715 100       2705 string => ( $self->array_is_subset( [$start], $accepting_ref ) ? join( '', @{$string_ref} ) : undef )
  208         1025  
326             };
327             }
328              
329             sub init_acyclic_iterator {
330 125     125 0 361 my ($self, $delim) = @_;
331 125         341 my %dflabel = ();
332 125         274 my @string = ();
333 125         273 my $lastDFLabel = 0;
334 125         482 my %nodelist = $self->as_node_list();
335 125         393 my @accepting = $self->get_accepting();
336              
337             # initialize
338 125         322 my @substack = ();
339 125         337 my $r = $self->get_acyclic_sub( $self->get_starting(), \%nodelist, \%dflabel, \@string, \@accepting, $lastDFLabel );
340 125         309 push( @substack, @{ $r->{substack} } );
  125         441  
341             return sub {
342 328     328   85921 while (1) {
343 715 100       1693 if ( !@substack ) {
344 125         354 return undef;
345             }
346 590         895 my $s = pop @substack;
347 590         1103 my $r = $s->();
348 590         941 push( @substack, @{ $r->{substack} } );
  590         1008  
349 590 100       2719 if ( $r->{string} ) {
350 203         1503 return $r->{string};
351             }
352             }
353             }
354 125         826 }
355              
356             sub new_acyclic_string_generator {
357 125     125 0 12018 my ($self,$delim) = @_;
358 125         508 my $iterator = $self->init_acyclic_iterator($delim);
359 125         415 return $iterator;
360             }
361              
362             sub get_deepdft_sub {
363 2078     2078 0 2900 my $self = shift;
364 2078         4394 my ( $start, $nodelist_ref, $dflabel_ref, $string_ref, $accepting_ref, $lastDFLabel, $max ) = @_;
365 2078         2884 my @ret = ();
366 2078 100       5121 my $c1 = ( ref $dflabel_ref eq 'HASH' ) ? @{ $dflabel_ref->{$start} } : 0;
  2075         3434  
367 2078 100 66     8248 if ( $max and $c1 < $max ) {
368 2075         3003 push( @{ $dflabel_ref->{$start} }, ++$lastDFLabel );
  2075         3844  
369 2075         2496 foreach my $adjacent ( keys( %{ $nodelist_ref->{$start} } ) ) {
  2075         5709  
370 2006         2470 my $c2 = @{ $dflabel_ref->{$adjacent} };
  2006         2889  
371 2006 100       3771 if ( $c2 < $max ) {
372 1343         1583 foreach my $symbol ( @{ $nodelist_ref->{$start}{$adjacent} } ) {
  1343         2740  
373 1953         2305 push( @{$string_ref}, $symbol );
  1953         3390  
374 1953         26901 my $string_clone = dclone($string_ref);
375 1953         23033 my $dflabel_clone = dclone($dflabel_ref);
376             push(
377             @ret,
378             sub {
379 1953     1953   4712 return $self->get_deepdft_sub( $adjacent, $nodelist_ref, $dflabel_clone, $string_clone, $accepting_ref, $lastDFLabel, $max );
380             }
381 1953         8331 );
382 1953         2996 pop @{$string_ref};
  1953         4747  
383             }
384             }
385             }
386             }
387             return {
388             substack => [@ret],
389             lastDFLabel => $lastDFLabel,
390 2078 100       8515 string => ( $self->array_is_subset( [$start], $accepting_ref ) ? join( '', @{$string_ref} ) : undef )
  1405         7451  
391             };
392             }
393              
394             sub init_deepdft_iterator {
395 125     125 0 242 my $self = shift;
396 125         198 my $MAXLEVEL = shift;
397 125         342 my %dflabel = ();
398 125         239 my @string = ();
399 125         216 my $lastDFLabel = 0;
400 125         362 my %nodelist = $self->as_node_list();
401 125         519 foreach my $node ( keys(%nodelist) ) {
402 548         1007 $dflabel{$node} = []; # initializes anonymous arrays for all nodes
403             }
404 125         529 my @accepting = $self->get_accepting();
405              
406             # initialize
407 125         360 my @substack = ();
408 125         386 my $r = $self->get_deepdft_sub( $self->get_starting(), \%nodelist, \%dflabel, \@string, \@accepting, $lastDFLabel, $MAXLEVEL );
409 125         357 push( @substack, @{ $r->{substack} } );
  125         297  
410             return sub {
411 1525     1525   443251 while (1) {
412 2078 100       5241 if ( !@substack ) {
413 125         407 return undef;
414             }
415 1953         3489 my $s = pop @substack;
416 1953         3386 my $r = $s->();
417 1953         3056 push( @substack, @{ $r->{substack} } );
  1953         3196  
418 1953 100       5697 if ( $r->{string} ) {
419 1400         10073 return $r->{string};
420             }
421             }
422             }
423 125         1552 }
424              
425             sub new_deepdft_string_generator {
426 125     125 0 906 my $self = shift;
427 125 50       397 my $MAXLEVEL = ( @_ ? shift : 1 );
428 125         479 return $self->init_deepdft_iterator($MAXLEVEL);
429             }
430              
431             1;
432              
433             __END__