Rubyで数独(ナンプレ)を解く(解決編)
4月 26th, 2008 admin Posted in Ruby, 今日のコード | 2 Comments »
※このコードでは解けない問題があることがわかったのでコードを書き直しました。
新しいコードはこちら「Rubyで数独(ナンプレ)を解く(本当の解決編)」
前回はそもそもナンプレを1度しかやったことがないこともあってルールを間違えており解けませんでした。
今回やっと解決編としてナンプレを解くコードを掲載します。
アルゴリズム
(1)全マスについて候補を求め、候補リスト(配列)を作成
(2)全マスについて、そのマスに関係するマスの候補リストから置ける数字を調べる
(3)(2)をマス数回行う < -このへんが頭悪い感じが出ているので改良候補
コード
RUBY:
-
class Numpla
-
def initialize(sheet)
-
@sheet = sheet
-
@size = Math.sqrt(sheet.size).to_i
-
@side_size = Math.sqrt(@size).to_i
-
@possible = Array.new
-
end
-
-
def resolve
-
81.times{
-
(0...@sheet.length).each do |i|
-
if @sheet[i] == 0
-
@possible[i] = get_possibles(get_neighbors(i))
-
else
-
@possible[i] = []
-
end
-
end
-
-
(0...@sheet.length).each do |i|
-
if @possible[i].size> 0
-
if @possible[i].size == 1
-
@sheet[i] = @possible[i].pop
-
else
-
@sheet[i] = get_answer(i,get_neighbors(i))
-
end
-
end
-
end
-
}
-
@sheet
-
end
-
-
def get_answer(index,neighbors)
-
answer = @possible[index]
-
possibles = Array.new
-
neighbors.each do |i|
-
possibles |= @possible[i]
-
end
-
possibles.each do |v|
-
answer.delete(v)
-
end
-
result = (answer.size == 1) ? answer.pop : 0;
-
return result
-
end
-
-
def get_neighbors(index)
-
get_neighbors_h(index) | get_neighbors_v(index) | get_neighbors_b(index)
-
end
-
-
def get_neighbors_h(index)
-
neighbors = Array.new
-
horizone = index / @size * @size
-
@size.times {|i|
-
neighbors.push(i + horizone)
-
}
-
neighbors.delete(index)
-
neighbors
-
end
-
-
def get_neighbors_v(index)
-
neighbors = Array.new
-
vertical = index % @size
-
@size.times {|i|
-
neighbors.push(@size * i + vertical);
-
}
-
neighbors.delete(index)
-
neighbors
-
end
-
-
-
def get_neighbors_b(x)
-
tf = x / @side_size * @side_size
-
offset = tf / @size % @side_size
-
fc = tf - @size * offset
-
-
neighbors = Array.new
-
(0..(@side_size-1)).each do |j|
-
(fc+@size*j..fc+@size*j+(@side_size-1)).each do |i| neighbors.push(i) end
-
end
-
-
neighbors
-
end
-
-
def get_possibles(neighbors)
-
possibles = Array.new
-
(1..@size).each{|i| possibles.push i }
-
neighbors.each do |i|
-
possibles.delete(@sheet[i])
-
end
-
-
possibles
-
end
-
end
-
-
sheet = Array.new
-
sheet.concat [3,7,0,6,2,0,0,0,1]
-
sheet.concat [0,0,8,0,5,0,0,6,9]
-
sheet.concat [4,0,0,0,7,0,0,8,0]
-
sheet.concat [0,6,0,0,0,4,0,3,8]
-
sheet.concat [8,0,0,0,3,0,0,0,6]
-
sheet.concat [5,2,0,0,8,6,0,9,0]
-
sheet.concat [0,4,0,0,1,0,0,0,3]
-
sheet.concat [9,3,0,0,6,0,8,0,0]
-
sheet.concat [6,0,0,0,4,2,0,7,5]
-
width = 9
-
height= 9
-
-
-
foo = Numpla.new(sheet)
-
-
result = foo.resolve
-
-
count = 1
-
result.each do |x|
-
print x
-
print ","
-
print "\n" unless count % 9> 0
-
count += 1
-
end
-
class Numpla
-
def initialize(sheet)
-
@sheet = sheet
-
@size = Math.sqrt(sheet.size).to_i
-
@side_size = Math.sqrt(@size).to_i
-
@possible = Array.new
-
end
-
-
def resolve
-
81.times{
-
(0...@sheet.length).each do |i|
-
if @sheet[i] == 0
-
@possible[i] = get_possibles(get_neighbors(i))
-
else
-
@possible[i] = []
-
end
-
end
-
-
(0...@sheet.length).each do |i|
-
if @possible[i].size> 0
-
if @possible[i].size == 1
-
@sheet[i] = @possible[i].pop
-
else
-
@sheet[i] = get_answer(i,get_neighbors(i))
-
end
-
end
-
end
-
}
-
@sheet
-
end
-
-
def get_answer(index,neighbors)
-
answer = @possible[index]
-
possibles = Array.new
-
neighbors.each do |i|
-
possibles |= @possible[i]
-
end
-
possibles.each do |v|
-
answer.delete(v)
-
end
-
result = (answer.size == 1) ? answer.pop : 0;
-
return result
-
end
-
-
def get_neighbors(index)
-
get_neighbors_h(index) | get_neighbors_v(index) | get_neighbors_b(index)
-
end
-
-
def get_neighbors_h(index)
-
neighbors = Array.new
-
horizone = index / @size * @size
-
@size.times {|i|
-
neighbors.push(i + horizone)
-
}
-
neighbors.delete(index)
-
neighbors
-
end
-
-
def get_neighbors_v(index)
-
neighbors = Array.new
-
vertical = index % @size
-
@size.times {|i|
-
neighbors.push(@size * i + vertical);
-
}
-
neighbors.delete(index)
-
neighbors
-
end
-
-
-
def get_neighbors_b(x)
-
tf = x / @side_size * @side_size
-
offset = tf / @size % @side_size
-
fc = tf - @size * offset
-
-
neighbors = Array.new
-
(0..(@side_size-1)).each do |j|
-
(fc+@size*j..fc+@size*j+(@side_size-1)).each do |i| neighbors.push(i) end
-
end
-
-
neighbors
-
end
-
-
def get_possibles(neighbors)
-
possibles = Array.new
-
(1..@size).each{|i| possibles.push i }
-
neighbors.each do |i|
-
possibles.delete(@sheet[i])
-
end
-
-
possibles
-
end
-
end
-
-
sheet = Array.new
-
sheet.concat [3,7,0,6,2,0,0,0,1]
-
sheet.concat [0,0,8,0,5,0,0,6,9]
-
sheet.concat [4,0,0,0,7,0,0,8,0]
-
sheet.concat [0,6,0,0,0,4,0,3,8]
-
sheet.concat [8,0,0,0,3,0,0,0,6]
-
sheet.concat [5,2,0,0,8,6,0,9,0]
-
sheet.concat [0,4,0,0,1,0,0,0,3]
-
sheet.concat [9,3,0,0,6,0,8,0,0]
-
sheet.concat [6,0,0,0,4,2,0,7,5]
-
width = 9
-
height= 9
-
-
-
foo = Numpla.new(sheet)
-
-
result = foo.resolve
-
-
count = 1
-
result.each do |x|
-
print x
-
print ","
-
print "\n" unless count % 9> 0
-
count += 1
-
end
出力

まとめ
コードを書いていてとても楽しかった。仕事ではWebアプリを書くことが多いのですが、Webアプリではアルゴリズムを自分で考えるということがあまりないんだなというのを改めて思い知らされました。ショッピングサイトでない限り掛け算や割り算はおろか足し算引き算もしなかったりしますからね。
とりあえず問題を解けるようになったので、まずは他のナンプレの解法をみて比較したり改良してもっとRubyっぽくしてみたいと思います。
4月 26th, 2008 at 1:43:04
[...] #追記 解決編として正しいソースコードを掲載しました [...]
4月 27th, 2008 at 2:11:13
[...] 前回で攻略したかと思われたナンプレですが、常に候補が1つに絞られたマスというのが存在するという仮定で作っていたため解けない問題があるということがわかりました。(そんな問題があるって知らなかったんです。) [...]