もりさんのプログラミング手帳

教えることは、二度学ぶこと

スポンサーリンク

【連載】VBAでナンプレ(数独)を解いてみよう⑥<解法3・列>

VBAでナンプレを解いてみよう【第6回目】です。

f:id:excel-accounting:20180511135041p:plain:w400

連載記事の一覧はこちら→【VBAナンプレ連載】


このブログはノンプログラマーによるノンプログラマーのためのやさしい解説付きです。

VBA初級者を想定した解説をしています。

f:id:excel-accounting:20180424002011p:plain:w150


解法3(列)でも引き続き登場する変数のおさらいです。

変数名
データ型
定義
PuzzleArea
Rangeオブジェクト パズル全体の9×9のエリア
BlockArea
Rangeオブジェクト PuzzleAreaの中の3×3エリア
r
Long型 9×9エリアの番号
c
Long型 9×9エリアの番号
r1
Long型 BlockArea始点セルの番号
c1
Long型 BlockArea始点セルの番号
num
Long型 1~9の数字

【解法3】セル探し(列)

【例題】

1列(赤枠)を1単位として解読します。

f:id:excel-accounting:20180614224406p:plain:w350

ナンプレのルールから、数字の1はA1セルにしか入らないことがわかります。

解読する範囲(単位)が異なるだけで、考え方は前回の解法3(行)とほぼ同じです。

  • 解法3(行):1を1単位として解読
  • 解法3(列):1を1単位として解読

f:id:excel-accounting:20180614224424p:plain:w370

イメージづくり

解き方のイメージを描いていきます。
これもほぼ解法3(行)と同じです。

f:id:excel-accounting:20180503203548p:plain:w150

解法3(列)を呼び出すタイミング

9×9エリアの現在地Cells(r, c)黄色セルのタイミング(列の先頭位置)で呼び出します。

f:id:excel-accounting:20180617071914p:plain:w400


呼び出す条件は行番号r=1となります。

事前チェック

解法3(列)が呼び出された時点で解読対象のが完成している(すべてのセルが埋まっている)場合、処理は不要です。

そこで、これから解読するの状態を最初にチェックし、

  • 完成している(空白セルなし)→解法3(列)終了
  • 完成していない(空白セルあり)→後続のチェック処理へ進む

とします。

チェック処理

解読対象のに1~9の各数字が存在するか順番に検索し、存在しない数字の場合、その数字が列の空白セルに代入可能かチェックします。

(下記例題の場合、黄色セル①~④)

f:id:excel-accounting:20180614224933p:plain:w350

単位で解読する場合の重複禁止範囲は、

  • 同じ行
  • 3×3エリア

です。

この禁止範囲に同じ数字が存在しなければ、その空白セルに代入可能と判定します。

f:id:excel-accounting:20180614225007p:plain:w350


代入可能という情報を保持させる変数を用意します。

変数名
データ型
定義
SetCnt
Long型 代入可能なセルの個数をカウントする
y
Long型 代入可能なセルの番号を保持する

※列番号はパブリック変数を使用するため不要

変数のイメージと使い方は解法3(行)と同じです。

代入可能な場合の処理は2つ

  • カウント変数を+1
  • 現在地の番号を格納
解答の判定

数字の代入先セルを「決定できるか・否か」の判定条件も同じです。

  • SetCnt>1の場合→代入先不明
  • SetCnt=1の場合→代入先決定

代入先のセルが決定できたら、[共通部品]AnswerSetを呼び出して、解答数字をセルに書き込みます。

次の数字のチェックに進む前に、カウント変数を初期化します。(カウントを0に戻す)

プログラム作成

ここからは具体的なプログラムを書いていきます。

呼び出す側の処理
If r = 1 Then Call Solution3_Col

解法3(列)のプロシージャ名はSolution3_Colとします。

9×9エリアにおける現在地の行番号rが1の場合に解法3(列)を呼び出します。

f:id:excel-accounting:20180608231701p:plain:w350

解法3(列)の各処理

これらはすべて解法3モジュールに記述します。

解読対象のに空白セルがあるか?

If WorksheetFunction.CountBlank(Range(Cells(1, c), Cells(9, c))) = 0 Then
    Exit Sub
End If

解読対象ののセル範囲はRange(Cells(1, c), Cells(9, c))です。

CountBlank関数で空白セルをチェックし、0の場合、その時点で解法3(列)は終了とします。


【ループ1】num=1~9まで繰り返す

(省略)


解読対象のにnumが存在するか?

If isExistNum(1, c, 9, c) = False Then
    '~処理~
End If

[共通部品]isExistNumを呼び出し、返り値がFalse(numが存在しない)の場合、次の処理に進みます。


【ループ2】1行目~9行目まで繰り返す
セルが空白か?

Dim i As Long
For i = 1 To 9 '1列目~9列目まで繰り返す
                
    If Cells(i, c).Value = "" Then
        '~処理~
    End If
                
