namero 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/namero.rb +1 -0
- data/lib/namero/board.rb +124 -49
- data/lib/namero/solver.rb +20 -13
- data/lib/namero/solver_extensions.rb +50 -0
- data/lib/namero/value.rb +1 -1
- data/lib/namero/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 027c27f9e7b9a2f8e9662c961c907c4cd38c1c4f6d5461bbd4ad03673604b605
|
4
|
+
data.tar.gz: 31fe4354d07c60114a714bc33a36f3800984d76ec66f78566d2bebd3d8b80dc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 478c5e53b2d38163b353cf97ae127bdc70699dc00f3010c7be1dfcb07f70479fed3ef8d0bd468c7c1d2e1ebcc772e4cd0bed853bcfd01876c10958b4a9ff228a
|
7
|
+
data.tar.gz: 2846d3036c4be2449d30d39c0ecc2a206dea9a67ee3483a8457950b8bbdb1b65958d8fce579061bd38c776f6ef5dc7b0a05dacf8ea37249b9a3ad5347bb2a64e
|
data/lib/namero.rb
CHANGED
data/lib/namero/board.rb
CHANGED
@@ -2,46 +2,60 @@ module Namero
|
|
2
2
|
class Board
|
3
3
|
attr_reader :n
|
4
4
|
|
5
|
+
# ..4.
|
6
|
+
# ...1
|
7
|
+
# 4...
|
8
|
+
# 21..
|
9
|
+
def self.load_from_string(str, n)
|
10
|
+
values = str.split("\n").map(&:chars).flatten.map.with_index do |v, idx|
|
11
|
+
if v == '.'
|
12
|
+
Namero::Value.new(value: nil, candidates: (1..n).to_a, index: idx)
|
13
|
+
else
|
14
|
+
Namero::Value.new(value: Integer(v), candidates: [Integer(v)], index: idx)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
Namero::Board.new(n: n, values: values)
|
18
|
+
end
|
19
|
+
|
20
|
+
# [
|
21
|
+
# nil, nil, 4, nil,
|
22
|
+
# nil, nil, nil, 1,
|
23
|
+
# 4, nil, nil, nil,
|
24
|
+
# 2, 1, nil, nil,
|
25
|
+
# ]
|
26
|
+
def self.load_from_array(array, n)
|
27
|
+
values = array.map.with_index do |v, idx|
|
28
|
+
if v
|
29
|
+
Namero::Value.new(value: v, candidates: [v], index: idx)
|
30
|
+
else
|
31
|
+
Namero::Value.new(value: nil, candidates: (1..n).to_a, index: idx)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
Namero::Board.new(n: n, values: values)
|
35
|
+
end
|
36
|
+
|
5
37
|
# n: Integer
|
6
38
|
# values: Array<Integer>
|
7
39
|
def initialize(n:, values: Array.new(n**2))
|
8
40
|
raise ArgumentError if values.size != n**2
|
9
41
|
@n = n
|
10
42
|
@values = values
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
n.times do |i|
|
22
|
-
@values[start+i] = value[i]
|
23
|
-
end
|
24
|
-
when :column
|
25
|
-
x = idx % n
|
26
|
-
n.times do |i|
|
27
|
-
@values[i * n + x] = value[i]
|
28
|
-
end
|
29
|
-
when :block
|
30
|
-
x = idx % n
|
31
|
-
y = idx / n
|
32
|
-
start_x = x / root_n * root_n
|
33
|
-
start_y = y / root_n * root_n
|
34
|
-
|
35
|
-
value_idx = 0
|
43
|
+
@rows = Array.new(n) do |i|
|
44
|
+
start = i * n
|
45
|
+
values[start...start+n]
|
46
|
+
end
|
47
|
+
@columns = Array.new(n) do |x|
|
48
|
+
Array.new(n) { |i| values[i * n + x] }
|
49
|
+
end
|
50
|
+
@blocks = Array.new(n) do |i|
|
51
|
+
idx = i % root_n * root_n + (i / root_n) * n * root_n
|
52
|
+
block = []
|
36
53
|
root_n.times do |y_offset|
|
37
54
|
root_n.times do |x_offset|
|
38
|
-
|
39
|
-
self[start_x + x_offset + (start_y + y_offset) * n] = value[value_idx]
|
40
|
-
value_idx += 1
|
55
|
+
block << values[idx + x_offset + y_offset * n]
|
41
56
|
end
|
42
57
|
end
|
43
|
-
|
44
|
-
raise "Unknown type: #{type}"
|
58
|
+
block
|
45
59
|
end
|
46
60
|
end
|
47
61
|
|
@@ -50,25 +64,12 @@ module Namero
|
|
50
64
|
when :index
|
51
65
|
@values[idx]
|
52
66
|
when :row
|
53
|
-
|
54
|
-
@values[start...start+@n]
|
67
|
+
@rows[idx / n]
|
55
68
|
when :column
|
56
69
|
x = idx % n
|
57
|
-
@
|
70
|
+
@columns[x]
|
58
71
|
when :block
|
59
|
-
|
60
|
-
y = idx / n
|
61
|
-
start_x = x / root_n * root_n
|
62
|
-
start_y = y / root_n * root_n
|
63
|
-
|
64
|
-
[].tap do |res|
|
65
|
-
root_n.times do |y_offset|
|
66
|
-
root_n.times do |x_offset|
|
67
|
-
|
68
|
-
res << self[start_x + x_offset + (start_y + y_offset) * n]
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
+
@blocks[idx / n / root_n * root_n + idx % n / root_n]
|
72
73
|
else
|
73
74
|
raise "Unknown type: #{type}"
|
74
75
|
end
|
@@ -86,15 +87,89 @@ module Namero
|
|
86
87
|
end
|
87
88
|
end
|
88
89
|
|
90
|
+
def each_affected_group(&block)
|
91
|
+
return enum_for(__method__) unless block_given?
|
92
|
+
|
93
|
+
@rows.each(&block)
|
94
|
+
@columns.each(&block)
|
95
|
+
@blocks.each(&block)
|
96
|
+
end
|
97
|
+
|
89
98
|
def complete?
|
90
|
-
@values.all? do |v|
|
99
|
+
all_filled = @values.all? do |v|
|
91
100
|
v.value
|
92
101
|
end
|
102
|
+
return false unless all_filled
|
103
|
+
|
104
|
+
return each_affected_group.all? do |group|
|
105
|
+
group.map(&:value).uniq.size == n
|
106
|
+
end
|
93
107
|
end
|
94
108
|
|
95
|
-
|
109
|
+
def to_s(mode: :simple)
|
110
|
+
out = +""
|
111
|
+
case mode
|
112
|
+
when :simple
|
113
|
+
@values.each.with_index do |v, idx|
|
114
|
+
if v.value
|
115
|
+
out << (v.value).to_s
|
116
|
+
else
|
117
|
+
out << '.'
|
118
|
+
end
|
119
|
+
out << "\n" if idx % n == n - 1
|
120
|
+
end
|
121
|
+
when :with_candidates
|
122
|
+
@values.each.each_slice(n).with_index do |values, index|
|
123
|
+
values.each_slice(root_n) do |values2|
|
124
|
+
values2.each do |v|
|
125
|
+
if v.value
|
126
|
+
out << '***'
|
127
|
+
else
|
128
|
+
out << (v.candidates.include?(1) ? '1' : '.')
|
129
|
+
out << (v.candidates.include?(2) ? '2' : '.')
|
130
|
+
out << (v.candidates.include?(3) ? '3' : '.')
|
131
|
+
end
|
132
|
+
out << ' '
|
133
|
+
end
|
134
|
+
out << ' | '
|
135
|
+
end
|
136
|
+
out << "\n"
|
137
|
+
values.each_slice(root_n) do |values2|
|
138
|
+
values2.each do |v|
|
139
|
+
if v.value
|
140
|
+
out << "*#{v.value}*"
|
141
|
+
else
|
142
|
+
out << (v.candidates.include?(4) ? '4' : '.')
|
143
|
+
out << (v.candidates.include?(5) ? '5' : '.')
|
144
|
+
out << (v.candidates.include?(6) ? '6' : '.')
|
145
|
+
end
|
146
|
+
out << ' '
|
147
|
+
end
|
148
|
+
out << ' | '
|
149
|
+
end
|
150
|
+
out << "\n"
|
151
|
+
values.each_slice(root_n) do |values2|
|
152
|
+
values2.each do |v|
|
153
|
+
if v.value
|
154
|
+
out << '***'
|
155
|
+
else
|
156
|
+
out << (v.candidates.include?(7) ? '7' : '.')
|
157
|
+
out << (v.candidates.include?(8) ? '8' : '.')
|
158
|
+
out << (v.candidates.include?(9) ? '9' : '.')
|
159
|
+
end
|
160
|
+
out << ' '
|
161
|
+
end
|
162
|
+
out << ' | '
|
163
|
+
end
|
164
|
+
out << "\n#{'-' * ((root_n + 1) * n + (root_n) * 3) if index % root_n == root_n - 1}\n"
|
165
|
+
end
|
166
|
+
else
|
167
|
+
raise "unknown mode: #{mode}"
|
168
|
+
end
|
169
|
+
out
|
170
|
+
end
|
96
171
|
|
97
|
-
def root_n
|
172
|
+
private def root_n
|
98
173
|
Integer.sqrt(n)
|
99
174
|
end
|
100
175
|
end
|
data/lib/namero/solver.rb
CHANGED
@@ -2,25 +2,28 @@ require 'set'
|
|
2
2
|
|
3
3
|
module Namero
|
4
4
|
class Solver
|
5
|
-
def initialize(board)
|
5
|
+
def initialize(board, extensions: [])
|
6
6
|
@board = board
|
7
7
|
@updated_candidate_queue = Set.new
|
8
|
+
@extensions = extensions
|
8
9
|
end
|
9
10
|
|
10
11
|
def solve
|
11
12
|
fill_candidates
|
12
13
|
loop do
|
13
|
-
idx =
|
14
|
-
|
14
|
+
idx = updated_candidate_queue.first
|
15
|
+
unless idx
|
16
|
+
extensions.each do |ex|
|
17
|
+
ex.solve(board, updated_candidate_queue)
|
18
|
+
end
|
19
|
+
break if updated_candidate_queue.empty?
|
20
|
+
next
|
21
|
+
end
|
15
22
|
@updated_candidate_queue.delete idx
|
16
23
|
fill_one_candidate(idx)
|
17
24
|
end
|
18
25
|
end
|
19
26
|
|
20
|
-
private
|
21
|
-
|
22
|
-
attr_reader :board, :updated_candidate_queue
|
23
|
-
|
24
27
|
def fill_one_candidate(idx)
|
25
28
|
v = board[idx]
|
26
29
|
return if v.candidates.size != 1
|
@@ -28,23 +31,27 @@ module Namero
|
|
28
31
|
fill_candidate_for(v)
|
29
32
|
end
|
30
33
|
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :board, :updated_candidate_queue, :extensions
|
37
|
+
|
31
38
|
def fill_candidate_for(v)
|
32
39
|
board[v.index, :row].each do |v2|
|
33
40
|
unless v2.value
|
34
|
-
v2.candidates
|
35
|
-
updated_candidate_queue << v2.index
|
41
|
+
changed = v2.candidates.delete(v.value)
|
42
|
+
updated_candidate_queue << v2.index if changed
|
36
43
|
end
|
37
44
|
end
|
38
45
|
board[v.index, :column].each do |v2|
|
39
46
|
unless v2.value
|
40
|
-
v2.candidates
|
41
|
-
updated_candidate_queue << v2.index
|
47
|
+
changed = v2.candidates.delete(v.value)
|
48
|
+
updated_candidate_queue << v2.index if changed
|
42
49
|
end
|
43
50
|
end
|
44
51
|
board[v.index, :block].each do |v2|
|
45
52
|
unless v2.value
|
46
|
-
v2.candidates
|
47
|
-
updated_candidate_queue << v2.index
|
53
|
+
changed = v2.candidates.delete(v.value)
|
54
|
+
updated_candidate_queue << v2.index if changed
|
48
55
|
end
|
49
56
|
end
|
50
57
|
v.candidates = [v.value]
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Namero
|
2
|
+
module SolverExtensions
|
3
|
+
# If a candidate number exists only one place in an affected group,
|
4
|
+
# we can fill the box.
|
5
|
+
# For example:
|
6
|
+
# When candidates are: [1 2 3] [2 3] [1 2 3] [1 2 3 4]
|
7
|
+
# The last box's value is 4 because there is 4 candidate only the last box.
|
8
|
+
class CandidateExistsOnlyOnePlace
|
9
|
+
def self.solve(board, queue)
|
10
|
+
self.new(board, queue).solve
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(board, queue)
|
14
|
+
@board = board
|
15
|
+
@queue = queue
|
16
|
+
end
|
17
|
+
|
18
|
+
def solve
|
19
|
+
board.each_affected_group do |group|
|
20
|
+
table = []
|
21
|
+
group.each do |value|
|
22
|
+
next if value.value
|
23
|
+
value.candidates.each do |c|
|
24
|
+
if table[c].nil?
|
25
|
+
table[c] = value
|
26
|
+
elsif table[c]
|
27
|
+
table[c] = false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
table.each.with_index do |value, idx|
|
33
|
+
if value
|
34
|
+
value.candidates = [idx]
|
35
|
+
queue << value.index
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :board, :queue
|
44
|
+
|
45
|
+
def n
|
46
|
+
board.n
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/namero/value.rb
CHANGED
data/lib/namero/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: namero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Masataka Pocke Kuwabara
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,6 +68,7 @@ files:
|
|
68
68
|
- lib/namero.rb
|
69
69
|
- lib/namero/board.rb
|
70
70
|
- lib/namero/solver.rb
|
71
|
+
- lib/namero/solver_extensions.rb
|
71
72
|
- lib/namero/value.rb
|
72
73
|
- lib/namero/version.rb
|
73
74
|
- namero.gemspec
|
@@ -89,8 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
90
|
- !ruby/object:Gem::Version
|
90
91
|
version: '0'
|
91
92
|
requirements: []
|
92
|
-
|
93
|
-
rubygems_version: 2.7.6
|
93
|
+
rubygems_version: 3.2.0.pre1
|
94
94
|
signing_key:
|
95
95
|
specification_version: 4
|
96
96
|
summary: ''
|