基礎から学ぶVBAプログラミング教室

もりさんのお題を解きながら楽しく勉強しよう

スポンサーリンク

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

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

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:20180530205023p:plain:w350

  • 1行目には空白セルが4個あり、1,4,6,7のいずれかが入る
  • D1セル・F1セル:同じエリアに数字の1が存在する
  • G1セル:同じ列に数字の1が存在する

以上より、数字の1はA1セルにしか入らないことがわかります。

これがセル探し(行)の考え方です。

解読する範囲が異なるだけで、全体的な考え方は解法2と同じです。

  • 解法2:3×3エリアを1単位として解読
  • 解法3:1行を1単位として解読

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


今回の記事は、解法2を理解している前提での解説になります。

まだ読んでない方は先にこちらからみてくださいね!
VBAでナンプレを解いてみよう④<解法2>

イメージづくり

解き方のイメージを描いていきます。

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

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

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

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


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

事前チェック

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

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

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

とします。

チェック処理

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

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

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

※ここのチェック方法が解法2と異なります

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

  • 同じ列
  • 3×3エリア

です。

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

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


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

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

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

変数のイメージと使い方は解法2と同じです。

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

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

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

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

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

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

フローチャート作成

解法3(行)の全体像です。

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

プログラム作成

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

呼び出す側の処理
If c = 1 Then Call Solution3_Row

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

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

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

解法3(行)の各処理

フローチャートに沿ってプログラムを書いていきます。
これらはすべて解法3モジュールに記述します。


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

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

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

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


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

(省略)


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

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

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


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

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

★ポイント★

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

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


BlockAreaの特定

Dim c2 As Long '解法3(行)のBlockArea始点セルの列番号
c2 = SearchBlockArea(i)

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

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

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


例えば、

空白セル①Cells(1, 1)のBlockAreaはRange(Cells(1, 1), Cells(3, 3))ですが、

空白セル②Cells(1, 4)のBlockAreaはRange(Cells(1, 4), Cells(3, 6))となります。

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

このように、解法3(行)プロシージャの処理開始~処理終了までの間で列番号が変動することによって、BlockAreaの位置も変わります。

そのため、BlockArea始点セルの列番号のみ、解法3(行)で特定しなおす必要があります。

行番号rは解法3(行)プロシージャ内ではずっと固定なので、パブリック変数のr1を使用しています。


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

Dim check1 As Boolean, check2 As Boolean
check1 = isExistNum(1, i, 9, i) '列のチェック
check2 = isExistNum(r1, c2, r1 + 2, c2 + 2) 'BlockAreaのチェック

★ポイント★
①列のチェック
渡す引数は、ローカル変数iです。

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


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

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

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


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

Dim SetCnt As Long
SetCnt = SetCnt + 1 '代入可能セルの個数
                        
Dim x As Long
x = i '現在地の【列】番号


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

If SetCnt = 1 Then Call AnswerSet(r, x, 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

    Next c
Next r

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

Sub Solution3_Row()

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

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

            Dim i As Long
            For i = 1 To 9 '1列目~9列目まで繰り返す
                
                If Cells(r, i).Value = "" Then
                
                    Dim c2 As Long '解法3(行)専用のBlockArea始点セル
                    c2 = SearchBlockArea(i)
                
                    Dim check1 As Boolean, check2 As Boolean
                    check1 = isExistNum(1, i, 9, i) '列のチェック
                    check2 = isExistNum(r1, c2, r1 + 2, c2 + 2) 'BlockAreaのチェック
                    
                    'どちらにもnumが存在しなければ
                    If check1 = False And check2 = False Then
                        
                        Dim SetCnt As Long, x As Long
                        SetCnt = SetCnt + 1 '代入可能セルの個数
                        x = i '現在地の【列】番号
                        
                    End If
                
                End If
                
            Next i
            
            '解答判定とセルへの書き込み
            If SetCnt = 1 Then Call AnswerSet(r, x, num)
            
            SetCnt = 0 '初期化

        End If

    Next num

End Sub

次回予告

次回は解法3(列)の作成に入っていきます。

解読する範囲が異なるだけで、考え方はまったく同じですよ。

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

最後に

ここまでお読みいただきありがとうございました。

*引き続き連載を読んでくださるはてなブロガーの方はぜひ読者になってくださいね。

*ツイッターでもブログ更新のツイートをしているので、こちらもフォローしてくれると嬉しいです。

第6回目へつづきます
VBAでナンプレを解いてみよう⑥

スポンサーリンク