Next i

★ポイント★

  • 行番号:1~9目を順番にチェックするので、解法3(列)のローカル変数を用意します。
  • 列番号:パブリック変数のを使用します。

f:id:excel-accounting:20180614225721p:plain:w350


BlockAreaの特定

Dim r2 As Long
r2 = SearchBlockArea(i)

★ポイント★
解法1・2ではプロシージャ内でBlockAreaの特定処理は行わず、パブリック変数のr1, c1を使用しました。

それに対して、解法3(列)ではこのようになります。

  • BlockArea始点セルの番号→解法3(列)のローカル変数r2を用意し、特定処理をする
  • BlockArea始点セルの番号→パブリック変数c1を使用

解法3(列)プロシージャの処理開始~処理終了までの間、列番号cは固定なのに対し、行番号が変動することによって、BlockAreaの行位置が変わるためです。


①同じにnumが存在するか検索
BlockAreaにnumが存在するか検索

Dim check1 As Boolean, check2 As Boolean
check1 = isExistNum(i, 1, i, 9) '行のチェック
check2 = isExistNum(r2, c1, r2 + 2, c1 + 2) 'BlockAreaのチェック

★ポイント★
①行のチェック
引数の行番号はローカル変数iです。

②BlockAreaのチェック
始点セルの行番号:解法3(行)のローカル変数r2
始点セルの列番号:パブリック変数のc1


検索①②のどちらにも存在しない

If check1 = False And check2 = False Then
    '~処理~                    
End If

条件は「どちらにも存在しない」なので、返り値FalseAnd結合します。


変数へデータ格納(SetCnt・行番号)

Dim SetCnt As Long
SetCnt = SetCnt + 1 '代入可能セルの個数
                        
Dim y As Long
y = i '現在地の行番号


SetCnt=1か?
セルに解答(num)を代入

If SetCnt = 1 Then Call AnswerSet(y, c, num)

共通モジュールの[共通部品]AnswerSetを呼び出して、セルへの解答書き込みを行います。

★ポイント★

書き込み先のセルを指定する引数(行,列)がポイントです。

行番号:解法3(列)のローカル変数
列番号:パブリック変数の


SetCntの初期化

SetCnt = 0

これで1つの数字のチェックが終わりました。
カウント変数を初期化して、次の数字をチェックします。

解法3(列)まとめ

今日のまとめです!

呼び出す側の「共通モジュール」と、呼び出される側の「解法3(列)プロシージャ」をまとめます。

共通モジュール[全体処理]

For r = 1 To 9
    For c = 1 To 9
    
        'BlockArea始点セルの特定
        r1 = SearchBlockArea(r)
        c1 = SearchBlockArea(c)
         
        '解法1を呼び出す
        If Cells(r, c).Value = "" Then Call Solution1
        
        '解法2を呼び出す
        If (r = 1 Or r = 4 Or r = 7) And (c = 1 Or c = 4 Or c = 7) Then Call Solution2
        
        '解法3(行)を呼び出す
        If c = 1 Then Call Solution3_Row

        '解法3(列)を呼び出す
        If r = 1 Then Call Solution3_Col

    Next c
Next r

解法3(列)プロシージャ

Sub Solution3_Col()

    '解読対象の列が完成している場合は不要のためExit
    If WorksheetFunction.CountBlank(Range(Cells(1, c), Cells(9, c))) = 0 Then
        Exit Sub
    End If

    For num = 1 To 9
    
        '対象の【列】にnumが存在しなければチェック開始
        If isExistNum(1, c, 9, c) = False Then

            Dim i As Long
            For i = 1 To 9 '1行目~9行目を順番にチェック
                
                If Cells(i, c).Value = "" Then
                
                    Dim r2 As Long
                    r2 = SearchBlockArea(i)
                    
                    Dim check1 As Boolean, check2 As Boolean
                    check1 = isExistNum(i, 1, i, 9) '行のチェック
                    check2 = isExistNum(r2, c1, r2 + 2, c1 + 2) 'BlockAreaのチェック
                    
                    If check1 = False And check2 = False Then 'どちらにも存在しなければ
                        
                        Dim SetCnt As Long, y As Long
                        SetCnt = SetCnt + 1 '代入可能セルの個数
                        y = i '現在位置の行番号
                        '列番号はcなので取得不要
                    
                    End If
                
                End If
                
            Next i
            
            If SetCnt = 1 Then Call AnswerSet(y, c, num)
            
            SetCnt = 0 '初期化

        End If

    Next num

End Sub

次回予告

これで全体処理・共通部品・解法1~3のすべてが完成しました。

次回はこれらをひとつにまとめて、実際にマクロがパズルを解く様子をお披露目します。

f:id:excel-accounting:20180428154537p:plain:w150

VBAでナンプレを解いてみよう【最終回】

スポンサーリンク