Cóż, możesz trochę ułatwić, poprawiając składnię:
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])
Trochę sprzątania:
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])
Okej, więc ten skrypt oczekuje argumentu wiersza poleceń i wywołuje na nim funkcję r. Jeśli w tym łańcuchu nie ma zer, r kończy działanie i wyświetla swój argument.
(Jeśli przekazano inny typ obiektu, None jest równoznaczne z przekazaniem zera, a każdy inny obiekt jest wypisywany do sys.stderr i skutkuje kodem zakończenia 1. W szczególności sys.exit ("jakiś komunikat o błędzie") jest szybki sposób na zamknięcie programu w przypadku wystąpienia błędu. Zobacz
http://www.python.org/doc/2.5.2/lib/module-sys.html )
Myślę, że oznacza to, że zera odpowiadają otwartej przestrzeni, a łamigłówka bez zer jest rozwiązana. Potem jest to okropne, rekurencyjne wyrażenie.
Pętla jest interesująca: for m in'%d'%5**18
Dlaczego 5 ** 18? Okazuje się, że '%d'%5**18
ocenia się do '3814697265625'
. Jest to ciąg, który ma każdą cyfrę 1-9 przynajmniej raz, więc może próbuje umieścić każdą z nich. W rzeczywistości wygląda na to, co r(a[:i]+m+a[i+1:])
robi: rekurencyjne wywołanie r, z pierwszym odstępem wypełnionym cyfrą z tego ciągu. Ale dzieje się tak tylko wtedy, gdy wcześniejsze wyrażenie jest fałszywe. Spójrzmy na to:
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)]
Tak więc umieszczanie jest wykonywane tylko wtedy, gdy m nie ma na liście potworów. Każdy element jest liczbą (jeśli pierwsze wyrażenie jest różne od zera) lub znakiem (jeśli pierwsze wyrażenie ma wartość zero). m jest wykluczone jako możliwe podstawienie, jeśli występuje jako znak, co może się zdarzyć tylko wtedy, gdy pierwsze wyrażenie ma wartość zero. Kiedy wyrażenie ma wartość zero?
Ma trzy części, które są pomnożone:
(i-j)%9
czyli zero, jeśli i i j są od siebie wielokrotnością 9, czyli ta sama kolumna.
(i/9^j/9)
czyli zero, jeśli i / 9 == j / 9, czyli ten sam wiersz.
(i/27^j/27|i%9/3^j%9/3)
czyli zero, jeśli oba są zerowe:
i/27^j^27
czyli zero, jeśli i / 27 == j / 27, czyli ten sam blok trzech wierszy
i%9/3^j%9/3
co jest zerem, jeśli i% 9/3 == j% 9/3, czyli ten sam blok trzech kolumn
Jeśli którakolwiek z tych trzech części wynosi zero, całe wyrażenie wynosi zero. Innymi słowy, jeśli i i j mają wspólny wiersz, kolumnę lub blok 3x3, to wartość j nie może być użyta jako kandydat na puste miejsce w i. Aha!
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])
Zwróć uwagę, że jeśli żadne z miejsc docelowych nie zadziała, r powróci i powróci do punktu, w którym można wybrać coś innego, więc jest to podstawowy algorytm głębokości.
Bez heurystyki nie jest to szczególnie wydajne. Wziąłem tę układankę z Wikipedii ( http://en.wikipedia.org/wiki/Sudoku ):
$ time python sudoku.py 530070000600195000098000060800060003400803001700020006060000280000419005000080079
534678912672195348198342567859761423426853791713924856961537284287419635345286179
real 0m47.881s
user 0m47.223s
sys 0m0.137s
Dodatek: Jak przepisałbym to jako programista konserwacyjny (ta wersja ma około 93x przyspieszenie :)
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'