はじめてのRuby
Rubyを勉強しているよ! ifとforと配列とメソッドの書き方を覚えたよ!
週末の新聞にナンバープレース(数独)の問題が載っていたので、それを解くプログラムを書くことにした。お題はこれ。
5 | 7 | |||||||
6 | 1 | 2 | 9 | |||||
7 | 2 | 6 | 3 | |||||
2 | 3 | 1 | 8 | |||||
4 | 8 | 7 | 2 | |||||
7 | 5 | 3 | ||||||
6 | 9 | |||||||
5 | 8 | 3 | 6 | 1 | 7 | |||
4 | 9 | 8 | 5 |
データの持ち方は迷ったが、ひとまず単純に81要素の配列にする。
init = [nil, 5, nil, nil, nil, nil, nil, 7, nil, 6, nil, nil, 1, nil, 2, nil, nil, 9, nil, 7, 2, nil, nil, nil, 6, 3, nil, 2, 3, nil, nil, nil, nil, nil, 1, 8, nil, nil, 4, 8, nil, 7, 2, nil, nil, 7, nil, nil, nil, 5, nil, nil, nil, 3, nil, 6, nil, nil, nil, nil, nil, 9, nil, 5, nil, 8, 3, nil, 6, 1, nil, 7, 4, nil, nil, 9, nil, 8, nil, nil, 5]
デバッグしやすいように、配列を9×9で表示するメソッドを書く。
def show_table(data) for i in 0..8 if i == 3 || i == 6 then puts " +===+===+===++===+===+===++===+===+===+ " else puts " +---+---+---++---+---+---++---+---+---+ " end for j in 0..8 if j == 3 || j == 6 then print " || " else print " | " end print data[i * 9 + j] == nil ? " " : data[i * 9 + j] end puts " |" end puts " +---+---+---++---+---+---++---+---+---+ " puts end
出力はこんな感じ。(aa記法を使ってもなぜか整形が崩れる・・・)
+---+---+---++---+---+---++---+---+---+ | | 5 | || | | || | 7 | | +---+---+---++---+---+---++---+---+---+ | 6 | | || 1 | | 2 || | | 9 | +---+---+---++---+---+---++---+---+---+ | | 7 | 2 || | | || 6 | 3 | | +===+===+===++===+===+===++===+===+===+ | 2 | 3 | || | | || | 1 | 8 | +---+---+---++---+---+---++---+---+---+ | | | 4 || 8 | | 7 || 2 | | | +---+---+---++---+---+---++---+---+---+ | 7 | | || | 5 | || | | 3 | +===+===+===++===+===+===++===+===+===+ | | 6 | || | | || | 9 | | +---+---+---++---+---+---++---+---+---+ | 5 | | 8 || 3 | | 6 || 1 | | 7 | +---+---+---++---+---+---++---+---+---+ | 4 | | || 9 | | 8 || | | 5 | +---+---+---++---+---+---++---+---+---+
あとは解く。解き方はいくつか考えたが、問題は9×9と限られているので、力づくでバックトラック。
def check_row(table, position, value) for i in 0..8 if table[position / 9 * 9 + i] == value then return false end end return true end def check_column(table, position, value) for i in 0..8 if table[i * 9 + position % 9] == value then return false end end return true end def check_block(table, position, value) upper_left = position / 27 * 27 + position % 9 / 3 * 3 for i in 0..8 if table[upper_left + i / 3 * 9 + i % 3] == value then return false end end return true end def proceed(init, result, position) if position > 80 then return true end if init[position] != nil then result[position] = init[position] return proceed(init, result, position + 1) else for i in 1..9 if check_row(result, position, i) && check_column(result, position, i) && check_block(result, position, i) then result[position] = i if proceed(init, result, position + 1) then return true end end end result[position] = nil return false end end show_table(init) result = [] for i in 0..80 result[i] = init[i] end if proceed(init, result, 0) then show_table(result) else puts "no answer" end
いちおう答えは出る。
+---+---+---++---+---+---++---+---+---+ | 1 | 5 | 9 || 6 | 8 | 3 || 4 | 7 | 2 | +---+---+---++---+---+---++---+---+---+ | 6 | 4 | 3 || 1 | 7 | 2 || 5 | 8 | 9 | +---+---+---++---+---+---++---+---+---+ | 8 | 7 | 2 || 5 | 9 | 4 || 6 | 3 | 1 | +===+===+===++===+===+===++===+===+===+ | 2 | 3 | 5 || 4 | 6 | 9 || 7 | 1 | 8 | +---+---+---++---+---+---++---+---+---+ | 9 | 1 | 4 || 8 | 3 | 7 || 2 | 5 | 6 | +---+---+---++---+---+---++---+---+---+ | 7 | 8 | 6 || 2 | 5 | 1 || 9 | 4 | 3 | +===+===+===++===+===+===++===+===+===+ | 3 | 6 | 1 || 7 | 2 | 5 || 8 | 9 | 4 | +---+---+---++---+---+---++---+---+---+ | 5 | 9 | 8 || 3 | 4 | 6 || 1 | 2 | 7 | +---+---+---++---+---+---++---+---+---+ | 4 | 2 | 7 || 9 | 1 | 8 || 3 | 6 | 5 | +---+---+---++---+---+---++---+---+---+
しょぼいっす・・・。
- 配列result[]を初期化するとき、もっと簡単にコピーする方法がありそう
- check_row、check_column、check_blockのロジックはほとんど同じなので、ブロックを使うと1つにできそう
- forでループを回すのがRubyっぽくないような気がする
- proceed()でinitを引きずりまわさないで済む方法があるはず
- バックトラックするときにわざわざresult[]にnilをセットし直しているのがスマートに見えない
- そもそもアルゴリズムがしょぼい
勉強しながら書き直してみよう。
この日記を読んだ人へのお願い。他にもしょぼいところがあれば教えてください。でも、コードの書き方は決して教えないでください。