質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1160閲覧

【Python版】数独ソルバーが誤動作してしまう

trami

総合スコア25

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2019/07/14 22:47

前提・実現したいこと

Pythonで数独ソルバーを作成しています.おおよその構図はできたと思ったのですが,動かしてみたらうまくいかず,試していく打ちに,間違いの所に数字を入れてしまい,問題が解けなくなっていました.

数独ソルバーのアルゴリズムとしては,
1.データの読み込み
2.値が空欄のところの座標を取得する.
3.1から9までが入ったcandidatesリストを作成し,縦・横・ボックスと比較して,値がすでに存在すれば,リストからその値を削除する.
4.candidatesリストのようそcandidateの要素が1つとなったところから埋めていく.
5.2~4を繰り返す.

といった感じです.

発生している問題・エラーメッセージ

数独ソルバーが正しく動作しない.具体的には,正しい答えではない所に数字を入れてしまい,数独を解けなくしている.無限ループに陥っています.

該当のソースコード

Python

1 2import numpy as np 3 4grid = '27.....163..574..8.....2....637.5.2..5.....6..8.2.345....1.....6..348..114.....85' 5 6def values_from_grid(grid): 7 "テキストから二次元配列のValuesを作成する" 8 values = [] 9 digits = "123456789" 10 chars = [c for c in grid if c in digits or c in '0.'] #01234567890.のみ取得.空欄は'0.'に対応 11 assert len(chars) == 81 #charsが81文字分ないと,エラーを発して強制終了 12 grid_int = list(map(lambda x: int(x) if x != "." else 0, chars)) #数字をcharからintへ.'.'があれば0にする 13 values = np.array(grid_int).reshape(9, 9) 14 15 return values 16 17def index_not_solved(values): #i:y, j:x 18 "空欄のインデックスを取得" 19 pair_indices = [] 20 for y in range(9): 21 for x in range(9): 22 if values[y,x] == 0: 23 pair_indices.append([y,x]) 24 25 return pair_indices 26 27def find_candidate(values, y, x): 28 "空欄に入る候補を探索" 29 candidates = [1,2,3,4,5,6,7,8,9] 30 31 candidates = list(set(candidates) - set(values[:,x])) 32 33 candidates = list(set(candidates) - set(values[y,:])) 34 35 box_tmp = values[(3*(y//3)):(3*(y//3) + 3),(3*(x//3)):(3*(x//3) + 3)].flatten() 36 candidates = list(set(candidates) - set(box_tmp)) 37 38 return candidates 39 40def unique_candidate(candidates, pair_indices, values): 41 "単一候補を埋める" 42 cnt_delete = 0 43 for i, (candidate, pair_index) in enumerate(zip(candidates, pair_indices)): 44 if len(candidate) == 1: 45 values[pair_index[0], pair_index[1]] = candidate[0] 46 candidates.pop(i - cnt_delete) 47 pair_indices.pop(i - cnt_delete) 48 cnt_delete += 1 49 return [candidates, pair_indices, values] 50 51values = values_from_grid(grid) 52pair_indices = index_not_solved(values) 53 54cnt = 1 55while len(pair_indices) > 0: 56 57 candidates = [] 58 for pair_index in pair_indices: 59 candidates.append(find_candidate(values, *pair_index)) 60 pair_indices = index_not_solved(values) 61 62 candidates, pair_indices, values = unique_candidate(candidates, pair_indices, values) 63 64 cnt += 1 65 if cnt > 50: 66 print('I cannot solve...') 67 print('Cuurent solution is') 68 print(values) 69 break 70 71else : 72 print('final solution is') 73 print(values) 74

試したこと

candidatesやvaluesを適宜,print()しながら中身をチェックしたところ,unique_canidiate()のところあたりが原因のような気がすることはわかったのですが,それ以上はわかりませんでした.
values_from_grid,index_not_solved,find_candidateの関数はおそらく問題はないと思われました.

補足情報(FW/ツールのバージョンなど)

Python3.x系を使っています.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

ループしながらループ対象を操作すると分かりづらいバグの原因になります。そのコードはそういう事態を想定していないのでは?

とりあえずこれで。

python

1def unique_candidate(candidates, pair_indices, values): 2 "単一候補を埋める" 3 cnt_delete = 0 4 for i, (candidate, pair_index) in enumerate(zip(candidates[:], pair_indices[:])): 5 if len(candidate) == 1: 6 values[pair_index[0], pair_index[1]] = candidate[0] 7 candidates.pop(i - cnt_delete) 8 pair_indices.pop(i - cnt_delete) 9 cnt_delete += 1 10 return [candidates, pair_indices, values] 11

注釈 ループ中でのシーケンスの変更には微妙な問題があります (これはミュータブルなシーケンスのみ、例えばリストで起こり得ます)。 どの要素が次に使われるかを追跡するために、内部的なカウンタが使われており、このカウンタは反復のたびに加算されます。 このカウンタがシーケンスの長さに達すると、ループは終了します。 このことから、スイートの中でシーケンスから現在の (または以前の) 要素を除去すると、(次の要素の位置が、既に処理済みの現在の要素のインデックスになるために) 次の要素が飛ばされることになります。 同様に、スイートの中でシーケンス中の現在の要素以前に要素を挿入すると、現在の要素がループの次の週で再度扱われることになります。 こうした仕様は、厄介なバグにつながります。 これは、シーケンス全体のスライスを使って一時的なコピーを作ることで避けられます。 例えば次のようにします:

python

1for x in a[:]: 2 if x < 0: a.remove(x)

https://docs.python.org/ja/3/reference/compound_stmts.html#the-for-statement

投稿2019/07/14 22:57

hayataka2049

総合スコア30933

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問