[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『数独・ナンプレの解答をランダムで作りたい』(asa)
数独・ナンプレ(9×9)の解答をランダムに作成したいです。
縦横に1-9、3×3に1-9がそれぞれ入る、普通の数独の、問題ではなく解答の盤をEXCELの関数やマクロを駆使して作りたいのですが、知識不足で、案がありません。ご教示ください。
< 使用 Excel:Office365、使用 OS:Windows10 >
https://excel-ubara.com/excelvba5/EXCELVBA230.html
(γ) 2020/04/26(日) 19:40
補足
自分は趣味として、数独を作成しているのですが、作成するとき、一つの作成方法として、完成盤面を作成し、その後いくつかの空欄を作り、完成させるという方法を取っています。エクセルを使うことで、完成盤面の作成を省こうと思っているのです。
(asa) 2020/04/26(日) 20:52
取りあえず別のことをしないといけないので、
他の回答者のコメントをお待ち下さい。
( γ) 2020/04/26(日) 21:23
Sub シャッフル()
Dim s As String
Dim i As Long, j As Long, j1 As Long, j2 As Long, k As Long
Dim x1, x2, xx
Randomize
'数字シャッフル
s = "123456789"
For i = 1 To 9
j = Int(Rnd() * 9 + 1)
s = j & Replace(s, j, "")
Next
With Range("a1").Resize(9, 9)
xx = .Value
For i = 1 To 9
For j = 1 To 9
xx(i, j) = Mid(s, xx(i, j), 1)
Next
Next
.Offset(10).Value = xx
'列シャッフル
For i = 1 To 9 Step 3
j1 = Int(Rnd() * 3) + i
j2 = Int(Rnd() * 3) + i
x1 = Application.Index(xx, 0, j1)
x2 = Application.Index(xx, 0, j2)
For j = 1 To 9
xx(j, j1) = x2(j, 1)
xx(j, j2) = x1(j, 1)
Next
Next
.Offset(20).Value = xx
'行シャッフル
For i = 1 To 9 Step 3
j1 = Int(Rnd() * 3) + i
j2 = Int(Rnd() * 3) + i
x1 = Application.Index(xx, j1, 0)
x2 = Application.Index(xx, j2, 0)
For j = 1 To 9
xx(j1, j) = x2(j)
xx(j2, j) = x1(j)
Next
Next
.Offset(30).Value = xx
End With
End Sub
(kazuo) 2020/04/26(日) 22:30
こんばんは! 昔に書いたコードの使いまわしですけど、、ちょっと書いてみました。 乱数は↓ここから拝借しております。 http://www001.upp.so-net.ne.jp/isaku/mt.html
3×3 を 3セットづつ作ればいいんですよね??? 偏ってないと思いますが、、、偏っている様にも見える(^^; まぁ、、何かの参考程度でお願いします。。。。 もう寝ます。 おやすみなさいzzzzzzzzzzzzzzzzzzzz
Option Explicit ' VBAによるメルセンヌツイスタ ' システムを起動してからの時間をミリ秒単位で返す ' http://msdn.microsoft.com/ja-jp/library/cc429827.aspx Private Declare Function GetTickCount Lib "kernel32" () As Long ' メルセンヌツイスタのパラメータ(ダイナミッククリエーターの結果) Private Const MTN = 644, MTM = 322, MTA = 12, MTB = 7, MTC = 15, MTD = 18 Private Const MXA = &H70C20000, UMK = &H78000000, LMK = &H7FFFFFF Private Const MKB = &H73736B80, MKC = &H6ED28000 ' 補助的な定数の宣言 Private Const MTL = MTN - MTM, MTK = MTN - 1, MTJ = MTL - 1, MTP = MTN - 2 Private Const PWA = 2 ^ MTA, PWB = 2 ^ MTB, PWC = 2 ^ MTC, PWD = 2 ^ MTD Private Const KB = MKB \ PWB, KC = MKC \ PWC Private Const P32 = 2# ^ 32, P31 = 2 ^ 31, P22 = 2# ^ 22, P9 = 2 ^ 9 Private Const M53 = 2# ^ -53, M32 = 2# ^ -32, M30 = 2# ^ -30 ' 乱数の状態 Private mt(0 To MTK), mti As Long ' 初期化の補助関数 Private Function Ri(ByRef r As Double, ByVal i As Long) As Long Dim s As Variant Dim shft As Double Dim a As Long If r >= P31 Then a = r - P32 Else a = r a = a Xor Int(r * M30) If a < 0 Then r = a + P32 Else r = a s = 1812433253 * CDec(r) + i: r = s - CDec(Int(s * M32)) * P32 If r >= P31 Then Ri = r - P31 Else Ri = r End Function ' s を種にして乱数を初期化する Public Sub InitMt(ByVal s As Long) Dim r As Double mt(0) = s And &H7FFFFFFF If s < 0 Then r = P32 + s Else r = s For mti = 1 To MTK: mt(mti) = Ri(r, mti): Next mti mti = MTN End Sub ' 31 ビットの整数乱数 Public Function NextMt() As Long Dim y, k As Long If mti = 0 Then InitMt (1) If mti = MTN Then mti = 0 For k = 0 To MTJ y = (mt(k) And UMK) Or (mt(k + 1) And LMK) mt(k) = mt(k + MTM) Xor (y \ 2) Xor (-(y And 1) And MXA) Next k For k = MTL To MTP y = (mt(k) And UMK) Or (mt(k + 1) And LMK) mt(k) = mt(k - MTL) Xor (y \ 2) Xor (-(y And 1) And MXA) Next k y = (mt(MTK) And UMK) Or (mt(0) And LMK) mt(MTK) = mt(MTM - 1) Xor (y \ 2) Xor (-(y And 1) And MXA) End If y = mt(mti): mti = mti + 1 y = y Xor (y \ PWA): y = y Xor ((y And KB) * PWB) y = y Xor ((y And KC) * PWC): y = y Xor (y \ PWD): NextMt = y End Function ' 0 以上 1 未満の乱数を返す Public Function NextUnifMt() As Double Dim x As Long x = NextMt \ P9: NextUnifMt = (NextMt * P22 + x) * M53 End Function ' 時間を種にして乱数を初期化する Public Sub RandomizeMt() InitMt (GetTickCount()) End Sub
Sub てすと()
Dim MyA As Variant
Dim MyB As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim ii As Long
Dim jj As Long
MyA = Split("1,2,3,4,5,6,7,8,9", ",")
ReDim MyB(1 To 9, 1 To 9)
For ii = 0 To 6 Step 3
For jj = 0 To 6 Step 3
k = 0
MyFScs MyA
For i = 1 To 3
For j = 1 To 3
MyB(i + ii, j + jj) = MyA(k)
k = k + 1
Next
Next
Next
Next
Range("A1").Resize(UBound(MyB, 1), UBound(MyB, 2)).Value = MyB
Erase MyA, MyB
End Sub
Private Sub MyFScs(ByRef x As Variant)
Dim y As Variant
Dim i As Long
Dim j As Long
Dim MyScs As Object
Set MyScs = CreateObject("System.Collections.SortedList")
ReDim y(LBound(x, 1) To UBound(x, 1))
RandomizeMt
For i = LBound(x, 1) To UBound(x, 1)
MyScs(NextUnifMt()) = i
Next
For i = 0 To MyScs.Count - 1
y(i) = x(MyScs.GetByIndex(i))
Next
x = y
Set MyScs = Nothing
Erase y
End Sub
(SoulMan) 2020/04/27(月) 00:33
作った(9×9)マスをひっくり返したり、横にしたり・・・。 (BJ) 2020/04/27(月) 12:50
トライアンドエラーのスパゲッティコードですが、
とりあえず作成できるみたいなので、新規シートでお試しください。
※ 尚 中断したい時は {Ctrl}+{Break}で止めてください。
Sub 問題作成()
Dim R As Long, C As Long, i As Long, LC As Long
Dim No As Long, Box As Range
Dim X As Range, W As Range
Dim wNo As String
Do
Cells(1, 1).Resize(9, 9).ClearContents
wNo = 順番
i = 0
For Each X In Cells(7, 7).Resize(3, 3)
i = i + 1
X.Value = Mid(wNo, i, 1)
Next
'-----
For R = 7 To 1 Step -3
For C = 7 To 1 Step -3
Set Box = Cells(R, C).Resize(3, 3)
If R = 7 And C = 7 Then
ElseIf (R = 1 And C = 1) Then
wNo = "123456789"
Box.ClearContents
For Each W In Box
For i = 1 To 9
No = Val(Mid(wNo, i, 1))
If WorksheetFunction.CountIf(W.Resize(9, 1), No) = 0 Then
If WorksheetFunction.CountIf(W.Resize(1, 9), No) = 0 Then
W.Value = Val(No)
Exit For
End If
End If
Next
Next
DoEvents
If WorksheetFunction.CountBlank(Box) = 0 Then Exit Do
Else
LC = 0
Do
wNo = 順番
Box.ClearContents
For Each W In Box
For i = 1 To Len(wNo)
No = Val(Mid(wNo, i, 1))
If WorksheetFunction.CountIf(W.Resize(9, 1), No) = 0 Then
If WorksheetFunction.CountIf(W.Resize(1, 9), No) = 0 Then
W.Value = Val(No)
wNo = Replace(wNo, CStr(No), "")
Exit For
End If
End If
Next
Next
DoEvents
LC = LC + 1
If LC >= 10 Then Exit Do
Loop While WorksheetFunction.CountBlank(Box) <> 0
End If
If WorksheetFunction.CountBlank(Box) <> 0 Then Exit For
Next
If WorksheetFunction.CountBlank(Box) <> 0 Then Exit For
Next
Loop
MsgBox "完了"
End Sub
Function 順番() As String
Dim W9 As String
Dim i As Long
Dim X As Range, W As Range
Set X = Cells(1, 21).Resize(9, 1)
X.Formula = "=rand()"
X.Value = X.Value
For i = 1 To 9
W9 = W9 & WorksheetFunction.Rank(Cells(i, 21), Cells(1, 21).Resize(9, 1))
Next
順番 = W9
End Function
(チオチモリン) 2020/04/27(月) 18:47
こんばんは! ただ作るだけじゃだめなんですね???(^^; 乱数のところは上のコードをそのまま使用して頂くとしてちょっと入れ替えてみました。。。 途中同じパターンになるところがあります。。。(多分????) 後は、、応用して頂けると助かります。。。 あっ、、罫線も付けておきました。。。。 では、、では、、、また、、、
Sub 改造問題作成配列最終版()
Dim MyA As Variant
Dim i As Long
Dim BoxNo As String
Dim 総合判定 As Boolean
Static カウンター As Boolean
i = 1
With Range("A1").Resize(9, 9)
.ClearContents
ReDim MyA(1 To 9, 1 To 9)
Application.ScreenUpdating = False
Do
BoxNo = Mid("012345678", i, 1)
総合判定 = False
BoxSub MyA, CLng(BoxNo), 総合判定
If 総合判定 Then
i = i + 1
Else
i = 1
ReDim MyA(1 To 9, 1 To 9)
End If
Loop While i < 10
Range("A1").Resize(UBound(MyA, 1), UBound(MyA, 2)).Value = MyA
If カウンター = False Then
.Borders.LineStyle = xlContinuous
With Range("A4:I6")
.Borders(xlEdgeTop).Weight = xlMedium
.Borders(xlEdgeBottom).Weight = xlMedium
End With
With Range("D1:F9")
.Borders(xlEdgeLeft).Weight = xlMedium
.Borders(xlEdgeRight).Weight = xlMedium
End With
.BorderAround Weight:=xlMedium
End If
カウンター = True
Application.ScreenUpdating = True
End With
MsgBox "完了"
Erase MyA
End Sub
Sub BoxSub(ByRef MyA As Variant, ByVal BoxNo As Long, ByRef 総合判定 As Boolean)
Dim i As Long, 回数 As Long
Dim r As Long, c As Long
Dim No As Long
Dim 候補 As String
Dim 判定 As Boolean
Dim 行列 As Variant
Dim 行 As Variant
Dim 列 As Variant
Dim v As Variant
Dim ri As Long
Dim cj As Long
Dim n As Long
Dim MyFlg As Boolean
Dim TempMyA As Variant
行列 = Array("1,1", "1,4", "4,1", "1,7", "7,1", "4,4", "4,7", "7,4", "7,7")
r = Split(行列(BoxNo), ",")(0)
c = Split(行列(BoxNo), ",")(1)
回数 = 0
Do
TempMyA = MyA
MyFlg = False
v = Split("1,2,3,4,5,6,7,8,9", ",")
MyFScs v
候補 = Join(v, "")
For ri = 0 To 2
For cj = 0 To 2
For i = 1 To Len(候補)
No = Val(Mid(候補, i, 1))
行 = Application.Index(MyA, r + ri, 0)
列 = Application.Transpose(Application.Index(MyA, 0, c + cj))
If UBound(Filter(行, No, True)) Then
If UBound(Filter(列, No, True)) Then
MyA(r + ri, c + cj) = Val(No)
候補 = Replace(候補, CStr(No), "")
Exit For
End If
End If
Next
Next
Next
チェック MyA, r, c, MyFlg
If MyFlg Then
Select Case BoxNo
Case 0 To 7
回数 = 回数 + 1
If 回数 >= 10 Then
判定 = False
MyA = TempMyA
Exit Do
End If
Case 8
判定 = False
MyA = TempMyA
Exit Do
End Select
Else
判定 = True
Exit Do
End If
Loop
総合判定 = 判定
Erase 行列, 行, 列, v, TempMyA
End Sub
Private Sub MyFScs(ByRef x As Variant)
Dim y As Variant
Dim i As Long
Dim j As Long
Dim MyScs As Object
Set MyScs = CreateObject("System.Collections.SortedList")
ReDim y(LBound(x, 1) To UBound(x, 1))
RandomizeMt
For i = LBound(x, 1) To UBound(x, 1)
MyScs(NextUnifMt()) = i
Next
For i = 0 To MyScs.Count - 1
y(i) = x(MyScs.GetByIndex(i))
Next
x = y
Set MyScs = Nothing
Erase y
End Sub
Sub チェック(ByRef v As Variant, ByVal ii As Long, ByVal jj As Long, ByRef MyFlg As Boolean)
Dim i As Long
Dim j As Long
For i = 0 To 2
For j = 0 To 2
If IsEmpty(v(ii + i, jj + j)) Then
MyFlg = True
Exit For
End If
Next
Next
End Sub
こんにちは! いやぁ難しいです。(^^; ちょっと私には無理そうなので チオチモリン さんのコードをお借りして 乱数をNextUnifMtにして上から書き込んで行く様にしてみました。 意味はありません。自己研鑽です。すみません。
何年かして他の誰かが見に来られることもあるでしょうから参考出展させてください。 では、、では、、また。。。 2020/05/01 10:52
シート上よりも配列の中の方が多少なりともスピードUp期待出来るので 配列に置き換えてみました。コードは変わりません。。。。 すみません。参考出展です。。。。m(__)m 2020/05/01 21:20
すみません。初期化→TempMyAにしました。 2020/05/03 12:03 (SoulMan) 2020/04/27(月) 20:35
参考サイトを紹介しておきます。
(1)「ナンプレ完成形はどこまで増やせる?」 http://www20.big.or.jp/~morm-e/puzzle/column/001/001.html
(2)「数独の例題生成 - ネットワークシステム研究室」 http://www.net.c.dendai.ac.jp/~ynakajima/index.htm
本文ここまでです。
================================================= 【以下は余談です。お急ぎの方はスキップ下さい】
既に提示いただいているコードで結果は得られますが、 上記(2)の記事に掲載されている、「バックトラック法」を用いたコードを 試してみました。
関係するのは、 mainの一部 check rand9 canBePlaced(縦、横、3×3のブロック(9個)に1〜9が重複無く配置されているかをチェック) などくらいです。
ちなみに、自分の学習を兼ねて、VBAとは別の言語で書いてみましたら、 1000個の数独パターンを 0.8秒くらいで作成できました。
ちなみに、バックトラック法を用いると、この場合、行き止まりになった再帰処理が 積み重なっていくわけですが、正解にたどり着いた段階で、それらの閉じられていない プロシージャをなんらかの手段で閉じる必要があります。
記事にあるC言語のコードでは、try catch構文を使用して対応しています。 (つまり、9×9の完成形に達したら、例外を投げて大域的ジャンプをしています。 これで、同時に関数スタックがすべてクリアーされるわけです。)
ところが、VBAにはこうした機構が無いので立ち往生します。 普通は、On error と Err.Raiseの意図的発生とを組み合わせるんでしょうが、 この場合は複数回繰り返すと、一回目の数独作成は成功しますが、 二度目のErr.Raiseの意図的発生で不可解なエラーが起きてしまい、 なんとも解決できない状況です。これは困ったなあ、という感じです。
愚痴になりましたのでこの辺で。
( γ) 2020/04/27(月) 23:13
1.数字の入れ替え x362880 2.90度回転 x2 3.ブロック入れ替え x6 4.行列入れ替え x36
プロシージャ名をアレンジとし、理論的には1回のアレンジで4.が36通りからになるようにし、3.を追加しました。
但し、2.は除いてあります。
ですので、1つの問題でアレンジすることにより、6千万とおりの問題が作成できることになります。
Sub アレンジ()
Dim s As String
Dim i As Long, j As Long
Dim xx
Randomize
With Range("a1").Resize(9, 9)
If WorksheetFunction.CountBlank(.Cells) = 9 * 9 Then
.Offset(0).Resize(3).Value = [{6,0,8,0,3,0,0,7,0;1,7,0,8,0,5,0,0,0;0,0,0,0,7,0,0,0,0}]
.Offset(3).Resize(3).Value = [{0,8,3,0,0,0,0,4,0;0,0,0,0,0,0,0,0,0;5,0,9,0,0,0,6,0,0}]
.Offset(6).Resize(3).Value = [{0,9,0,0,4,0,7,8,0;7,2,0,0,5,0,9,1,0;0,0,0,0,0,1,0,3,0}]
.Replace "0", ""
End If
xx = .Value
'数字シャッフル
s = "123456789"
For i = 1 To 9
j = Int(Rnd() * 9 + 1)
s = j & Replace(s, j, "")
Next
For i = 1 To 9
For j = 1 To 9
If xx(i, j) <> "" Then xx(i, j) = Mid(s, xx(i, j), 1)
Next
Next
For i = 1 To 2
列シャッフル xx
Next
For i = 1 To 2
行シャッフル xx
Next
For i = 1 To 2
xx = 列ブロックシャッフル(xx)
Next
For i = 1 To 2
xx = 行ブロックシャッフル(xx)
Next
.Value = xx
End With
End Sub
Private Sub 列シャッフル(ByRef xx)
Dim i As Long, j As Long, j1 As Long, j2 As Long
Dim x1, x2
For i = 1 To 9 Step 3
j1 = Int(Rnd() * 3) + i
j2 = Int(Rnd() * 3) + i
If j1 = j2 Then Exit Sub
x1 = Application.Index(xx, 0, j1)
x2 = Application.Index(xx, 0, j2)
For j = 1 To 9
xx(j, j1) = x2(j, 1)
xx(j, j2) = x1(j, 1)
Next
Next
End Sub
Private Sub 行シャッフル(ByRef xx)
Dim i As Long, j As Long, j1 As Long, j2 As Long
Dim x1, x2
For i = 1 To 9 Step 3
j1 = Int(Rnd() * 3) + i
j2 = Int(Rnd() * 3) + i
If j1 = j2 Then Exit Sub
x1 = Application.Index(xx, j1, 0)
x2 = Application.Index(xx, j2, 0)
For j = 1 To 9
xx(j1, j) = x2(j)
xx(j2, j) = x1(j)
Next
Next
End Sub
Private Function 列ブロックシャッフル(ByVal xx) As Variant
Dim i As Long, j As Long, j1 As Long, j2 As Long
Dim x1, x2
列ブロックシャッフル = xx
x1 = xx
j1 = Int(Rnd() * 3) + 1
j2 = Int(Rnd() * 3) + 1
If j1 = j2 Then Exit Function
For i = 1 To 3
For j = 1 To 9
x1(j, (j1 - 1) * 3 + i) = xx(j, (j2 - 1) * 3 + i)
Next
For j = 1 To 9
x1(j, (j2 - 1) * 3 + i) = xx(j, (j1 - 1) * 3 + i)
Next
Next
列ブロックシャッフル = x1
End Function
Private Function 行ブロックシャッフル(ByVal xx) As Variant
Dim i As Long, j As Long, j1 As Long, j2 As Long
Dim x1
行ブロックシャッフル = xx
x1 = xx
j1 = Int(Rnd() * 3) + 1
j2 = Int(Rnd() * 3) + 1
If j1 = j2 Then Exit Function
For i = 1 To 3
For j = 1 To 9
x1((j1 - 1) * 3 + i, j) = xx((j2 - 1) * 3 + i, j)
Next
For j = 1 To 9
x1((j2 - 1) * 3 + i, j) = xx((j1 - 1) * 3 + i, j)
Next
Next
行ブロックシャッフル = x1
End Function
(kazuo) 2020/04/28(火) 07:38
おはようございます。 >数独は、各行、各列ともに、1〜9が一回だけ重複なく登場しないといけないと思いますが、 すみません。そんなルールがあるのですね??? わたしゃただただ1〜9が並べばいいのかと思っていました。。。
もう少し勉強します。ありがとうございます。。。m(__)m (SoulMan) 2020/04/29(水) 07:20
ちょっとコードを整理しました。 ロジックは同じです。
Sub 問題作成()
Dim i As Long
Dim BoxNo As String
i = 9
Cells(1, 1).Resize(9, 9).ClearContents
Application.ScreenUpdating = False
Do
BoxNo = Mid("124357689", i, 1)
If BoxSub(CLng(BoxNo)) = True Then
i = i - 1
If i = 0 Then Exit Do
Else
i = 9
Cells(1, 1).Resize(9, 9).ClearContents
End If
DoEvents
Loop
Application.ScreenUpdating = True
' MsgBox "完了"
End Sub
Function BoxSub(BoxNo As Long) As Boolean
Dim i As Long, LC As Long
Dim R As Long, C As Long
Dim No As Long
Dim wNo As String
Dim w As Range
Dim Box As Range
Dim W9 As Boolean
R = Array(0, 1, 1, 1, 4, 4, 4, 7, 7, 7)(BoxNo)
C = Array(0, 1, 4, 7, 1, 4, 7, 1, 4, 7)(BoxNo)
Set Box = Cells(R, C).Resize(3, 3)
LC = 0
Do
Box.ClearContents
wNo = 順番
For Each w In Box
For i = 1 To Len(wNo)
No = Val(Mid(wNo, i, 1))
If WorksheetFunction.CountIf(w.Resize(9, 1), No) = 0 Then
If WorksheetFunction.CountIf(w.Resize(1, 9), No) = 0 Then
w.Value = Val(No)
wNo = Replace(wNo, CStr(No), "")
Exit For
End If
End If
Next
Next
DoEvents
If WorksheetFunction.CountBlank(Box) <> 0 Then
Select Case BoxNo
Case 2 To 9: LC = LC + 1: If LC >= 10 Then W9 = False: Exit Do
Case 1: W9 = False: Exit Do
End Select
Else
W9 = True
Exit Do
End If
Loop
BoxSub = W9
End Function
Function 順番() As String
Dim W9 As String
Dim i As Long
Dim x As Range, w As Range
Set x = Cells(1, 21).Resize(9, 1)
x.Formula = "=rand()"
x.Value = x.Value
For i = 1 To 9
W9 = W9 & WorksheetFunction.Rank(Cells(i, 21), Cells(1, 21).Resize(9, 1))
Next
順番 = W9
End Function
(チオチモリン) 2020/04/29(水) 18:21
既に回答をいただいていますが、乱数に基づき設定する方法をもう一つ。 少し前の発言で触れたバックトラック法を使った方法を参考までに載せておきます。 (ロジックは基本的に先だって紹介したサイトのものです) コードはやや長いですが、実行時間はたぶん短いです。
1.標準モジュールにそのままコピーペイストして下さい。 2.testを実行すると、A1:I9に結果を表示します。 3.いくつもの完成形を続けて作成する場合には、コードの最初にある kosuという定数を修正して下さい。
================================================= Option Explicit
Const kosu = 1 ' 作成する数独完成形の個数(■■要修正)
Const N As Long = 9 Const B As Long = 3 Dim checkCounter As Long ' check関数の使用回数(debug用)
'数独完成形をkaisu個作成
Sub test()
Dim board() As Long
Dim k As Long
Dim t
t = Timer
'Rnd (-10) '(1)乱数シードを固定したい場合
Randomize '(2)その都度、シードを変える場合
On Error GoTo success
For k = 1 To kosu
checkCounter = 0
ReDim board(1 To N * N)
Call check(board, 1)
success:
Resume Next
Call myPrint(board, k) 'シート書き込み
'Debug.Print checkCounter; " checkcounter"
Next
Debug.Print Timer - t; " " & kosu & " 個作成済み"
Debug.Print "-----------------------------------"
End Sub
'pos番目のマスに候補を入力する
Function check(board() As Long, pos&)
Dim newPos&
Dim j&, k&
Dim newValue&
checkCounter = checkCounter + 1 'このプロシージャの実行回数カウントのため
If pos = N * N + 1 Then '完成形となったら
Err.Raise vbObjectError + 513 '例外のthrow に相当。
End If
'未記入セルのうち、一番若いもの
For k = pos To N * N
If board(k) = 0 Then
newPos = k
Exit For
End If
Next
ReDim randBoard9(1 To N) As Long
Call rand9(randBoard9) 'シャッフルした1〜9からなる配列の生成
For j = 1 To N
newValue = randBoard9(j)
'縦・横・ボックス内に重なりがないか検証
If canBePlaced(board, newPos, newValue) Then
board(newPos) = newValue 'OKなら仮記入
Call check(board, newPos + 1) '次の未記入セルに対して再帰実行
Debug.Print newPos; " backtracking"
board(newPos) = 0 '■Backtracking(1〜9まで試して不首尾ならワンステップ前に戻る)
End If
Next
End Function
Function rand9(randomBoard() As Long) '1〜9の値をランダムにシャッフル
Dim k&, i&, j&, tmp&
For k = 1 To N
randomBoard(k) = k
Next
'ランダムにシャッフル
For i = 1 To N
'j = Application.RandBetween(1, N) '乱数シード値のコントロール外のため使用せず
j = Int(Rnd() * N) + 1
If i <> j Then
tmp = randomBoard(i)
randomBoard(i) = randomBoard(j)
randomBoard(j) = tmp
End If
Next
End Function
Function canBePlaced(board() As Long, pos&, v&) As Boolean
'check関数で仮に入れられた値が、縦・横・その値の所属の3×3マスに
'既にあれば Falseを返す(なければ Trueを返す)
Dim r&, c&
Dim i&, j&, topLeft&
r = ((pos - 1) \ N) + 1 ' r=行
c = ((pos - 1) Mod N) + 1 ' c=列
For i = 1 To N
If board((r - 1) * N + i) = v Then '同じ行に同一値があれば
canBePlaced = False: Exit Function
End If
If board(c + (i - 1) * N) = v Then '同じ列に同一値があれば
canBePlaced = False: Exit Function
End If
Next
topLeft = N * ((r - 1) \ B) * B + ((c - 1) \ B) * B + 1
For i = 1 To B
For j = 1 To B
'3×3ボックス内に同一値があれば
If board(topLeft + (i - 1) * N + (j - 1)) = v Then
canBePlaced = False: Exit Function
End If
Next
Next
canBePlaced = True
End Function
Function myPrint(board() As Long, r As Long)
Dim k&, j&
ReDim v(1 To N, 1 To N)
For k = 1 To N
For j = 1 To N
v(k, j) = board((k - 1) * N + j)
Next
Next
'ワークシート書き込み
Cells((r - 1) * 11 + 1, "A").Resize(N, N).Value = v
End Function
=================================================
結果の書き出しは、各エリアに直接書き込んでいますが、
書式を整えたA1:I9 にいったん書き込み、その後、コピーペイストすると
各エリアに書式を設定する手間は省けるかもしれません。
なお、Err.Raiseの後始末にはResumeを使うとよかったんですね。 (On Errorを繰り返し使うことはできないんですね。すっかり忘れていました)
また、元のコードでの乱数シードの使い方と少し変えています。 私には、頻繁にシード値を変えると却って乱数作成の規則性?が崩れるように 思えます。 (γ) 2020/04/30(木) 22:25
Const N As Long = 9, B As Long = 3 Dim flag As Boolean ' success 確認用
'数独問題の妥当性チェック
Sub 妥当性チェック()
Dim Board() As Long, Dummy() As Long
Dim k As Long, kosu As Long, ans As Long
Call 初期化
kosu = 30 '確率 1-0.5^29=99.9999998%
ReDim Dummy(1 To N * N)
Range("a2:i10,a13:i21").ClearContents
On Error GoTo success
For k = 1 To kosu
boardget Board
Call check2(Board, 1)
success:
Resume Next
If flag Then
MsgBox "解がありません"
Exit Sub
End If
If hikaku(Board, Dummy) Then
ans = ans + 1
End If
Call myPrint2(Board, ans)
If 1 < ans Then
MsgBox "解が複数あります"
Exit Sub
End If
Dummy = Board
Next
If MsgBox("盤面を記入しますか?", vbYesNo) = vbYes Then
Range("A2").Resize(N, N).Copy Range("K13")
Range("K13").Resize(N, N).Interior.Color = vbYellow
End If
End Sub
Private Sub 初期化()
Const 解析 = "解析"
Dim i As Long, j As Long
Dim sp As Shape
Randomize
On Error Resume Next
Set sp = ActiveSheet.Shapes(解析)
On Error GoTo 0
If Not sp Is Nothing Then Exit Sub
ActiveWindow.DisplayGridlines = False
Cells.Clear
Cells.Interior.Color = 13431551
Rows("1:40").RowHeight = 17.25
Columns("a:z").ColumnWidth = 2.5
Range("A1").Value = " 回答1"
Range("A12").Value = " 回答2"
Range("K1").Value = "問題入力"
Range("K12").Value = "盤面"
With Range("K2").Resize(B, B)
For i = 0 To N - 1 Step B
For j = 0 To N - 1 Step B
.Offset(i, j).BorderAround xlContinuous, xlMedium
.Offset(i, j).Borders(xlInsideHorizontal).Weight = xlThin
.Offset(i, j).Borders(xlInsideVertical).Weight = xlThin
Next
Next
.Resize(N, N).BorderAround xlContinuous, xlMedium
End With
With Range("K2").Resize(N, N)
.Font.Bold = True
.HorizontalAlignment = xlCenter
.Copy
Range("A2").PasteSpecial xlPasteFormats
Range("A13").PasteSpecial xlPasteFormats
Application.CutCopyMode = False
.Cells(1).Select
.Interior.Color = 15921906
.Offset(0).Resize(B).Value = [{6,0,0,0,3,0,0,0,2;1,7,0,8,0,5,0,0,0;0,0,0,0,7,0,0,0,0}]
.Offset(3).Resize(B).Value = [{0,8,3,0,0,0,0,4,0;0,0,0,0,0,0,0,0,0;5,0,9,0,0,0,6,0,0}]
.Offset(6).Resize(B).Value = [{0,9,0,0,4,0,7,8,0;7,0,0,0,5,0,9,1,0;0,0,0,0,0,1,0,3,0}]
.Replace "0", ""
End With
With Range("u2:x3")
Set sp = .Worksheet.Shapes.AddShape(msoShapeRoundedRectangle, .Left, .Top, .Width, .Height)
sp.Name = 解析
End With
With sp
.Shadow.Type = msoShadow21
.OnAction = "妥当性チェック"
With .TextFrame2
.VerticalAnchor = msoAnchorMiddle
With .TextRange
.Font.Bold = msoTrue
.Font.Size = 14
.Text = "解析"
.ParagraphFormat.Alignment = msoAlignCenter
End With
End With
End With
End Sub
Private Function hikaku(Board() As Long, Dummy() As Long) As Boolean
Dim i As Long
For i = LBound(Board) To UBound(Board)
If Board(i) <> Dummy(i) Then
hikaku = True
Exit For
End If
Next
End Function
Private Sub boardget(Board() As Long)
Dim r As Range
Dim i As Long, ans As Long
ReDim Board(1 To N * N)
For Each r In Worksheets(1).Range("K2").Resize(N, N)
i = i + 1
ans = Val(r.Value)
If ans < 1 Or 9 < ans Then
ans = 0
r.ClearContents
End If
Board(i) = ans
Next
End Sub
Function check2(Board() As Long, pos As Long)
Dim newPos As Long
Dim j As Long, k As Long
Dim newValue As Long
flag = False
If pos = N * N + 1 Then
Err.Raise vbObjectError + 513
End If
For k = pos To N * N
If Board(k) = 0 Then
newPos = k
Exit For
End If
Next
ReDim randBoard9(1 To N) As Long
Call rand9(randBoard9)
For j = 1 To N
newValue = randBoard9(j)
If canBePlaced(Board, newPos, newValue) Then
Board(newPos) = newValue
Call check2(Board, newPos + 1)
Board(newPos) = 0
End If
Next
flag = True
End Function
Private Function myPrint2(Board() As Long, r As Long)
Dim k As Long, j As Long
ReDim v(1 To N, 1 To N)
For k = 1 To N
For j = 1 To N
v(k, j) = Board((k - 1) * N + j)
Next
Next
Cells((r - 1) * 11 + 2, "A").Resize(N, N).Value = v
End Function
Private Function rand9(randomBoard() As Long) '1〜9の値をランダムにシャッフル
Dim k As Long, i As Long, j As Long, tmp As Long
For k = 1 To N
randomBoard(k) = k
Next
'ランダムにシャッフル
For i = 1 To N
j = Int(Rnd() * N) + 1
If i <> j Then
tmp = randomBoard(i)
randomBoard(i) = randomBoard(j)
randomBoard(j) = tmp
End If
Next
End Function
Private Function canBePlaced(Board() As Long, pos&, v&) As Boolean
'check関数で仮に入れられた値が、縦・横・その値の所属の3×3マスに
'既にあれば Falseを返す(なければ Trueを返す)
Dim r As Long, c As Long
Dim i As Long, j As Long, topLeft As Long
r = ((pos - 1) \ N) + 1 ' r=行
c = ((pos - 1) Mod N) + 1 ' c=列
For i = 1 To N
If Board((r - 1) * N + i) = v Then '同じ行に同一値があれば
canBePlaced = False: Exit Function
End If
If Board(c + (i - 1) * N) = v Then '同じ列に同一値があれば
canBePlaced = False: Exit Function
End If
Next
topLeft = N * ((r - 1) \ B) * B + ((c - 1) \ B) * B + 1
For i = 1 To B
For j = 1 To B
'3×3ボックス内に同一値があれば
If Board(topLeft + (i - 1) * N + (j - 1)) = v Then
canBePlaced = False: Exit Function
End If
Next
Next
canBePlaced = True
End Function
(kazuo) 2020/05/02(土) 19:15
こんばんは。ちょっと書こうかどうか迷ったのですが、
γさんの On Error ステートメントの使い方に非常に違和感を感じます。
Resume ステートメントはエラー処理ルーチンの中で使うべきもので、 通常の処理の中で使うものではないかと思います。
今回の例では、 ・Errorが発生したとき、successに飛ぶ ・Resume Next が実行されるため、エラー発生行の次の行にとぶ ・Resume Next が実行される(なにもおこらないけど...)
と、Resume Nextで戻った先がResume Nextで、 都合2回 Resume Next が実行されるのが私の違和感の元です。 これで問題なく動いてるでしょといわれれば、言葉がないですが、
素直に On Error Resume Next で処理すべきかと思います。
For k = 1 To kosu
checkCounter = 0
ReDim board(1 To N * N)
On Error Resume Next
Call check(board, 1)
flgSucccess = Err.Number
On Error Goto 0
If flgSucccess = bObjectError + 513 Then Call myPrint(board, k) 'シート書き込み
Next
(´・ω・`) 2020/05/02(土) 21:14
(´・ω・`)さん、ありがとうございました。 On Error Resume Nextの機能をよく理解していなかったようです。 (ヘルプ引用) | On Error Resume Next ステートメントは、 | 実行時エラーを発生させたステートメントの直後にあるステートメント、 | または On Error Resume Next ステートメントを含むプロシージャから最後に呼び出しを行った直後のステートメントを使って、 | 実行を継続します。 私は、どうやら前者のイメージしか持っていなかったので、checkをすべてたためないだろうと思っていました。 助かりました。 (γ) 2020/05/02(土) 23:19
On Error Resume Nextは、1か所で十分?
On Error Resume Next
For i = 1 To 10
ans = 1 / 0
If Err Then
Err.Clear
End If
Next
MsgBox i
(BJ) 2020/05/02(土) 23:42
>On Error Resume Nextは、1か所で十分? そうですね。 Err.Clearを使った方がいいですね。 Microsoftのサイトにもそのような例が示されています。 https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/clear-method-visual-basic-for-applications
個人的な好みでというかクセで On Error は、できるだけピンポイントに仕掛けたいので、 今回このように書きました。 ただ、ループの中にいれて On Error Resume Next と On Error Goto 0 を 繰り返すと処無駄な理コストがかかっちゃいますね。 (´・ω・`) 2020/05/03(日) 05:58
Excel vba 関数機能別一覧表 :
https://www.ceodata.com/excel-functions/
(γ) 2020/05/03(日) 10:37
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.