昨日は、数独をVBAで解くための前段として、どのような形で解析すれば良いかについて検討してみた。
infoment.hatenablog.com
そこで今日からは、実際に解を求めるべく、コードの作成に挑戦する。
今回は、数独解析用に「Sudoku」クラスを作成した。
また、コメントを解説に変えているため、通常は無いほどの過剰コメントとなっている。
クラスモジュール(Sudoku)
モジュールレベル変数。
Option Explicit ' 各マスの情報を表した10桁の数(文字列)を格納するための配列。 Dim ArrAll(1 To 9, 1 To 9) As Variant ' 各マスの数値。 Dim ArrNum(1 To 9, 1 To 9) As Variant ' シート上の9×9の範囲。 Dim TableRange As Range
クラスの初期化。
Private Sub Class_Initialize() ' 画面更新の一時停止。 Application.ScreenUpdating = False End Sub
初期設定。
' 初期設定。 Public Sub init(table_range As Range) ' 9×9マスの範囲設定。 Set TableRange = table_range ' 初期値が空欄のマスは、分かり易くするため文字色を赤に変更。 Dim rng As Range For Each rng In TableRange If rng = vbNullString Then rng.Font.Color = 192 Else rng.Font.Color = vbBlack End If Next ' 配列の初期値をセット。 ' ArrAllは、各マスにどの数字が入るか入らないかを格納する。 ' 各値は10桁の数値で構成されており、各桁は0~2の値をとる。 ' ※0桁目は、数値の確定状況を表し、1~9桁目は数値が入るか ' 入らないかを表している。 ' 0:入らない(確定),1:入る(確定),2:未定。 ' 従って、空欄の初期値は2222222222となる。 Dim r As Long Dim c As Long Dim i As Long Dim v(9) As Variant For r = 1 To 9 For c = 1 To 9 If table_range.Cells(r, c) = vbNullString Then ArrAll(r, c) = "2222222222" ' 便宜上、空欄のマスには0をセットする。 ArrNum(r, c) = 0 Else v(0) = 1 For i = 1 To 9 If i = table_range.Cells(r, c) Then v(i) = 1 Else v(i) = 0 End If Next ArrAll(r, c) = Join(v, vbNullString) ArrNum(r, c) = table_range.Cells(r, c) End If Next Next End Sub
さて、ここからは各マスについて、どの値が入るのか入らないのか、一つずつ確認していく。確認する範囲は、以下の3通りだ。
- 行方向(9マス)
- 列方向(9マス)
- 所属する小範囲(3×3=9マス)
例えば、(5,5)は空欄であるため、ここに入りうる数値を考えてみる。
行方向
1,3,4,8は空欄であるため、該当する桁は0となる。
列方向
同様に、列方向について確認する。
小範囲
最後に、小範囲で確認する。
ここまで3回のチェックの中で、重複している数値が幾つかある。しかし、重複はこの際無視して、とにかく存在すれば0にする。
今回の例では確認の結果、5以外は全て0(=その数字になり得ないことが確定)となった。そこで、10桁の中に0が幾つあるか数えたくなったので、こちらの関数を作成した。
Private Function CountNumber(arr As Variant, target_number As Long) As Long Dim i As Long Dim counter As Long For i = LBound(arr) + 1 To UBound(arr) If arr(i) = target_number Then counter = counter + 1 End If Next CountNumber = counter End Function
以上をサブプロシージャに落とし込んだのが、↓ こちら。
Private Sub CheckNumber(r_target As Long, c_target As Long) ' 確定マスの場合、処理終了。 If Left(ArrAll(r_target, c_target), 1) = 1 Then Exit Sub Dim r As Long Dim c As Long Dim v(9) As Variant Dim i As Long ' 各桁置き換え用に、一旦配列化する。 For i = 0 To 9 v(i) = Mid(ArrAll(r_target, c_target), i + 1, 1) Next ' 行方向確認。 Dim Index As Long For c = 1 To 9 Index = ArrNum(r_target, c) If Index <> 0 Then v(Index) = 0 End If Next ' 列方向確認。 For r = 1 To 9 Index = ArrNum(r, c_target) If Index <> 0 Then v(Index) = 0 End If Next ' 3×3範囲確認。 Dim sr As Long sr = WorksheetFunction.RoundUp(r_target / 3, 0) * 3 - 2 Dim sc As Long sc = WorksheetFunction.RoundUp(c_target / 3, 0) * 3 - 2 For r = sr To sr + 2 For c = sc To sc + 2 Index = ArrNum(r, c) If Index <> 0 Then v(Index) = 0 End If Next Next ' 0の数が8個ならば、残った数で確定。 If CountNumber(v, 0) = 8 Then v(0) = 1 For i = 1 To 9 If v(i) <> 0 Then v(i) = 1 Exit For End If Next ArrNum(r_target, c_target) = i End If ArrAll(r_target, c_target) = Join(v, vbNullString) End Sub
これを、9×9マスで実行する。
Public Sub Analize() Dim r As Long Dim c As Long For r = 1 To 9 For c = 1 To 9 CheckNumber r, c Next Next End Sub
最後に、こちらで貼り付けなどを行う。
Private Sub Class_Terminate() TableRange = ArrNum TableRange.Replace 0, vbNullString Application.ScreenUpdating = True End Sub
標準モジュール
こちらでは、範囲をセットして解析を実行するだけ。
Sub 数独() Dim SDK As Sudoku Set SDK = New Sudoku SDK.init Range("A1:I9") SDK.Analize End Sub
先のサンプルで試してみると、5回の繰り返しで解析が完了した。
勿論、これだけで全ての問題が解けるわけではない。実際、現時点で解けるのは「入門編」レベルまで。某サイトの「初級編」すら、最終解に至ることができなかった。ここから更に、幾つかのロジックを追加する必要がある。
ということで、明日に続きます。
参考まで。