web-dev-qa-db-ja.com

Python-どのように機能しますか?

私は自分の数独ソルバーで遊んでいて、これに出くわしたとき、良い高速なデザインへのポインタを探していました。

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])

私の実装は頭の中で数独を解くのと同じように数独を解きますが、この暗号アルゴリズムはどのように機能しますか?

http://scottkirkwood.blogspot.com/2006/07/shortest-sudoku-solver-in-python.html

76
Ville Salonen

まあ、構文を修正することで少し簡単になります:

_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〜9の文字列であるため、各桁を配置しようとしている可能性があります。実際、これはr(a[:i]+m+a[i+1:])が行っていることのように見えます:再帰的にrを呼び出し、その文字列の数字で最初の空白を埋めます。ただし、これは前の式が偽の場合にのみ発生します。それを見てみましょう:

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が文字として表示される場合、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:
      # At this point, m is not excluded by any row, column, or block, so let's place it and recurse
      r(a[:i]+m+a[i+1:])

r(argv[1])
_

どの配置もうまくいかない場合、rは戻って他のものを選択できるポイントに戻ることに注意してください。したがって、これは基本的な深さ優先アルゴリズムです。

ヒューリスティックを使用しないため、特に効率的ではありません。私はこのパズルをウィキペディアから取りました( http://en.wikipedia.org/wiki/Sudok ):

_$ 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:
      # At this point, m is not excluded by any row, column, or block, so let's place it and recurse
      r(a[:i]+m+a[i+1:])

if __== '__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'
_
215
Bill Barksdale

わかりやすくする:

def r(a):
    i = a.find('0') # returns -1 on fail, index otherwise
    ~i or exit(a) # ~(-1) == 0, anthing else is not 0
                  # thus: if i == -1: exit(a)
    inner_lexp = [ (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)]  # r appears to be a string of 81 
                                        # characters with 0 for empty and 1-9 
                                        # otherwise
    [m in inner_lexp or r(a[:i]+m+a[i+1:]) for m in'%d'%5**18] # recurse
                            # trying all possible digits for that empty field
                            # if m is not in the inner lexp

from sys import *
r(argv[1]) # thus, a is some string

したがって、内側のリスト式を作成するだけです。私はそれが行に設定された数字を収集することを知っています-さもなければ、それの周りのコードは意味がありません。しかし、私はそれがどのようにそれを行うのか本当の手掛かりを持っていません(そして、そのバイナリの空想を今すぐ解決するのに疲れすぎています、申し訳ありません)

9
Tetha

r(a)は、各ステップでボードの_0_を埋めようとする再帰関数です。

i=a.find('0');~i or exit(a)は成功時の終了です。ボードに_0_値がもうない場合は、これで完了です。

mは、_0_を埋めようとする現在の値です。

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を現在の_0_に入れるのが明らかに間違っている場合、真実であると評価します。 「is_bad」というニックネームを付けましょう。これが最も扱いにくいビットです。 :)

_is_bad or r(a[:i]+m+a[i+1:]_は条件付き再帰ステップです。現在のソリューション候補が正気であると思われる場合、ボード内の次の_0_を再帰的に評価しようとします。

_for m in '%d'%5**18_は、1から9までのすべての数値を列挙します(非効率的)。

6
Deestan

短い数独ソルバーの多くは、セルが正常に満たされるまで、残っているすべての有効な数値を再帰的に試します。私はこれを分解していませんが、ただそれをざっと見て、それがそれがすることのように見えます。

4
Lou Franco

コードは実際には機能しません。自分でテストできます。未解決の数独パズルのサンプルを次に示します。

807000003602080000000200900040005001000798000200100070004003000000040108300000506

このWebサイト( http://www.sudokuwiki.org/sudoku.htm )を使用して、パズルのインポートをクリックし、上記の文字列をコピーするだけです。 pythonプログラムの出力は:817311213622482322131224934443535441555798655266156777774663869988847188399979596

これはソリューションに対応していません。実際、最初の行に2つの1があり、矛盾がすでに見られます。

1
Basil