[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『数独・ナンプレの解答をランダムで作りたい』(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.