line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Algorithm::ConstructDFA;
|
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
109337
|
use 5.012000;
|
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
81
|
|
4
|
2
|
|
|
2
|
|
13
|
use strict;
|
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
218
|
|
5
|
2
|
|
|
2
|
|
10
|
use warnings;
|
|
2
|
|
|
|
|
9
|
|
|
2
|
|
|
|
|
71
|
|
6
|
2
|
|
|
2
|
|
10
|
use base qw(Exporter);
|
|
2
|
|
|
|
|
14
|
|
|
2
|
|
|
|
|
689
|
|
7
|
2
|
|
|
2
|
|
3818
|
use Storable qw/freeze thaw/;
|
|
2
|
|
|
|
|
14349
|
|
|
2
|
|
|
|
|
337
|
|
8
|
2
|
|
|
2
|
|
2774
|
use List::UtilsBy qw/partition_by/;
|
|
2
|
|
|
|
|
6620
|
|
|
2
|
|
|
|
|
812
|
|
9
|
2
|
|
|
2
|
|
9116
|
use List::MoreUtils qw/uniq/;
|
|
2
|
|
|
|
|
4724
|
|
|
2
|
|
|
|
|
216
|
|
10
|
2
|
|
|
2
|
|
2557
|
use Data::AutoBimap;
|
|
2
|
|
|
|
|
1333
|
|
|
2
|
|
|
|
|
84
|
|
11
|
2
|
|
|
2
|
|
2765
|
use Memoize;
|
|
2
|
|
|
|
|
10995
|
|
|
2
|
|
|
|
|
6452
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.03';
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( 'all' => [ qw(
|
16
|
|
|
|
|
|
|
construct_dfa
|
17
|
|
|
|
|
|
|
) ] );
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
our @EXPORT = qw(
|
22
|
|
|
|
|
|
|
construct_dfa
|
23
|
|
|
|
|
|
|
);
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
local $Storable::canonical = 1;
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub _memoizess {
|
28
|
90
|
|
|
90
|
|
145
|
my ($sub) = @_;
|
29
|
90
|
|
|
|
|
134
|
my %cache;
|
30
|
|
|
|
|
|
|
return sub {
|
31
|
18965
|
|
|
18965
|
|
29700
|
my ($s) = @_;
|
32
|
18965
|
100
|
|
|
|
71349
|
if (not exists $cache{$s}) {
|
33
|
736
|
|
|
|
|
1294
|
$cache{$s} = $sub->($s);
|
34
|
|
|
|
|
|
|
}
|
35
|
18965
|
|
|
|
|
332676
|
return $cache{$s};
|
36
|
90
|
|
|
|
|
534
|
};
|
37
|
|
|
|
|
|
|
}
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
sub _get_graph {
|
40
|
30
|
|
|
30
|
|
73
|
my ($roots, $labelf, $nullablef, $successorsf, $acceptingf) = @_;
|
41
|
|
|
|
|
|
|
|
42
|
30
|
|
|
|
|
404
|
my $m = Data::AutoBimap->new();
|
43
|
|
|
|
|
|
|
|
44
|
30
|
|
|
364
|
|
710
|
my $label = _memoizess(sub { $labelf->($m->n2s($_[0])) });
|
|
364
|
|
|
|
|
1256
|
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
my $successors = memoize(sub {
|
47
|
364
|
|
|
364
|
|
9587
|
map { $m->s2n($_) } $successorsf->($m->n2s($_[0]))
|
|
805
|
|
|
|
|
35531
|
|
48
|
30
|
|
|
|
|
270
|
});
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
my $accepting = sub {
|
51
|
785
|
|
|
785
|
|
9500
|
!!$acceptingf->(map { $m->n2s($_) } @_)
|
|
6853
|
|
|
|
|
51167
|
|
52
|
30
|
|
|
|
|
20567
|
};
|
53
|
|
|
|
|
|
|
|
54
|
30
|
|
|
|
|
62
|
my %nullable;
|
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
my $nullable = _memoizess(sub {
|
57
|
364
|
|
|
364
|
|
1299
|
!!$nullablef->($m->n2s($_[0]));
|
58
|
30
|
|
|
|
|
172
|
});
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
my $all_reachable_and_self = _memoizess(sub {
|
61
|
8
|
|
|
8
|
|
20
|
my ($v) = @_;
|
62
|
8
|
|
|
|
|
18
|
my %seen;
|
63
|
8
|
|
|
|
|
23
|
my @todo = ($v);
|
64
|
8
|
|
|
|
|
42
|
while (@todo) {
|
65
|
81
|
|
|
|
|
860
|
my $c = pop @todo;
|
66
|
81
|
100
|
|
|
|
296
|
next if $seen{$c}++;
|
67
|
63
|
100
|
|
|
|
209
|
push @todo, $successors->($c) if $nullable->($c);
|
68
|
|
|
|
|
|
|
}
|
69
|
8
|
|
|
|
|
295
|
[keys %seen];
|
70
|
30
|
|
|
|
|
451
|
});
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
my $all_reachable_and_self_many = sub {
|
73
|
1167
|
|
|
1167
|
|
35905
|
my %seen;
|
74
|
1167
|
|
|
|
|
3173
|
my @todo = (@_);
|
75
|
1167
|
|
|
|
|
3393
|
while (@todo) {
|
76
|
18941
|
|
|
|
|
152889
|
my $c = pop @todo;
|
77
|
18941
|
100
|
|
|
|
89997
|
next if $seen{$c}++;
|
78
|
11980
|
100
|
|
|
|
32670
|
push @todo, $successors->($c) if $nullable->($c);
|
79
|
|
|
|
|
|
|
}
|
80
|
1167
|
|
|
|
|
35836
|
keys %seen;
|
81
|
30
|
|
|
|
|
212
|
};
|
82
|
|
|
|
|
|
|
|
83
|
161
|
|
|
|
|
221
|
my $start = [
|
84
|
8
|
|
|
|
|
30
|
sort { $a cmp $b }
|
85
|
|
|
|
|
|
|
uniq map {
|
86
|
30
|
100
|
|
|
|
677
|
$nullable->($_) ? @{ $all_reachable_and_self->($_) } : $_
|
|
30
|
|
|
|
|
139
|
|
87
|
|
|
|
|
|
|
}
|
88
|
30
|
|
|
|
|
92
|
map { $m->s2n($_) }
|
89
|
|
|
|
|
|
|
@$roots
|
90
|
|
|
|
|
|
|
];
|
91
|
|
|
|
|
|
|
|
92
|
30
|
|
|
|
|
589
|
my $start_s = join ' ', @$start;
|
93
|
|
|
|
|
|
|
|
94
|
30
|
|
|
|
|
149
|
my @todo = ($start);
|
95
|
30
|
|
|
|
|
76
|
my %seen;
|
96
|
|
|
|
|
|
|
my $dfa;
|
97
|
|
|
|
|
|
|
|
98
|
0
|
|
|
|
|
0
|
my @accepting_dfa_states;
|
99
|
0
|
|
|
|
|
0
|
my %predecessors;
|
100
|
|
|
|
|
|
|
|
101
|
30
|
|
|
|
|
100
|
while (@todo) {
|
102
|
1197
|
|
|
|
|
1979
|
my $src = pop @todo;
|
103
|
1197
|
|
|
|
|
1551
|
my @src = @{ $src };
|
|
1197
|
|
|
|
|
5723
|
|
104
|
1197
|
|
|
|
|
8270
|
my $src_s = join ' ', @src;
|
105
|
1197
|
100
|
|
|
|
9152
|
next if $seen{$src_s}++;
|
106
|
|
|
|
|
|
|
|
107
|
400
|
|
|
|
|
1045
|
my $src_accepts = $accepting->(@src);
|
108
|
400
|
100
|
|
|
|
1500
|
push @accepting_dfa_states, $src_s if $src_accepts;
|
109
|
|
|
|
|
|
|
|
110
|
3442
|
|
|
3442
|
|
19191
|
my %p = partition_by { $label->($_) }
|
111
|
400
|
|
|
|
|
2683
|
grep { defined $label->($_) } @src;
|
|
3442
|
|
|
|
|
6976
|
|
112
|
|
|
|
|
|
|
|
113
|
400
|
|
|
|
|
5214
|
while (my ($k, $v) = each %p) {
|
114
|
32776
|
|
|
|
|
42681
|
my @dst = sort { $a cmp $b } uniq
|
|
3442
|
|
|
|
|
171612
|
|
115
|
1167
|
|
|
|
|
2312
|
$all_reachable_and_self_many->(map { $successors->($_) } @$v);
|
116
|
|
|
|
|
|
|
|
117
|
1167
|
|
|
|
|
6339
|
push @todo, \@dst;
|
118
|
1167
|
|
|
|
|
3939
|
my $dst_s = join ' ', @dst;
|
119
|
1167
|
|
|
|
|
3749
|
$dfa->{$src_s}->{$k} = $dst_s;
|
120
|
1167
|
|
|
|
|
11219
|
$predecessors{$dst_s}->{$src_s}++;
|
121
|
|
|
|
|
|
|
}
|
122
|
|
|
|
|
|
|
}
|
123
|
|
|
|
|
|
|
|
124
|
30
|
|
|
|
|
63
|
my %reachable = do {
|
125
|
30
|
|
|
|
|
41
|
my %seen;
|
126
|
30
|
|
|
|
|
135
|
my @todo = @accepting_dfa_states;
|
127
|
30
|
|
|
|
|
115
|
while (@todo) {
|
128
|
1241
|
|
|
|
|
1628
|
my $c = pop @todo;
|
129
|
1241
|
100
|
|
|
|
5282
|
next if $seen{$c}++;
|
130
|
355
|
|
|
|
|
521
|
push @todo, keys %{ $predecessors{$c} };
|
|
355
|
|
|
|
|
1958
|
|
131
|
|
|
|
|
|
|
}
|
132
|
30
|
|
|
|
|
143
|
map { $_ => 1 } keys %seen;
|
|
355
|
|
|
|
|
834
|
|
133
|
|
|
|
|
|
|
};
|
134
|
|
|
|
|
|
|
|
135
|
30
|
|
|
|
|
283
|
my $o = Data::AutoBimap->new(start => 0);
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Ensure that DFA state 0 is the one that corresponds to no
|
138
|
|
|
|
|
|
|
# vertices in the input graph. This is an API convention and
|
139
|
|
|
|
|
|
|
# does not have significance beyond that.
|
140
|
30
|
|
|
|
|
600
|
my $r = { $o->s2n('') => {
|
141
|
|
|
|
|
|
|
Combines => [],
|
142
|
|
|
|
|
|
|
Accepts => $accepting->()
|
143
|
|
|
|
|
|
|
} };
|
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# Ensure start state is 1 also as a convention
|
146
|
30
|
|
|
|
|
137
|
$o->s2n($start_s);
|
147
|
|
|
|
|
|
|
|
148
|
30
|
|
|
|
|
403
|
while (my ($src, $x) = each %$dfa) {
|
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# Merge dead states
|
151
|
377
|
100
|
|
|
|
1208
|
$src = '' unless $reachable{$src};
|
152
|
|
|
|
|
|
|
|
153
|
377
|
|
|
|
|
1902
|
my @src_combines = map { $m->n2s($_) } split/ /, $src;
|
|
3411
|
|
|
|
|
18780
|
|
154
|
377
|
|
100
|
|
|
4221
|
$r->{$o->s2n($src)}{Combines} //= \@src_combines;
|
155
|
0
|
|
50
|
|
|
0
|
$r->{$o->s2n($src)}{Combines} = [ sort { $a cmp $b }
|
|
22
|
|
|
|
|
119
|
|
156
|
377
|
100
|
|
|
|
4332
|
uniq (@{$r->{$o->s2n($src)}{Combines} // []}, @src_combines) ]
|
157
|
|
|
|
|
|
|
if $src eq '';
|
158
|
|
|
|
|
|
|
|
159
|
377
|
|
100
|
|
|
1326
|
$r->{$o->s2n($src)}{Accepts} //=
|
160
|
|
|
|
|
|
|
0 + $accepting->(split/ /, $src);
|
161
|
|
|
|
|
|
|
|
162
|
377
|
|
|
|
|
2083
|
while (my ($k, $dst) = each %{$x}) {
|
|
1544
|
|
|
|
|
26693
|
|
163
|
|
|
|
|
|
|
|
164
|
1167
|
100
|
|
|
|
2955
|
$dst = '' unless $reachable{$dst};
|
165
|
|
|
|
|
|
|
|
166
|
1167
|
|
|
|
|
3001
|
$r->{$o->s2n($src)}{NextOver}{$k} = $o->s2n($dst);
|
167
|
|
|
|
|
|
|
|
168
|
1167
|
100
|
100
|
|
|
17661
|
if ((not defined $r->{$o->s2n($dst)}{Combines}) or $dst eq '') {
|
169
|
333
|
|
|
|
|
4035
|
my @dst_combines = map { $m->n2s($_) } split/ /, $dst;
|
|
2203
|
|
|
|
|
12461
|
|
170
|
333
|
|
100
|
|
|
10924
|
$r->{$o->s2n($dst)}{Combines} //= \@dst_combines;
|
171
|
0
|
|
50
|
|
|
0
|
$r->{$o->s2n($dst)}{Combines} = [ sort { $a cmp $b }
|
|
120
|
|
|
|
|
312
|
|
172
|
333
|
100
|
|
|
|
3711
|
uniq (@{$r->{$o->s2n($dst)}{Combines} // []}, @dst_combines) ]
|
173
|
|
|
|
|
|
|
if $dst eq '';
|
174
|
|
|
|
|
|
|
}
|
175
|
|
|
|
|
|
|
|
176
|
1167
|
|
100
|
|
|
12270
|
$r->{$o->s2n($dst)}{Accepts} //=
|
177
|
|
|
|
|
|
|
0 + $accepting->(split/ /, $dst);
|
178
|
|
|
|
|
|
|
}
|
179
|
|
|
|
|
|
|
}
|
180
|
|
|
|
|
|
|
|
181
|
30
|
|
|
|
|
2946
|
return $r;
|
182
|
|
|
|
|
|
|
}
|
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
sub construct_dfa {
|
185
|
30
|
|
|
30
|
1
|
24080394
|
my (%o) = @_;
|
186
|
|
|
|
|
|
|
|
187
|
30
|
50
|
|
|
|
199
|
die unless ref $o{is_nullable};
|
188
|
30
|
50
|
33
|
|
|
258
|
die unless ref $o{is_accepting} or exists $o{final};
|
189
|
30
|
50
|
|
|
|
143
|
die unless ref $o{successors};
|
190
|
30
|
50
|
|
|
|
126
|
die unless ref $o{get_label};
|
191
|
30
|
50
|
|
|
|
110
|
die unless exists $o{start};
|
192
|
30
|
50
|
33
|
|
|
127
|
die if ref $o{is_accepting} and exists $o{final};
|
193
|
|
|
|
|
|
|
|
194
|
30
|
50
|
|
|
|
322
|
if (exists $o{final}) {
|
195
|
30
|
|
|
|
|
43
|
my %in_final = map { $_ => 1 } @{ $o{final} };
|
|
30
|
|
|
|
|
243
|
|
|
30
|
|
|
|
|
80
|
|
196
|
|
|
|
|
|
|
$o{is_accepting} = sub {
|
197
|
785
|
|
|
785
|
|
21061
|
grep { $in_final{$_} } @_
|
|
6853
|
|
|
|
|
13032
|
|
198
|
30
|
|
|
|
|
238
|
};
|
199
|
|
|
|
|
|
|
}
|
200
|
|
|
|
|
|
|
|
201
|
30
|
|
|
|
|
160
|
_get_graph($o{start}, $o{get_label}, $o{is_nullable},
|
202
|
|
|
|
|
|
|
$o{successors}, $o{is_accepting});
|
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
}
|
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
1;
|
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
__END__
|