File Coverage

blib/lib/Algorithm/SAT/Expression.pm
Criterion Covered Total %
statement 49 50 98.0
branch 4 6 66.6
condition n/a
subroutine 12 12 100.0
pod 5 7 71.4
total 70 75 93.3


line stmt bran cond sub pod time code
1             package Algorithm::SAT::Expression;
2 6     6   18416 use 5.008001;
  6         16  
  6         202  
3 6     6   24 use strict;
  6         6  
  6         160  
4 6     6   19 use warnings;
  6         7  
  6         198  
5             require Algorithm::SAT::Backtracking;
6 6     6   23 use Carp qw(croak);
  6         6  
  6         2704  
7             our $VERSION = "0.12";
8              
9             # Boolean expression builder. Note that the connector for clauses is `OR`;
10             # so, when calling the instance methods `xor`, `and`, and `or`, the clauses
11             # you're generating are `AND`ed with the existing clauses in the expression.
12             sub new {
13 25     25 0 35625 return bless {
14             _literals => {},
15             _expr => [],
16             _implementation => "Algorithm::SAT::Backtracking"
17             },
18             shift;
19             }
20              
21             sub with {
22 20     20 1 33 my $self = shift;
23 20 50       1380 if ( eval "require $_[0];1;" ) {
24 20         61 $self->{_implementation} = shift; $self->{_implementation}->import();
  20         115  
25             }
26             else {
27 0         0 croak "The '$_[0]' could not be loaded";
28             }
29 20         72 return $self;
30             }
31              
32             # ### or
33             # Add a clause consisting of the provided literals or'ed together.
34             sub or {
35 50     50 1 180 my $self = shift;
36 50         84 $self->_ensure(@_);
37 50         59 push( @{ $self->{_expr} }, [@_] );
  50         94  
38 50         81 return $self;
39             }
40              
41             # ### xor
42             # Add clauses causing each of the provided arguments to be xored.2
43             sub xor {
44              
45             # This first clause is the 'or' portion. "One of them must be true."
46 10     10 1 1299 my $self = shift;
47 10         25 my @literals = @_;
48 10         13 push( @{ $self->{_expr} }, \@_ );
  10         24  
49 10         24 $self->_ensure(@literals);
50              
51             # Then, we generate clauses such that "only one of them is true".
52 10         38 for ( my $i = 0; $i <= $#literals; $i++ ) {
53 25         65 for ( my $j = $i + 1; $j <= $#literals; $j++ ) {
54 20         46 push(
55 20         20 @{ $self->{_expr} },
56             [ $self->negate_literal( $literals[$i] ),
57             $self->negate_literal( $literals[$j] )
58             ]
59             );
60             }
61             }
62 10         24 return $self;
63             }
64              
65             # ### and
66             # Add each of the provided literals into their own clause in the expression.
67             sub and {
68 10     10 1 43 my $self = shift;
69 10         28 $self->_ensure(@_);
70 10         24 push( @{ $self->{_expr} }, [$_] ) for @_;
  15         58  
71 10         23 return $self;
72             }
73              
74             # ### solve
75             # Solve this expression with the backtrack solver. Lazy-loads the solver.
76             sub solve {
77 10     10 1 5620 return $_[0]->{_implementation}
78             ->new->solve( $_[0]->{_variables}, $_[0]->{_expr} );
79             }
80              
81             # ### _ensure
82             # Private method that ensures that a particular literal is marked as being in
83             # the expression.
84             sub _ensure {
85 70     70   71 my $self = shift;
86 165         314 do {
87 110         183 $self->{_literals}->{$_} = 1;
88 110         89 push( @{ $self->{_variables} }, $_ );
  110         240  
89             }
90 70 100       111 for grep { !$self->{_literals}->{$_} }
  165         376  
91             map { substr( $_, 0, 1 ) eq "-" ? substr( $_, 1 ) : $_ } @_;
92             }
93              
94             sub negate_literal {
95 40     40 0 62 my $self = shift;
96 40         38 my $var = shift;
97              
98 40 50       331 return ( substr( $var, 0, 1 ) eq "-" )
99             ? substr( $var, 1 )
100             : '-' . $var;
101             }
102              
103             1;
104             __END__