| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Games::Sudoku::PatternSolver::CPLogic; |
|
2
|
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
1069
|
use strict; |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
176
|
|
|
4
|
4
|
|
|
4
|
|
19
|
use warnings; |
|
|
4
|
|
|
|
|
1964
|
|
|
|
4
|
|
|
|
|
418
|
|
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
require 5.06.0; |
|
7
|
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
26
|
use Bit::Vector; |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
293
|
|
|
9
|
4
|
|
|
4
|
|
2363
|
use Algorithm::Combinatorics qw(combinations); |
|
|
4
|
|
|
|
|
18205
|
|
|
|
4
|
|
|
|
|
11633
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# array with 81 hashes representing the fields; field->{covers} returns Bit::Vector with constraint coverage field indexes set (row, column and block in one) |
|
12
|
|
|
|
|
|
|
my $Fields = Games::Sudoku::PatternSolver::Patterns::init_fields(); |
|
13
|
|
|
|
|
|
|
# an array of 27 arrays, holding the 9 indexes sharing any row, column or box |
|
14
|
|
|
|
|
|
|
my $Groups = Games::Sudoku::PatternSolver::Patterns::build_groups(); |
|
15
|
|
|
|
|
|
|
my $Group_Vectors = [map {my $v = Bit::Vector->new(81); $v->Index_List_Store(@$_); $v} @$Groups]; |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# an array of 54 (9x6) arrays with 3 Bit::Vector(s) each: [intersection of box with row or column], [remainder of box], [remainder of row or column] |
|
18
|
|
|
|
|
|
|
my $Intersections = Games::Sudoku::PatternSolver::Patterns::get_intersections(); |
|
19
|
|
|
|
|
|
|
# to be initialized with each symbol's candidates map as a Bit::Vector |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# ugly: a 2nd data structure with the candidates, that has to be created and maintained |
|
22
|
|
|
|
|
|
|
# only for use by advanced logic (locked_sets, locked_candidates) |
|
23
|
|
|
|
|
|
|
my %Candidate_Vectors = (); |
|
24
|
|
|
|
|
|
|
my $steps_taken; |
|
25
|
|
|
|
|
|
|
my $RECORD_STEPS = 0; |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub apply_logic { |
|
28
|
102
|
|
|
102
|
0
|
324
|
my ($puzzle) = @_; |
|
29
|
|
|
|
|
|
|
|
|
30
|
102
|
|
|
|
|
343
|
my $puzzle_string = $puzzle->{strPuzzle}; |
|
31
|
102
|
|
|
|
|
3152
|
$puzzle_string =~ s/[^1-9]/0/g; |
|
32
|
|
|
|
|
|
|
|
|
33
|
102
|
|
|
|
|
13402
|
$_->{candidates} = {}, $_->{value} = undef for @$Fields; # clear Field objects |
|
34
|
|
|
|
|
|
|
|
|
35
|
102
|
|
|
|
|
381
|
my $index_arrays_by_symbol = {}; # symbol -> array with all fields (offset) where the symbol is the given value |
|
36
|
102
|
|
|
|
|
240
|
my $coverage_vectors_by_symbol = {}; # do we really need this? currently only for populating and deleting the candidates |
|
37
|
|
|
|
|
|
|
|
|
38
|
102
|
|
|
|
|
278
|
my $missing_count = 81; |
|
39
|
102
|
|
|
|
|
283
|
my $field_index = -1; |
|
40
|
102
|
|
|
|
|
1452
|
foreach my $symbol (split //, $puzzle_string) { |
|
41
|
8262
|
|
|
|
|
11999
|
$field_index++; |
|
42
|
8262
|
100
|
|
|
|
21088
|
$symbol =~ /[1-9]/ or next; |
|
43
|
3493
|
|
|
|
|
4913
|
$missing_count--; |
|
44
|
3493
|
|
|
|
|
4747
|
push @{$index_arrays_by_symbol->{$symbol}}, $field_index; |
|
|
3493
|
|
|
|
|
7437
|
|
|
45
|
3493
|
|
|
|
|
7320
|
$Fields->[$field_index]{value} = $symbol; |
|
46
|
|
|
|
|
|
|
} |
|
47
|
|
|
|
|
|
|
|
|
48
|
102
|
|
|
|
|
1266
|
foreach my $symbol (keys %$index_arrays_by_symbol) { |
|
49
|
904
|
|
|
|
|
2657
|
my $covers = Bit::Vector->new(81); |
|
50
|
904
|
|
|
|
|
7996
|
$covers->Or($covers, $Fields->[$_]{covers}) for $puzzle->{symbolVectors}{$symbol}->Index_List_Read(); |
|
51
|
904
|
|
|
|
|
2468
|
$coverage_vectors_by_symbol->{$symbol} = $covers; |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
102
|
|
|
|
|
551
|
foreach my $symbol (1..9) { |
|
55
|
|
|
|
|
|
|
# rem: $coverage_vectors_by_symbol used only here and in put_value(), would be nice to drop |
|
56
|
918
|
|
66
|
|
|
3231
|
my $area_vector = $coverage_vectors_by_symbol->{$symbol} ||= Bit::Vector->new(81); |
|
57
|
918
|
|
|
|
|
3125
|
my $candidate_vector = Bit::Vector->new(81); |
|
58
|
|
|
|
|
|
|
|
|
59
|
918
|
|
|
|
|
2037
|
foreach my $field (@$Fields) { |
|
60
|
74358
|
100
|
|
|
|
153263
|
next if $field->{value}; |
|
61
|
42921
|
100
|
|
|
|
109882
|
unless ($area_vector->bit_test($field->{offset})) { |
|
62
|
14791
|
|
|
|
|
28924
|
$field->{candidates}{$symbol} = 1; |
|
63
|
14791
|
|
|
|
|
33965
|
$candidate_vector->Bit_On($field->{offset}); |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
} |
|
66
|
918
|
|
|
|
|
4976
|
$Candidate_Vectors{$symbol} = $candidate_vector; |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
|
|
69
|
102
|
|
|
|
|
232
|
my $filled_total = 0; |
|
70
|
102
|
|
|
|
|
213
|
my $dropped_total = 0; |
|
71
|
102
|
|
|
|
|
252
|
my $dropping_candidates_did_help = 0; |
|
72
|
102
|
|
|
|
|
271
|
$steps_taken = []; |
|
73
|
|
|
|
|
|
|
|
|
74
|
102
|
|
|
|
|
406
|
while ($missing_count) { |
|
75
|
370
|
|
|
|
|
802
|
my $last_filled = 0; |
|
76
|
|
|
|
|
|
|
|
|
77
|
370
|
|
|
|
|
1307
|
$last_filled += hidden_singles($puzzle); |
|
78
|
370
|
|
|
|
|
1506
|
$last_filled += naked_singles($puzzle); |
|
79
|
|
|
|
|
|
|
|
|
80
|
370
|
100
|
66
|
|
|
1472
|
unless ($last_filled) { |
|
81
|
|
|
|
|
|
|
#### last; # skip further efforts to reduce the candidates (advanced methods) |
|
82
|
|
|
|
|
|
|
|
|
83
|
118
|
|
|
|
|
292
|
my $last_dropped = 0; |
|
84
|
|
|
|
|
|
|
|
|
85
|
118
|
|
|
|
|
501
|
$last_dropped += locked_sets($puzzle); |
|
86
|
118
|
|
|
|
|
519
|
$last_dropped += locked_candidates($puzzle); |
|
87
|
|
|
|
|
|
|
|
|
88
|
118
|
100
|
|
|
|
502
|
$last_dropped or last; |
|
89
|
|
|
|
|
|
|
|
|
90
|
39
|
|
|
|
|
133
|
$dropped_total += $last_dropped; |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
} elsif ($dropped_total) { |
|
93
|
|
|
|
|
|
|
$dropping_candidates_did_help = 1; |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
|
|
96
|
291
|
|
|
|
|
544
|
$filled_total += $last_filled; |
|
97
|
291
|
|
|
|
|
994
|
$missing_count -= $last_filled; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
102
|
|
|
|
|
284
|
my $puzzle_string_2; |
|
101
|
102
|
100
|
|
|
|
373
|
if ($filled_total) { |
|
102
|
99
|
100
|
|
|
|
465
|
$puzzle_string_2 = join '', map {$Fields->[$_]{value} || '.'} (0..80); |
|
|
8019
|
|
|
|
|
23972
|
|
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# are 8 symbols completely filled in and 1 symbol entirely missing? |
|
105
|
99
|
50
|
66
|
|
|
1084
|
if ($missing_count == 9 && $puzzle->{uniqueGivens} == 8) { |
|
106
|
0
|
|
|
|
|
0
|
for (1..9) { |
|
107
|
0
|
0
|
|
|
|
0
|
next if $puzzle_string_2 =~ /$_/; |
|
108
|
|
|
|
|
|
|
|
|
109
|
0
|
|
|
|
|
0
|
my $added = $puzzle_string_2 =~ s/\./$_/g; |
|
110
|
0
|
|
|
|
|
0
|
$filled_total += $added; |
|
111
|
0
|
|
|
|
|
0
|
$missing_count -= $added; |
|
112
|
|
|
|
|
|
|
# did not yet find a sudoku where that happend |
|
113
|
0
|
|
|
|
|
0
|
warn "'$puzzle_string': Logic mode finished by adding '$_' as the last symbol.\n"; |
|
114
|
|
|
|
|
|
|
|
|
115
|
0
|
|
|
|
|
0
|
last; |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
} |
|
118
|
|
|
|
|
|
|
|
|
119
|
99
|
|
|
|
|
467
|
$puzzle->{strPuzzleAfterLogic} = $puzzle_string_2; |
|
120
|
|
|
|
|
|
|
} |
|
121
|
|
|
|
|
|
|
|
|
122
|
102
|
|
|
|
|
409
|
$puzzle->{logicFilled} = $filled_total; |
|
123
|
102
|
|
|
|
|
450
|
$puzzle->{candidatesDropped} = $dropped_total; |
|
124
|
102
|
|
|
|
|
476
|
$puzzle->{candidateVectors} = \%Candidate_Vectors; |
|
125
|
102
|
|
|
|
|
377
|
$puzzle->{droppingCandidatesDidHelp} = $dropping_candidates_did_help; |
|
126
|
102
|
|
|
|
|
370
|
$puzzle->{logicSteps} = $steps_taken; |
|
127
|
|
|
|
|
|
|
|
|
128
|
102
|
100
|
|
|
|
492
|
if ($missing_count) { |
|
|
|
100
|
|
|
|
|
|
|
129
|
79
|
|
|
|
|
274
|
$puzzle->{logicSolved} = 0; |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
} elsif ($filled_total) { |
|
132
|
|
|
|
|
|
|
|
|
133
|
22
|
|
|
|
|
55
|
$puzzle->{logicSolved} = 1; |
|
134
|
22
|
|
|
|
|
88
|
push @{$puzzle->{solutions}}, $puzzle_string_2; |
|
|
22
|
|
|
|
|
100
|
|
|
135
|
22
|
|
|
|
|
62
|
$puzzle->{solutionCount} = 1; |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
} else { |
|
138
|
1
|
50
|
|
|
|
4
|
print "Puzzle was no puzzle!\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
|
|
141
|
102
|
|
|
|
|
3089
|
return $puzzle |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub hidden_singles { |
|
145
|
370
|
|
|
370
|
0
|
907
|
my ($puzzle) = @_; |
|
146
|
|
|
|
|
|
|
|
|
147
|
370
|
50
|
|
|
|
1114
|
print "hidden_singles()\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
148
|
|
|
|
|
|
|
|
|
149
|
370
|
|
|
|
|
976
|
my $symbol_counts = $puzzle->{symbolCounts}; |
|
150
|
370
|
|
|
|
|
767
|
my $filled = 0; |
|
151
|
|
|
|
|
|
|
# moving symbols with most givens upfront probably wouldn't yield |
|
152
|
370
|
|
|
|
|
1073
|
foreach my $symbol (1..9) { |
|
153
|
3330
|
100
|
100
|
|
|
17394
|
next if (exists($symbol_counts->{$symbol}) && ($symbol_counts->{$symbol} == 9)); |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
GROUP: |
|
156
|
2614
|
|
|
|
|
5145
|
foreach my $g (@$Groups) { |
|
157
|
70578
|
|
|
|
|
101927
|
my $only_position = undef; |
|
158
|
70578
|
|
|
|
|
116526
|
foreach my $field_index (@$g) { |
|
159
|
520410
|
100
|
|
|
|
1192046
|
next unless exists $Fields->[$field_index]{candidates}{$symbol}; |
|
160
|
61332
|
100
|
|
|
|
126138
|
next GROUP if defined $only_position; |
|
161
|
31946
|
|
|
|
|
51752
|
$only_position = $field_index; |
|
162
|
|
|
|
|
|
|
} |
|
163
|
|
|
|
|
|
|
|
|
164
|
41192
|
100
|
|
|
|
89554
|
if (defined $only_position) { |
|
165
|
2560
|
|
|
|
|
8004
|
put_value($only_position, $symbol, $puzzle); |
|
166
|
2560
|
|
|
|
|
4149
|
$filled++; |
|
167
|
2560
|
50
|
|
|
|
7405
|
push @$steps_taken, 'HS_V_' . $symbol . '_' . Games::Sudoku::PatternSolver::Patterns::field_coordinates($only_position) |
|
168
|
|
|
|
|
|
|
if $RECORD_STEPS; |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
|
|
173
|
370
|
|
|
|
|
1390
|
return $filled; |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
sub naked_singles { |
|
177
|
370
|
|
|
370
|
0
|
1029
|
my ($puzzle) = @_; |
|
178
|
|
|
|
|
|
|
|
|
179
|
370
|
50
|
|
|
|
1054
|
print "naked_singles()\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
180
|
|
|
|
|
|
|
|
|
181
|
370
|
|
|
|
|
924
|
my $filled = 0; |
|
182
|
370
|
|
|
|
|
978
|
foreach my $field (@$Fields) { |
|
183
|
29970
|
100
|
|
|
|
64111
|
$field->{value} and next; |
|
184
|
9419
|
|
|
|
|
13477
|
my @candidates = keys %{$field->{candidates}}; |
|
|
9419
|
|
|
|
|
23459
|
|
|
185
|
9419
|
100
|
|
|
|
23041
|
if (@candidates == 1) { |
|
186
|
649
|
|
|
|
|
1414
|
my $field_index = $field->{offset}; |
|
187
|
649
|
|
|
|
|
1276
|
my $symbol = shift @candidates; |
|
188
|
649
|
|
|
|
|
2022
|
put_value($field_index, $symbol, $puzzle); |
|
189
|
649
|
|
|
|
|
1019
|
$filled++; |
|
190
|
649
|
50
|
|
|
|
1967
|
push @$steps_taken, 'NS_V_' . $symbol . '_' . Games::Sudoku::PatternSolver::Patterns::field_coordinates($field_index) |
|
191
|
|
|
|
|
|
|
if $RECORD_STEPS; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
} |
|
194
|
|
|
|
|
|
|
|
|
195
|
370
|
|
|
|
|
1018
|
return $filled; |
|
196
|
|
|
|
|
|
|
} |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Because the naked and hidden sets of candidates occuring within a group always are mutually complementary |
|
199
|
|
|
|
|
|
|
# we have to implement search for only one of the two types. |
|
200
|
|
|
|
|
|
|
# Going for the hidden pairs, triples and quads seems like it can be done more easily with our candidates and groups vectors. |
|
201
|
|
|
|
|
|
|
# example with a hidden quad set: 816573294392......4572.9..6941...5687854961236238...4.279.....1138....7.564....82 |
|
202
|
|
|
|
|
|
|
sub locked_sets { |
|
203
|
118
|
|
|
118
|
0
|
360
|
my ($puzzle) = @_; |
|
204
|
|
|
|
|
|
|
|
|
205
|
118
|
50
|
|
|
|
395
|
print "locked_sets()\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
206
|
|
|
|
|
|
|
|
|
207
|
118
|
|
|
|
|
335
|
my $symbol_counts = $puzzle->{symbolCounts}; |
|
208
|
118
|
|
|
|
|
290
|
my $dropped = 0; |
|
209
|
118
|
|
|
|
|
1064
|
my $test_vector = Bit::Vector->new(81); |
|
210
|
|
|
|
|
|
|
|
|
211
|
118
|
|
|
|
|
530
|
for (my $group_index = 0; $group_index < 27; $group_index++) { |
|
212
|
3186
|
|
|
|
|
24570
|
my $group_array = $Groups->[$group_index]; |
|
213
|
3186
|
|
|
|
|
6216
|
my $group_vector = $Group_Vectors->[$group_index]; |
|
214
|
|
|
|
|
|
|
|
|
215
|
3186
|
|
|
|
|
5499
|
my %candidate_counts = (); |
|
216
|
3186
|
|
|
|
|
4948
|
my %candidate_vectors = (); |
|
217
|
3186
|
|
|
|
|
5785
|
foreach my $field_index (@$group_array) { |
|
218
|
28674
|
100
|
|
|
|
66860
|
$Fields->[$field_index]{value} and next; |
|
219
|
9390
|
|
|
|
|
13149
|
foreach my $candidate_symbol (keys %{$Fields->[$field_index]{candidates}}) { |
|
|
9390
|
|
|
|
|
23795
|
|
|
220
|
27331
|
|
|
|
|
49384
|
$candidate_counts{$candidate_symbol}++; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# stay with candidates which occupy 2-4 cells in the group |
|
225
|
3186
|
|
|
|
|
6922
|
foreach my $candidate_symbol (keys %candidate_counts) { |
|
226
|
9390
|
|
|
|
|
14616
|
my $candidate_count = $candidate_counts{$candidate_symbol}; |
|
227
|
9390
|
100
|
100
|
|
|
26960
|
if ($candidate_count > 4 || $candidate_count == 1) { |
|
228
|
905
|
|
|
|
|
1907
|
delete $candidate_counts{$candidate_symbol}; |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
} else { |
|
231
|
8485
|
|
|
|
|
22008
|
my $candidate_in_group_vector = Bit::Vector->new(81); |
|
232
|
8485
|
|
|
|
|
25445
|
$candidate_in_group_vector->And($group_vector, $Candidate_Vectors{$candidate_symbol}); |
|
233
|
8485
|
|
|
|
|
17039
|
$candidate_vectors{$candidate_symbol} = $candidate_in_group_vector; |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
} |
|
236
|
3186
|
|
|
|
|
8003
|
my @candidates_in_group = keys %candidate_counts; |
|
237
|
3186
|
100
|
|
|
|
13278
|
next if @candidates_in_group < 4; |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# iterate over all possible combinations of the 2-4 different candidates found above |
|
240
|
1222
|
|
|
|
|
2265
|
foreach my $k_size (4, 3, 2) { |
|
241
|
3666
|
100
|
|
|
|
24298
|
next if @candidates_in_group <= $k_size; |
|
242
|
3151
|
|
|
|
|
8725
|
my $combination_iterator = combinations(\@candidates_in_group, $k_size); |
|
243
|
3151
|
|
|
|
|
136852
|
while (my $candidates_combination = $combination_iterator->next) { |
|
244
|
41546
|
|
|
|
|
373423
|
$test_vector->Empty(); |
|
245
|
41546
|
|
|
|
|
66068
|
foreach my $candidate (@$candidates_combination) { |
|
246
|
123863
|
|
|
|
|
257633
|
$test_vector->Or($test_vector, $candidate_vectors{$candidate}); |
|
247
|
|
|
|
|
|
|
} |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# do the k different candidates occupy exactly k cells? |
|
250
|
41546
|
|
|
|
|
97993
|
my @combined_fields = $test_vector->Index_List_Read(); |
|
251
|
41546
|
100
|
|
|
|
127524
|
if (@combined_fields == $k_size) { |
|
252
|
558
|
50
|
|
|
|
1468
|
print "group $group_index, hidden_set (@$candidates_combination) in cells: [@combined_fields]\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
253
|
|
|
|
|
|
|
# got a hidden set |
|
254
|
|
|
|
|
|
|
# now we must look for any other candidates in the same cells, which we can eliminate |
|
255
|
558
|
|
|
|
|
1051
|
my %hidden_candidates = (); |
|
256
|
558
|
|
|
|
|
1552
|
@hidden_candidates{@$candidates_combination} = undef; |
|
257
|
|
|
|
|
|
|
|
|
258
|
558
|
|
|
|
|
1010
|
foreach my $field_index (@combined_fields) { |
|
259
|
1430
|
|
|
|
|
2039
|
foreach my $candidate_symbol (keys %{$Fields->[$field_index]{candidates}}) { |
|
|
1430
|
|
|
|
|
4159
|
|
|
260
|
3565
|
100
|
|
|
|
9134
|
unless (exists $hidden_candidates{$candidate_symbol}) { |
|
261
|
356
|
|
|
|
|
951
|
drop_candidate($field_index, $candidate_symbol); |
|
262
|
356
|
|
|
|
|
525
|
$dropped++; |
|
263
|
356
|
50
|
|
|
|
982
|
push @$steps_taken, 'LS_D_' . $candidate_symbol . '_' . Games::Sudoku::PatternSolver::Patterns::field_coordinates($field_index) |
|
264
|
|
|
|
|
|
|
if $RECORD_STEPS; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
} |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
} |
|
271
|
|
|
|
|
|
|
} |
|
272
|
|
|
|
|
|
|
|
|
273
|
118
|
|
|
|
|
1351
|
return $dropped; |
|
274
|
|
|
|
|
|
|
} |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub locked_candidates { |
|
277
|
118
|
|
|
118
|
0
|
426
|
my ($puzzle) = @_; |
|
278
|
|
|
|
|
|
|
|
|
279
|
118
|
50
|
|
|
|
419
|
print "locked_candidates()\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
280
|
|
|
|
|
|
|
|
|
281
|
118
|
|
|
|
|
448
|
my $symbol_counts = $puzzle->{symbolCounts}; |
|
282
|
118
|
|
|
|
|
266
|
my $dropped = 0; |
|
283
|
118
|
|
|
|
|
447
|
my $in_box_vector = Bit::Vector->new(81); |
|
284
|
118
|
|
|
|
|
464
|
my $in_line_vector = Bit::Vector->new(81); |
|
285
|
|
|
|
|
|
|
|
|
286
|
118
|
|
|
|
|
382
|
foreach my $set (@$Intersections) { |
|
287
|
6372
|
|
|
|
|
15257
|
my ($intersection_vector, $boxremainder_vector, $lineremainder_vector) = @$set; |
|
288
|
|
|
|
|
|
|
|
|
289
|
6372
|
|
|
|
|
9870
|
my %candidates_in_intersection = (); |
|
290
|
6372
|
|
|
|
|
16735
|
foreach my $field_index ($intersection_vector->Index_List_Read()) { |
|
291
|
19116
|
|
|
|
|
31199
|
my $intersection_field = $Fields->[$field_index]; |
|
292
|
19116
|
|
|
|
|
28953
|
$candidates_in_intersection{$_}++ for keys %{$intersection_field->{candidates}}; |
|
|
19116
|
|
|
|
|
54912
|
|
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
6372
|
|
|
|
|
14740
|
foreach my $symbol (keys %candidates_in_intersection) { |
|
296
|
11833
|
|
|
|
|
19188
|
my $candidate_vector = $Candidate_Vectors{$symbol}; |
|
297
|
|
|
|
|
|
|
|
|
298
|
11833
|
|
|
|
|
31218
|
$in_box_vector->And($candidate_vector, $boxremainder_vector); |
|
299
|
11833
|
|
|
|
|
25080
|
my @further_box_matches = $in_box_vector->Index_List_Read(); |
|
300
|
|
|
|
|
|
|
|
|
301
|
11833
|
|
|
|
|
29500
|
$in_line_vector->And($candidate_vector, $lineremainder_vector); |
|
302
|
11833
|
|
|
|
|
25033
|
my @further_line_matches = $in_line_vector->Index_List_Read(); |
|
303
|
|
|
|
|
|
|
|
|
304
|
11833
|
100
|
100
|
|
|
64636
|
if (@further_box_matches && ! @further_line_matches) { |
|
|
|
100
|
100
|
|
|
|
|
|
305
|
|
|
|
|
|
|
# case of 'Claiming' |
|
306
|
49
|
|
|
|
|
99
|
foreach my $field_index (@further_box_matches) { |
|
307
|
84
|
|
|
|
|
216
|
drop_candidate($field_index, $symbol); |
|
308
|
84
|
|
|
|
|
147
|
$dropped++; |
|
309
|
84
|
50
|
|
|
|
276
|
push @$steps_taken, 'LC_D_' . $symbol . '_' . Games::Sudoku::PatternSolver::Patterns::field_coordinates($field_index) |
|
310
|
|
|
|
|
|
|
if $RECORD_STEPS; |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
} elsif (@further_line_matches && ! @further_box_matches) { |
|
314
|
|
|
|
|
|
|
# case of 'Pointing' aka 'Box / Line reduction' |
|
315
|
64
|
|
|
|
|
163
|
foreach my $field_index (@further_line_matches) { |
|
316
|
109
|
|
|
|
|
293
|
drop_candidate($field_index, $symbol); |
|
317
|
109
|
|
|
|
|
200
|
$dropped++; |
|
318
|
109
|
50
|
|
|
|
320
|
push @$steps_taken, 'LC_D_' . $symbol . '_' . Games::Sudoku::PatternSolver::Patterns::field_coordinates($field_index) |
|
319
|
|
|
|
|
|
|
if $RECORD_STEPS; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
} |
|
324
|
|
|
|
|
|
|
|
|
325
|
118
|
|
|
|
|
878
|
return $dropped; |
|
326
|
|
|
|
|
|
|
} |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
sub drop_candidate { |
|
329
|
549
|
|
|
549
|
0
|
1070
|
my ($index, $value) = @_; |
|
330
|
|
|
|
|
|
|
|
|
331
|
549
|
50
|
|
|
|
1240
|
print "Dropping candidate '$value' from $index\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
332
|
|
|
|
|
|
|
|
|
333
|
549
|
|
|
|
|
956
|
my $field = $Fields->[$index]; |
|
334
|
549
|
|
|
|
|
1156
|
delete $field->{candidates}{$value}; |
|
335
|
549
|
|
|
|
|
1646
|
$Candidate_Vectors{$value}->Bit_Off($index); |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
sub put_value { |
|
339
|
3209
|
|
|
3209
|
0
|
9719
|
my ($index, $value, $puzzle) = @_; |
|
340
|
|
|
|
|
|
|
|
|
341
|
3209
|
50
|
|
|
|
6964
|
print "Putting '$value' in $index\n" if $Games::Sudoku::PatternSolver::VERBOSE; |
|
342
|
|
|
|
|
|
|
|
|
343
|
3209
|
|
|
|
|
5716
|
my $field = $Fields->[$index]; |
|
344
|
3209
|
|
|
|
|
6593
|
$field->{value} = $value; |
|
345
|
3209
|
|
|
|
|
10290
|
$field->{candidates} = {}; |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# drop candidates |
|
348
|
3209
|
|
|
|
|
6391
|
my $area_vector = $field->{covers}; |
|
349
|
|
|
|
|
|
|
# same value candidates from covered fields |
|
350
|
3209
|
|
|
|
|
47186
|
delete $Fields->[$_]{candidates}{$value} for $area_vector->Index_List_Read(); |
|
351
|
|
|
|
|
|
|
# and also in the symbol's map of remaining candidates |
|
352
|
3209
|
|
|
|
|
56822
|
$Candidate_Vectors{$value}->Bit_Off($_) for $area_vector->Index_List_Read(); |
|
353
|
|
|
|
|
|
|
# and in all candidate maps the one occupied place |
|
354
|
3209
|
|
|
|
|
28013
|
$_->Bit_Off($index) for values %Candidate_Vectors; |
|
355
|
|
|
|
|
|
|
|
|
356
|
3209
|
|
|
|
|
11325
|
$puzzle->{symbolVectors}{$value}->Bit_On($index); |
|
357
|
3209
|
|
|
|
|
8460
|
$puzzle->{symbolCounts}{$value}++; |
|
358
|
|
|
|
|
|
|
} |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
1 |