Bộ giải Sudoku ngắn nhất bằng Python - Nó hoạt động như thế nào?


81

Tôi đang chơi xung quanh với bộ giải Sudoku của riêng mình và đang tìm kiếm một số gợi ý về thiết kế tốt và nhanh khi tôi bắt gặp điều này:

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

Việc triển khai của riêng tôi giải quyết Sudokus giống như cách tôi giải quyết chúng trong đầu nhưng thuật toán khó hiểu này hoạt động như thế nào?

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


21
trông giống như một mục vào cuộc thi perl khó hiểu! Tôi nghĩ một trong những điểm của trăn là để viết mã sạch mà có thể dễ dàng hiểu :)
warren

1
Con trăn đó trông không giống như được thụt vào một cách chính xác. : /
Jake

18
Đây là một bằng chứng khác cho thấy bạn có thể viết mã khó hiểu bằng bất kỳ ngôn ngữ nào.
JesperE

Tôi nghĩ rằng đây phải là một câu trả lời chơi gôn mã.
Loren Pechtel,

2
BTW Tôi khá chắc rằng đây là một cuộc thi viết bộ giải sudoku ngắn nhất có thể.
John

Câu trả lời:


220

Chà, bạn có thể làm mọi thứ dễ dàng hơn một chút bằng cách sửa cú pháp:

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

Dọn dẹp một chút:

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

Được rồi, vì vậy tập lệnh này mong đợi một đối số dòng lệnh và gọi hàm r trên đó. Nếu không có số 0 nào trong chuỗi đó, r sẽ thoát và in ra đối số của nó.

(Nếu một loại đối tượng khác được truyền, None tương đương với việc chuyển số 0 và bất kỳ đối tượng nào khác được in ra sys.stderr và dẫn đến mã thoát là 1. Cụ thể, sys.exit ("một số thông báo lỗi") là một cách nhanh chóng để thoát khỏi chương trình khi xảy ra lỗi. Xem http://www.python.org/doc/2.5.2/lib/module-sys.html )

Tôi đoán điều này có nghĩa là số không tương ứng với không gian mở và một câu đố không có số không sẽ được giải. Sau đó, có biểu thức đệ quy khó chịu đó.

Vòng lặp là thú vị: for m in'%d'%5**18

Tại sao 5 ** 18? Nó chỉ ra rằng '%d'%5**18đánh giá đến '3814697265625'. Đây là một chuỗi có mỗi chữ số 1-9 ít nhất một lần, vì vậy có thể nó đang cố gắng đặt từng chữ số đó. Trên thực tế, có vẻ như đây là những gì r(a[:i]+m+a[i+1:])đang làm: gọi đệ quy r, với ô trống đầu tiên được điền bởi một chữ số từ chuỗi đó. Nhưng điều này chỉ xảy ra nếu biểu thức trước đó là sai. Hãy xem xét điều đó:

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

Vì vậy, việc sắp xếp chỉ được thực hiện nếu m không có trong danh sách quái vật đó. Mỗi phần tử là một số (nếu biểu thức đầu tiên khác không) hoặc một ký tự (nếu biểu thức đầu tiên là 0). m được loại trừ là thay thế có thể xảy ra nếu nó xuất hiện dưới dạng ký tự, điều này chỉ có thể xảy ra nếu biểu thức đầu tiên bằng 0. Khi nào thì biểu thức bằng 0?

Nó có ba phần được nhân lên:

  • (i-j)%9 là 0 nếu i và j là bội số của 9 cách nhau, tức là cùng một cột.
  • (i/9^j/9) là 0 nếu i / 9 == j / 9, tức là cùng một hàng.
  • (i/27^j/27|i%9/3^j%9/3) là 0 nếu cả hai đều bằng 0:
    • i/27^j^27 bằng 0 nếu i / 27 == j / 27, tức là cùng một khối gồm ba hàng
    • i%9/3^j%9/3 bằng 0 nếu i% 9/3 == j% 9/3, tức là cùng một khối gồm ba cột

Nếu bất kỳ phần nào trong ba phần này bằng không, toàn bộ biểu thức bằng không. Nói cách khác, nếu tôi và j chia sẻ một hàng, cột hoặc khối 3x3, thì giá trị của j không thể được sử dụng làm ứng cử viên cho ô trống tại 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:
      # 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])

Lưu ý rằng nếu không có vị trí nào hoạt động hiệu quả, r sẽ quay lại và sao lưu đến điểm có thể chọn thứ gì đó khác, vì vậy, đó là thuật toán đầu tiên về chiều sâu cơ bản.

Không sử dụng bất kỳ heuristics nào, nó không đặc biệt hiệu quả. Tôi lấy câu đố này từ Wikipedia ( http://en.wikipedia.org/wiki/Sudoku ):

$ time python sudoku.py 530070000600195000098000060800060003400803001700020006060000280000419005000080079
534678912672195348198342567859761423426853791713924856961537284287419635345286179

real    0m47.881s
user    0m47.223s
sys 0m0.137s

Phụ lục: Tôi sẽ viết lại nó như thế nào với tư cách là một lập trình viên bảo trì (phiên bản này có tốc độ tăng gấp 93 lần :)

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'

1
... mà chỉ đi để chứng minh rằng bạn vẫn có thể viết mã xấu trong python nếu bạn cố gắng thực sự khó khăn :-)
John Fouhy

2
Chỉ để rõ ràng, bạn có thể muốn thay đổi i%9/3 == j%9/3thành (i%9) / 3 == (j%9) / 3. Tôi biết bạn phải biết thuộc lòng thứ tự của các toán tử, nhưng nó rất dễ quên và làm cho việc quét dễ dàng hơn một chút.
Jordan Reiter

1
Điều gì sẽ xảy ra nếu các số được chuyển đến hàm là sai? Điều này sẽ tồn tại mãi mãi hay nó sẽ tự chấm dứt sau khi tất cả các kết hợp được thử?
Gundars Mēness

2
@ GundarsMēness Tại mỗi điểm trong đệ quy, một vị trí trống duy nhất được xử lý. Nếu không tìm thấy chữ số hợp lệ nào cho vị trí này, hàm chỉ trả về. Điều đó có nghĩa, nếu không có chữ số có giá trị cho các vị trí còn trống đầu tiên có thể được tìm thấy (tức là đầu vào là một sudoku không hợp lệ) trở về toàn bộ chương trình mà không cần đầu ra ( sys.exit(a)không bao giờ đạt được)
MartinStettner

5
@JoshBibb Tôi biết đây là một bài đăng cũ, nhưng lỗi đó đang xảy ra với bạn vì nó được viết cho Python2 và bạn đang chạy nó bằng Python3. Thay thế tất cả các /toán tử trong same_row, same_colsame_blockbằng các //toán tử và bạn sẽ nhận được câu trả lời đúng.
Adam Smith

10

không làm phiền nó:

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

Vì vậy, chúng ta chỉ cần tính ra biểu thức danh sách bên trong. Tôi biết nó thu thập các chữ số được đặt trong dòng - nếu không, mã xung quanh nó không có ý nghĩa gì. Tuy nhiên, tôi không có manh mối thực sự làm thế nào nó làm điều đó (và tôi quá mệt mỏi để tìm ra sự ưa thích nhị phân đó ngay bây giờ, xin lỗi)


Tôi không phải là một chuyên gia về trăn, nhưng dòng 3 là hay xuất cảnh, vì vậy tôi nghĩ logic của bạn là ngược lại
Bobby Jack

Giả sử i = -1. Khi đó ~ i = 0 và 0 hoặc foo khiến foo được đánh giá. Mặt khác, nếu i! = -1, thì ~ i sẽ khác 0, do đó, phần đầu tiên của hoặc sẽ là true, khiến tham số thứ hai của hoặc không được đánh giá, do đoản mạch đánh giá.
Tetha 14/10/08

7

r(a)là một hàm đệ quy cố gắng điền vào 0bảng trong mỗi bước.

i=a.find('0');~i or exit(a)là kết thúc thành công. Nếu không còn 0giá trị nào tồn tại trong bảng, chúng ta đã hoàn tất.

mlà giá trị hiện tại mà chúng tôi sẽ cố gắng điền vào 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)]đánh giá trung thực nếu nó không chính xác về mặt hiển thị khi đưa mvào dòng điện 0. Hãy đặt biệt danh cho nó là "is_bad". Đây là một chút khó khăn nhất. :)

