さて、構文を修正することで、物事を少し簡単にすることができます。
def r(a):
i = a.find('0')
~i or exit(a)
[m in[(i-j)%9*(i/9^j/9)*(i/27^j/27|i%9/3^j%9/3)or a[j]for j in range(81)] or r(a[:i]+m+a[i+1:])for m in'%d'%5**18]
from sys import *
r(argv[1])
少し片付け:
from sys import exit, argv
def r(a):
i = a.find('0')
if i == -1:
exit(a)
for m in '%d' % 5**18:
m in[(i-j)%9*(i/9^j/9)*(i/27^j/27|i%9/3^j%9/3) or a[j] for j in range(81)] or r(a[:i]+m+a[i+1:])
r(argv[1])
さて、このスクリプトはコマンドライン引数を期待し、その上で関数rを呼び出します。その文字列にゼロがない場合、rは終了し、引数を出力します。
(別のタイプのオブジェクトが渡された場合、Noneはゼロを渡すことと同等であり、他のオブジェクトはsys.stderrに出力され、終了コードは1になります。特にsys.exit( "some error message")はエラーが発生したときにプログラムを終了する簡単な方法。http://www.python.org/doc/2.5.2/lib/module-sys.htmlを参照して
ください)
これは、ゼロがオープンスペースに対応し、ゼロのないパズルが解かれることを意味していると思います。次に、その厄介な再帰式があります。
ループは興味深いです: for m in'%d'%5**18
なぜ5 ** 18?に'%d'%5**18
評価されることがわかり'3814697265625'
ます。これは、少なくとも1回は1〜9の各桁を持つ文字列であるため、それぞれを配置しようとしている可能性があります。実際、これが実行しているように見えますr(a[:i]+m+a[i+1:])
。rを再帰的に呼び出し、最初の空白にその文字列の数字を入力します。ただし、これは、前の式がfalseの場合にのみ発生します。それを見てみましょう:
m in [(i-j)%9*(i/9^j/9)*(i/27^j/27|i%9/3^j%9/3) or a[j] for j in range(81)]
したがって、配置はmがそのモンスターリストにない場合にのみ行われます。各要素は、数値(最初の式がゼロ以外の場合)または文字(最初の式がゼロの場合)のいずれかです。mは、文字として表示される場合、可能な置換として除外されます。これは、最初の式がゼロの場合にのみ発生する可能性があります。式がゼロになるのはいつですか?
乗算される3つの部分があります。
(i-j)%9
iとjが9の倍数離れている場合、つまり同じ列の場合、これはゼロです。
(i/9^j/9)
i / 9 == j / 9、つまり同じ行の場合、これはゼロです。
(i/27^j/27|i%9/3^j%9/3)
これらの両方がゼロの場合、これはゼロです。
i/27^j^27
i / 27 == j / 27の場合、つまり3行の同じブロックの場合はゼロです。
i%9/3^j%9/3
i%9/3 == j%9/3、つまり3列の同じブロックの場合はゼロです
これらの3つの部分のいずれかがゼロの場合、式全体がゼロになります。つまり、iとjが行、列、または3x3ブロックを共有している場合、jの値をiの空白の候補として使用することはできません。あはは!
from sys import exit, argv
def r(a):
i = a.find('0')
if i == -1:
exit(a)
for m in '3814697265625':
okay = True
for j in range(81):
if (i-j)%9 == 0 or (i/9 == j/9) or (i/27 == j/27 and i%9/3 == j%9/3):
if a[j] == m:
okay = False
break
if okay:
r(a[:i]+m+a[i+1:])
r(argv[1])
どの配置もうまくいかない場合、rは他の何かを選択できるポイントに戻って戻るので、これは基本的な深さ優先アルゴリズムであることに注意してください。
ヒューリスティックを使用しないため、特に効率的ではありません。私はウィキペディア(http://en.wikipedia.org/wiki/Sudoku)からこのパズルを取りました:
$ time python sudoku.py 530070000600195000098000060800060003400803001700020006060000280000419005000080079
534678912672195348198342567859761423426853791713924856961537284287419635345286179
real 0m47.881s
user 0m47.223s
sys 0m0.137s
補遺:メンテナンスプログラマーとしてどのように書き直すか(このバージョンでは約93倍のスピードアップがあります:)
import sys
def same_row(i,j): return (i/9 == j/9)
def same_col(i,j): return (i-j) % 9 == 0
def same_block(i,j): return (i/27 == j/27 and i%9/3 == j%9/3)
def r(a):
i = a.find('0')
if i == -1:
sys.exit(a)
excluded_numbers = set()
for j in range(81):
if same_row(i,j) or same_col(i,j) or same_block(i,j):
excluded_numbers.add(a[j])
for m in '123456789':
if m not in excluded_numbers:
r(a[:i]+m+a[i+1:])
if __name__ == '__main__':
if len(sys.argv) == 2 and len(sys.argv[1]) == 81:
r(sys.argv[1])
else:
print 'Usage: python sudoku.py puzzle'
print ' where puzzle is an 81 character string representing the puzzle read left-to-right, top-to-bottom, and 0 is a blank'