2日前に、Python 3で解決しようとした数独の問題が発生しました。解決策は存在するという通知を受けましたが、複数の解決策が存在するかどうかはわかりません。
問題は次のとおりです。数独の9x9グリッドが完全に空です。ただし、色付きのボックスは含まれており、これらのボックス内では、数値の合計は平方数でなければなりません。それ以外は、通常の数独のルールが適用されます。
ここでの問題は、数独パズルを解決することではなく、色付きのボックスのルールを満たす実行可能なパズルを生成することです。
私の戦略
numpy配列を使用して、グリッドを81個のインデックスに分割しました。これを9x9グリッドに再配置できます。
import numpy as np
print(np.array([i for i in range(81)]).reshape((9, 9)))
->
[[ 0 1 2 3 4 5 6 7 8]
[ 9 10 11 12 13 14 15 16 17]
[18 19 20 21 22 23 24 25 26]
[27 28 29 30 31 32 33 34 35]
[36 37 38 39 40 41 42 43 44]
[45 46 47 48 49 50 51 52 53]
[54 55 56 57 58 59 60 61 62]
[63 64 65 66 67 68 69 70 71]
[72 73 74 75 76 77 78 79 80]]
以下は、インデックスのすべてのブロックを含むリストです。
boxes = [[44, 43, 42, 53],[46, 47, 38],[61, 60],[69, 70],[71, 62],
[0, 9, 18],[1, 10, 11, 20],[2, 3, 12],[4, 13, 14],[5, 6],
[7, 8],[17, 26, 35],[21, 22, 23],[15, 16, 24, 25, 34],
[27, 36, 37],[19, 28, 29],[45, 54],[55, 56],[63, 64, 65],
[72, 73, 74],[57, 66, 75 ],[58, 59, 67, 68],[76, 77],[78, 79, 80]]
写真または上の配列からわかるように、ボックスは2、3、4、または5のブロックに配置されています(8 2、12 3、3 4、1 5)。数独のルールを破ることなく、ボックスに複数の数値を含めることができることにも気付きましたが、可能なのは1つの数値のうち2つだけです。その情報を考えると、可能な最大の平方は36となり、9 + 9 + 8 + 7 + 6 = 39となるため、ブロックの合計が49に達することはありません。リストの合計に平方数が含まれているかどうかを確認するには、次の関数を作成しました。
def isSquare(array):
if np.sum(array) in [i**2 for i in range(1,7)]:
return True
else:
return False
リストに正しい量の重複、つまり1つの数値のみの複数の重複が含まれているかどうかを確認するために、次の関数を作成しました。
def twice(array):
counter = [0]*9
for i in range(len(array)):
counter[array[i]-1]+=1
if 3 in counter:
return False
if counter.count(2)>1:
return False
return True
1から9の数字が与えられた場合、リストを合計して平方数にする必要がある場合、リストを解く方法は限られています。itertoolsを使用して、解決策を見つけ、配列に分割します。ここで、インデックス0は2のブロックを含み、インデックス1は3のブロックを含みます。
from itertools combinations_with_replacement
solutions = []
for k in range(2, 6):
solutions.append([list(i) for i in combinations_with_replacement(np.arange(1, 10), k) if
isSquare(i) and twice(i)])
ただし、これらのリストの順列は、「二乗問題」の実行可能な解決策です。itertoolsを再度使用すると、可能な数のボックス(数独のルールなし)の合計は8782になります。
from itertools import permutations
def find_squares():
solutions = []
for k in range(2, 6):
solutions.append([list(i) for i in combinations_with_replacement(np.arange(1, 10), k) if
isSquare(i) and twice(i)])
s = []
for item in solutions:
d=[]
for arr in item:
for k in permutations(arr):
d.append(list(k))
s.append(d)
return s # 4-dimensional array, max 2 of each
solutions = find_squares()
total = sum([len(i) for i in solutions])
print(total)
-> 8782
これは、ボードが合法であるかどうかを決定する機能を実装するのに十分なはずです。つまり、行、列、ボックスに1〜9の数字が1つだけ含まれています。私の実装:
def legal_row(arr):
for k in range(len(arr)):
values = []
for i in range(len(arr[k])):
if (arr[k][i] != 0):
if (arr[k][i] in values):
return False
else:
values.append(arr[k][i])
return True
def legal_column(arr):
return legal_row(np.array(arr, dtype=int).T)
def legal_box(arr):
return legal_row(arr.reshape(3,3,3,3).swapaxes(1,2).reshape(9,9))
def legal(arr):
return (legal_row(arr) and legal_column(arr) and legal_box(arr))
ランタイムの難しさ
簡単なアプローチは、すべてのブロックのすべての組み合わせをチェックすることです。私はこれを実行し、いくつかの実行可能な問題を生成しましたが、私のアルゴリズムの複雑さにより、これには非常に長い時間がかかります。
代わりに、いくつかのプロパティをランダム化しようとしました:ブロックの順序とソリューションの順序。これを使用して、試行回数を制限し、解決策が実行可能かどうかを確認しました。
attempts = 1000
correct = 0
possibleBoards = []
for i in range(1, attempts+1):
board = np.zeros((9, 9), dtype=int)
score = 0
shapes = boxes
np.random.shuffle(shapes)
for block in shapes:
new_board = board
new_1d = board.reshape(81)
all_sols = solutions[len(block)-2]
np.random.shuffle(all_sols)
for sols in all_sols:
#print(len(sols))
new_1d[block] = sols
new_board = new_1d.reshape((9, 9))
if legal(new_board):
board = new_board
score+=1
break
confirm = board.reshape(81)
#solve(board) # Using my solve function, not important here
# Note that without it, correct would always be 0 as the middle of the puzzle has no boxes
confirm = board.reshape(81)
if (i%1000==0 or i==1):
print("Attempt",i)
if 0 not in confirm:
correct+=1
print(correct)
possibleBoards.append(board)
上記のコードでは、変数スコアは、アルゴリズムが試行中に検出できたブロックの数を示しています。変数correctは、生成された数独ボードの何枚を完了できたかを示します。700回の試行でどれだけうまく機能したかに興味がある場合は、ここにいくつかの統計を示します(これは履歴グラフで、x軸はスコアを表し、y軸はこれらの700回の試行中に存在した各スコアの数を表します)。
助けが必要なもの
私はこの問題の解決策を見つけるための実行可能な方法を見つけるのに苦労しています。この解決策は、実際には有限の時間で実行できます。私のコードのいくつかを高速化または改善するためのヒント、問題への異なるアプローチのアイデア、問題の解決策、またはこの問題に関連するPython / Numpyに関するいくつかの有用なヒントを大いに感謝します。
isSquare(a): return math.sqrt(sum(a)) % 1.0 == 0
とか、ぐらい。[i**2 for i in range(1,7)]
毎回再計算してその中で線形検索を行うよりもかなり高速です。また、in
すでにブール値を返します。の必要はありませんif
。