Algorithm-ConstructDFA2
view release on metacpan or search on metacpan
lib/Algorithm/ConstructDFA2.pm view on Meta::CPAN
sub _init_matches {
my ($self) = @_;
$self->_dbh->do(q{
INSERT INTO Match(vertex, input)
SELECT Vertex.value, Input.value
FROM
Vertex CROSS JOIN Input
WHERE
_vertex_matches(Vertex.value, Input.value)+0 = 1
ORDER BY Vertex.value, Input.value
});
}
sub _init_epsilon_closure {
my ($self) = @_;
$self->_dbh->do(q{
INSERT INTO Closure(root, e_reachable)
WITH RECURSIVE all_e_successors_and_self(root, v) AS (
SELECT value AS root, value AS v FROM vertex
UNION
SELECT r.root, Edge.dst
FROM Edge
INNER JOIN all_e_successors_and_self AS r
ON (Edge.src = r.v)
INNER JOIN Vertex AS src_vertex
ON (Edge.src = src_vertex.value)
WHERE src_vertex.is_nullable
)
SELECT root, v FROM all_e_successors_and_self
ORDER BY root, v
});
}
sub _vertex_str_from_vertices {
my ($self, @vertices) = @_;
return $self->_json->encode([
nsort_by { $_ } uniq(grep { defined } @vertices)
]);
}
sub _vertex_str_to_vertices {
my ($self, $vertex_str) = @_;
return @{ $self->_json->decode($vertex_str) };
}
sub _find_state_id_by_vertex_str {
my ($self, $vertex_str) = @_;
my $sth = $self->_dbh->prepare(q{
SELECT state_id FROM State WHERE vertex_str = ?
});
return $self->_dbh->selectrow_array($sth, {}, $vertex_str);
}
sub _find_or_create_state_from_vertex_str {
my ($self, $vertex_str) = @_;
my $state_id = _find_state_id_by_vertex_str($self, $vertex_str);
return $state_id if defined $state_id;
$self->_dbh->begin_work();
my $sth = $self->_dbh->prepare(q{
INSERT INTO State(vertex_str) VALUES (?)
});
$sth->execute($vertex_str);
$state_id = $self->_dbh->sqlite_last_insert_rowid();
$self->_dbh->commit();
return $state_id;
}
sub _vertex_str_from_partial_list {
my ($self, @vertices) = @_;
return $self->_vertex_str_from_vertices() unless @vertices;
my $escaped_roots = join ", ", map {
$self->_dbh->quote($_)
} @vertices;
my ($vertex_str) = $self->_dbh->selectrow_array(qq{
SELECT _canonical(json_group_array(closure.e_reachable))
FROM Closure
WHERE root IN ($escaped_roots)
});
return $vertex_str;
}
sub find_or_create_state_id {
my ($self, @vertices) = @_;
my $vertex_str = _vertex_str_from_partial_list($self, @vertices);
return _find_or_create_state_from_vertex_str($self, $vertex_str);
}
sub vertices_in_state {
my ($self, $state_id) = @_;
return map { @$_ } $self->_dbh->selectall_array(q{
SELECT vertex FROM Configuration WHERE state = ?
}, {}, $state_id);
}
sub cleanup_dead_states {
my ($self, $vertices_accept) = @_;
$self->_dbh->sqlite_create_function( '_vertices_accept', 1, sub {
my @vertices = $self->_vertex_str_to_vertices(@_);
return !! $vertices_accept->(@vertices);
});
$self->_dbh->begin_work();
$self->_dbh->do(q{
CREATE TEMPORARY TABLE accepting AS
SELECT state_id AS state
FROM State
WHERE _vertices_accept(vertex_str)+0 = 1
});
my @accepting = map { @$_ } $self->_dbh->selectall_array(q{
SELECT state FROM accepting
});
# NOTE: this also renames states in transitions involving
# possible start states, but they would then simply have no
# transitions, which should be fine.
$self->_dbh->do(q{
WITH RECURSIVE all_living(state) AS (
SELECT state FROM accepting
UNION
SELECT src AS state
FROM Transition
INNER JOIN all_living
ON (Transition.dst = all_living.state)
)
UPDATE Transition
SET dst = ?
WHERE dst NOT IN (SELECT state FROM all_living)
}, {}, $self->dead_state_id);
$self->_dbh->do(q{
DROP TABLE accepting;
});
$self->_dbh->commit();
# TODO: is there a better way to drop the function?
$self->_dbh->sqlite_create_function( '_vertices_accept', 1, undef );
return @accepting;
}
sub compute_some_transitions {
my ($self, $limit) = @_;
$limit //= 1_000;
my $sth = $self->_dbh->prepare_cached(q{
SELECT
s.state_id AS src
, i.value AS input
, _canonical(json_group_array(closure.e_reachable))
AS dst_vertex_str
FROM
state s
CROSS JOIN input i
LEFT JOIN configuration c
ON (s.state_id = c.state)
LEFT JOIN match m
ON (m.vertex = c.vertex AND m.input = i.value)
LEFT JOIN edge
ON (m.vertex = edge.src)
LEFT JOIN closure
ON (edge.dst = closure.root)
LEFT JOIN transition t
ON (t.src = s.state_id AND t.input = i.value)
WHERE
t.dst IS NULL
GROUP BY
s.state_id, i.rowid
ORDER BY
s.state_id, i.rowid
LIMIT ?
});
my @new = $self->_dbh->selectall_array($sth, {}, $limit);
my $find_or_create = memoize(sub {
_find_or_create_state_from_vertex_str($self, @_);
});
my $sth2 = $self->_dbh->prepare(q{
INSERT INTO Transition(src, input, dst) VALUES (?, ?, ?)
});
my @transitions;
for my $t (@new) {
push @transitions, [(
$t->[0],
$t->[1],
$find_or_create->($t->[2]),
)];
}
$self->_dbh->begin_work();
$sth2->execute(@$_) for @transitions;
$self->_dbh->commit();
return scalar @new;
}
sub transitions_as_3tuples {
my ($self) = @_;
return $self->_dbh->selectall_array(q{
SELECT src, input, dst FROM transition
});
}
sub transitions_as_5tuples {
my ($self) = @_;
return $self->_dbh->selectall_array(q{
SELECT * FROM view_transitions_as_5tuples
});
}
sub backup_to_file {
my ($self, $schema_version, $file) = @_;
die unless $schema_version eq 'v0';
$self->_dbh->sqlite_backup_to_file($file);
}
# sub backup_to_dbh {
# my ($self, $schema_version) = @_;
#
# die unless $schema_version eq 'v0';
#
# require File::Temp;
#
# my ($fh, $filename) = File::Temp::tempfile();
#
# $self->_dbh->sqlite_backup_to_file($filename);
#
# my $dbh = DBI->connect('dbi:SQLite:dbname=:memory:');
#
# $dbh->sqlite_backup_from_file($filename);
#
# File::Temp::unlink0($fh, $filename);
#
# undef $fh;
#
# return $dbh;
# }
1;
__END__
=head1 NAME
Algorithm::ConstructDFA2 - Deterministic finite automaton construction
=head1 SYNOPSIS
use Algorithm::ConstructDFA2;
my $dfa = Algorithm::ConstructDFA2->new(
input_alphabet => [ @symbols ],
input_vertices => [ qw/ 2 3 4 / ],
input_edges => [ [ 2, 3 ], [ 3, 4 ] ],
vertex_nullable => sub($vertex) { ... },
vertex_matches => sub($vertex, $input) { ... },
storage_dsn => 'dbi:SQLite:dbname=...',
);
my $start_id = $dfa->find_or_create_state_id(qw/ 2 /);
while (my $count = $dfa->compute_some_transitions(1_000)) {
...
}
( run in 2.687 seconds using v1.01-cache-2.11-cpan-ceb78f64989 )