File Coverage

blib/lib/Algorithm/X/DLX.pm
Criterion Covered Total %
statement 45 87 51.7
branch 8 32 25.0
condition 4 12 33.3
subroutine 11 18 61.1
pod 0 14 0.0
total 68 163 41.7


line stmt bran cond sub pod time code
1             package Algorithm::X::DLX;
2              
3 1     1   440 use strict;
  1         2  
  1         26  
4 1     1   4 use warnings;
  1         1  
  1         77  
5              
6             our $VERSION = '0.03';
7              
8             require 5.06.0;
9              
10 1     1   410 use Algorithm::X::LinkedMatrix;
  1         2  
  1         870  
11              
12             sub new {
13 1     1 0 3 my ($class, $problem) = @_;
14 1         6 return bless { A_ => Algorithm::X::LinkedMatrix->new($problem), iterator => undef }, $class;
15             }
16              
17             sub count_solutions {
18 1     1 0 8 my $self = shift;
19              
20 1         3 my $options = Options();
21 1         1 $options->{get_solutions} = 0;
22              
23 1         4 return $self->search($options)->{number_of_solutions};
24             }
25              
26             sub find_solutions {
27 0     0 0 0 my ($self, $max) = @_;
28              
29 0         0 my $options = Options();
30 0 0       0 $options->{max_solutions} = $max if defined $max;
31              
32 0         0 return $self->search($options)->{solutions};
33             }
34              
35             sub search {
36 1     1 0 6 my ($self, $options) = @_;
37 1   33     3 $options ||= Options();
38            
39 1 50       3 if ($options->{random_engine}) {
40 0         0 die "The option to select a random engine has been removed in Perl";
41             }
42              
43 1         5 my $result = { profile => [], number_of_solutions => 0, solutions => [] };
44 1   33     6 $self->{iterator} ||= $self->get_solver($options->{choose_random_column}, $result->{profile});
45              
46 1         2 while (my $solution = $self->{iterator}() ) {
47 0         0 $result->{number_of_solutions}++;
48 0 0       0 if ($options->{get_solutions}) {
49 0         0 push @{$result->{solutions}}, $solution;
  0         0  
50             }
51 0 0       0 last if $result->{number_of_solutions} >= $options->{max_solutions};
52             }
53            
54 1         8 return $result;
55             }
56              
57             sub next_solution {
58 0     0 0 0 my $self = shift;
59              
60 0         0 return $self->{iterator}();
61             }
62              
63             sub get_solver {
64 1     1 0 2 my ($self, $random_column, $profile) = @_;
65              
66 1         3 my $h = $self->{A_}->root_id();
67 1         2 my @placed = ();
68 1         1 my $level = 0;
69 1         2 my @state_stack = ([undef, undef]);
70              
71             return sub {
72             # brought back on track by by Antti Ajanki, Tom Boothby at https://github.com/sagemath/sage/blob/develop/src/sage/combinat/dlx.py
73              
74 1     1   8 while ( $level >= 0 ) {
75 1         2 my ($c, $r) = @{$state_stack[$level]};
  1         2  
76              
77 1 50       3 if ( not $c ) {
    0          
78 1 50       3 ++$profile->[ @placed ] if $profile;
79              
80 1 50       3 if ($self->R($h) == $h) {
81             # base case ( no columns left )
82 0         0 $level--;
83 0         0 return [ @placed ];
84              
85             } else {
86             # fetch remaining columns that share the same, lowest node count at present
87 1         2 my @cs = ();
88 1         2 for (my $j = $self->R($h); $j != $h; $j = $self->R($j)) {
89 1 50 33     2 if (@cs && $self->S($j) < $self->S($cs[0])) {
90 0         0 @cs = ();
91             }
92 1 50 33     14 push @cs, $j if !@cs || $self->S($j) == $self->S($cs[0]);
93             }
94              
95 1 50       21 die "No columns found" if !@cs;
96            
97 1 50       4 if ($self->S($cs[0]) < 1) {
98 1         31 $level--;
99 1         7 next;
100             }
101              
102 0 0       0 $c = $random_column ? ($cs[int rand @cs]) : $cs[0];
103              
104 0         0 $self->cover_column($c);
105 0         0 $state_stack[$level] = [$c, $c];
106             }
107              
108             } elsif ($self->D($r) != $c) {
109              
110 0 0       0 if ($c != $r) {
111 0         0 pop @placed;
112 0         0 for (my $j = $self->L($r); $j != $r; $j = $self->L($j) ) {
113 0         0 $self->uncover_column($j);
114             }
115             }
116              
117 0         0 $r = $self->D($r);
118 0         0 $placed[$level] = $self->Y($r);
119 0         0 for (my $j = $self->R($r); $j != $r; $j = $self->R($j)) {
120 0         0 $self->cover_column($j);
121             }
122              
123 0         0 $state_stack[$level] = [$c, $r];
124 0         0 $level++;
125              
126 0 0       0 if (@state_stack == $level) {
127 0         0 push @state_stack, [undef, undef];
128             } else {
129 0         0 $state_stack[$level] = [undef, undef];
130             }
131              
132             } else {
133 0 0       0 if ($c != $r) {
134 0         0 pop @placed;
135              
136 0         0 for (my $j = $self->L($r); $j != $r; $j = $self->L($j) ) {
137 0         0 $self->uncover_column($j);
138             }
139             }
140 0         0 $self->uncover_column($c);
141 0         0 $level--;
142             }
143             }
144 1         11 };
145             }
146              
147             sub Options {
148             return {
149 1     1 0 4 choose_random_column => 0,
150             get_solutions => 1,
151             max_solutions => ~0,
152             # random_engine => undef,
153             }
154             }
155              
156             # acquire some matrix methods
157 0     0 0 0 sub cover_column { return shift()->{A_}->cover_column(@_) }
158 0     0 0 0 sub uncover_column { return shift()->{A_}->uncover_column(@_) }
159 0     0 0 0 sub Y { return shift()->{A_}->Y(@_) }
160 1     1 0 4 sub S { return shift()->{A_}->S(@_) }
161 3     3 0 8 sub R { return shift()->{A_}->R(@_) }
162 0     0 0   sub L { return shift()->{A_}->L(@_) }
163 0     0 0   sub D { return shift()->{A_}->D(@_) }
164              
165             1;
166              
167             __END__