is_bad or r(a[:i]+m+a[i+1:]là một bước đệ quy có điều kiện. Nó sẽ đệ quy cố gắng đánh giá tiếp theo 0 trong hội đồng, ứng viên giải pháp hiện tại có vẻ khỏe mạnh hay không.

for m in '%d'%5**18 liệt kê tất cả các số từ 1 đến 9 (không hiệu quả).


5

Rất nhiều người giải sudoku ngắn chỉ cần thử đệ quy mọi số hợp pháp có thể còn lại cho đến khi chúng điền thành công các ô. Tôi chưa tách nó ra, nhưng chỉ lướt qua nó, có vẻ như đó là những gì nó làm.


3

Mã không thực sự hoạt động. Bạn có thể tự mình kiểm tra. Đây là một mẫu câu đố sudoku chưa được giải:

807000003602080000000200900040005001000798000200100070004003000000040108300000506

Bạn có thể sử dụng trang web này ( http://www.sudokuwiki.org/sudoku.htm ), nhấp vào nhập câu đố và chỉ cần sao chép chuỗi trên. Đầu ra của chương trình python là: 817311213622482322131224934443535441555798655266156777774663869988847188399979596

mà không tương ứng với giải pháp. Trên thực tế, bạn đã có thể thấy một sự mâu thuẫn, hai số 1 ở hàng đầu tiên.


1
Điểm tốt. Làm thế nào bạn quản lý để tìm một câu đố như vậy? Có một số loại đặc điểm trong câu đố ném của người giải quyết này?
Ville Salonen

3
Cẩn thận: Nó được viết bằng Python 2.7 và tạo ra phản hồi chính xác là: 897451623632987415415236987749325861163798254258164379584613792976542138321879546. Không sử dụng Python 3 vì các khoảng chia khác nhau.
Dự án beta
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.