File Coverage

blib/lib/Algorithm/SAT/Expression.pm
Criterion Covered Total %
statement 48 49 97.9
branch 4 6 66.6
condition n/a
subroutine 12 12 100.0
pod 5 7 71.4
total 69 74 93.2


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