[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



답변

글쎄요, 구문을 수정하면 좀 더 쉽게 할 수 있습니다.

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을 호출합니다. 해당 문자열에 0이 없으면 r이 종료되고 인수를 인쇄합니다.

(다른 유형의 객체가 전달되면 None은 0을 전달하는 것과 동일하며 다른 객체는 sys.stderr에 인쇄되고 종료 코드 1이됩니다. 특히 sys.exit ( “some error message”)는 오류 발생시 프로그램을 종료하는 빠른 방법.
http://www.python.org/doc/2.5.2/lib/module-sys.html 참조 )

이것은 0이 열린 공간에 해당하고 0이없는 퍼즐이 해결된다는 것을 의미한다고 생각합니다. 그런 다음 불쾌한 재귀 표현이 있습니다.

루프는 흥미 롭습니다. 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이 해당 몬스터 목록에없는 경우에만 수행됩니다. 각 요소는 숫자 (첫 번째 표현식이 0이 아닌 경우) 또는 문자 (첫 번째 표현식이 0 인 경우)입니다. m은 문자로 표시되는 경우 가능한 대체로 제외되며 첫 번째 표현식이 0 인 경우에만 발생할 수 있습니다. 식이 언제 0입니까?

곱해지는 세 부분이 있습니다.

  • (i-j)%9 i와 j가 9의 배수, 즉 동일한 열이면 0입니다.
  • (i/9^j/9) i / 9 == j / 9, 즉 동일한 행이면 0입니다.
  • (i/27^j/27|i%9/3^j%9/3) 둘 다 0이면 0입니다.
    • i/27^j^27 i / 27 == j / 27이면 0, 즉 3 개 행의 동일한 블록
    • i%9/3^j%9/3 i % 9 / 3 == j % 9 / 3 인 경우 0입니다. 즉, 3 개 열의 동일한 블록

이 세 부분 중 하나가 0이면 전체 표현식은 0입니다. 즉, i와 j가 행, 열 또는 3×3 블록을 공유하면 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은 다른 것을 선택할 수있는 지점으로 돌아와 백업하므로 기본 깊이 우선 알고리즘입니다.

휴리스틱을 사용하지 않고 특히 효율적이지 않습니다. Wikipedia ( 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:
      # 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 __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'


답변

난독 해제 :

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

따라서 내부 목록 표현식을 해결하면됩니다. 줄에 설정된 숫자를 수집한다는 것을 알고 있습니다. 그렇지 않으면 주변 코드가 의미가 없습니다. 그러나 나는 그것이 어떻게하는지에 대한 실제 단서가 없습니다 (그리고 나는 지금 그 바이너리 공상을 해결하기에는 너무 피곤합니다, 죄송합니다)


답변

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까지의 모든 숫자를 열거합니다 (비효율적으로).


답변

많은 짧은 스도쿠 솔버는 셀을 성공적으로 채울 때까지 남은 가능한 모든 법적 번호를 재귀 적으로 시도합니다. 나는 이것을 분해하지 않았지만 그것을 훑어 보면 그것이하는 일인 것 같습니다.


답변

코드는 실제로 작동하지 않습니다. 직접 테스트 할 수 있습니다. 다음은 미해결 스도쿠 퍼즐 샘플입니다.

807000003602080000000200900040005001000798000200100070004003000000040108300000506

이 웹 사이트 ( http://www.sudokuwiki.org/sudoku.htm )를 사용하여 퍼즐 가져 오기를 클릭하고 위의 문자열을 복사하면됩니다. Python 프로그램의 출력은 다음과 같습니다. 817311213622482322131224934443535441555798655266156777774663869988847188399979596

솔루션에 해당하지 않습니다. 사실 여러분은 이미 모순을 볼 수 있습니다. 첫 번째 줄에 2 개의 1이 있습니다.


답변