Hai ngày trước, tôi đã gặp một vấn đề về sudoku mà tôi đã cố gắng giải quyết với Python 3. Tôi đã được thông báo rằng một giải pháp tồn tại, nhưng tôi không chắc chắn liệu có tồn tại nhiều giải pháp hay không.
Vấn đề như sau: Một lưới sudoku 9x9 hoàn toàn trống rỗng. Tuy nhiên, nó có chứa các hộp màu và bên trong các hộp này, tổng các số phải là một số vuông . Ngoài ra, các quy tắc sudoku bình thường được áp dụng.
Vấn đề ở đây không phải là giải một câu đố sudoku, mà là tạo ra một câu đố khả thi, thỏa mãn các quy tắc của các hộp màu .
Chiến lược của tôi
Sử dụng mảng numpy, tôi đã chia lưới thành 81 chỉ mục, có thể được sắp xếp lại thành lưới 9x9.
import numpy as np
print(np.array([i for i in range(81)]).reshape((9, 9)))
->
[[ 0 1 2 3 4 5 6 7 8]
[ 9 10 11 12 13 14 15 16 17]
[18 19 20 21 22 23 24 25 26]
[27 28 29 30 31 32 33 34 35]
[36 37 38 39 40 41 42 43 44]
[45 46 47 48 49 50 51 52 53]
[54 55 56 57 58 59 60 61 62]
[63 64 65 66 67 68 69 70 71]
[72 73 74 75 76 77 78 79 80]]
Dưới đây là danh sách chứa tất cả các khối chỉ số.
boxes = [[44, 43, 42, 53],[46, 47, 38],[61, 60],[69, 70],[71, 62],
[0, 9, 18],[1, 10, 11, 20],[2, 3, 12],[4, 13, 14],[5, 6],
[7, 8],[17, 26, 35],[21, 22, 23],[15, 16, 24, 25, 34],
[27, 36, 37],[19, 28, 29],[45, 54],[55, 56],[63, 64, 65],
[72, 73, 74],[57, 66, 75 ],[58, 59, 67, 68],[76, 77],[78, 79, 80]]
Như bạn có thể thấy từ hình ảnh , hoặc từ mảng trên, các hộp được sắp xếp thành các khối 2, 3, 4 hoặc 5 (8 twos, 12 threes, 3 fours, 1 fiver). Tôi cũng nhận thấy rằng một hộp có thể chứa nhiều số mà không vi phạm bất kỳ quy tắc nào của sudoku, nhưng chỉ có 2 trong số một số là có thể. Cho thông tin đó, bình phương lớn nhất có thể sẽ là 36, vì 9 + 9 + 8 + 7 + 6 = 39, và do đó, không có tổng khối nào có thể đạt tới 49. Để tìm hiểu xem tổng của danh sách có chứa số vuông không , Tôi đã thực hiện chức năng sau:
def isSquare(array):
if np.sum(array) in [i**2 for i in range(1,7)]:
return True
else:
return False
Để tìm hiểu xem danh sách có chứa số lượng trùng lặp chính xác hay không, nghĩa là, nhiều hơn một bản sao chỉ có một số, tôi đã thực hiện chức năng sau:
def twice(array):
counter = [0]*9
for i in range(len(array)):
counter[array[i]-1]+=1
if 3 in counter:
return False
if counter.count(2)>1:
return False
return True
Bây giờ, với các chữ số 1-9, có một số cách giới hạn giải pháp cho một danh sách, nếu danh sách phải tổng hợp thành một số vuông. Sử dụng itertools , tôi có thể tìm thấy các giải pháp, chia chúng thành một mảng, trong đó chỉ số 0 chứa các khối twos, chỉ số 1 chứa các khối ba mươi, v.v.
from itertools combinations_with_replacement
solutions = []
for k in range(2, 6):
solutions.append([list(i) for i in combinations_with_replacement(np.arange(1, 10), k) if
isSquare(i) and twice(i)])
Tuy nhiên, bất kỳ hoán vị của các danh sách này là giải pháp khả thi cho "vấn đề bình phương". Sử dụng itertools một lần nữa, tổng số lượng hộp có thể (không có quy tắc sudoku) tổng cộng là 8782.
from itertools import permutations
def find_squares():
solutions = []
for k in range(2, 6):
solutions.append([list(i) for i in combinations_with_replacement(np.arange(1, 10), k) if
isSquare(i) and twice(i)])
s = []
for item in solutions:
d=[]
for arr in item:
for k in permutations(arr):
d.append(list(k))
s.append(d)
return s # 4-dimensional array, max 2 of each
solutions = find_squares()
total = sum([len(i) for i in solutions])
print(total)
-> 8782
Điều này là đủ để thực hiện chức năng quyết định xem một bảng có hợp pháp hay không, nghĩa là các hàng, cột và hộp chỉ chứa một trong các chữ số 1-9. Thực hiện của tôi:
def legal_row(arr):
for k in range(len(arr)):
values = []
for i in range(len(arr[k])):
if (arr[k][i] != 0):
if (arr[k][i] in values):
return False
else:
values.append(arr[k][i])
return True
def legal_column(arr):
return legal_row(np.array(arr, dtype=int).T)
def legal_box(arr):
return legal_row(arr.reshape(3,3,3,3).swapaxes(1,2).reshape(9,9))
def legal(arr):
return (legal_row(arr) and legal_column(arr) and legal_box(arr))
Khó khăn với thời gian chạy
Một cách tiếp cận đơn giản sẽ là kiểm tra mọi sự kết hợp của từng khối. Tôi đã khắc phục điều này và tạo ra một số vấn đề khả thi, tuy nhiên sự phức tạp của thuật toán của tôi khiến việc này mất quá nhiều thời gian.
Thay vào đó, tôi đã cố gắng ngẫu nhiên một số thuộc tính: Thứ tự của các khối và thứ tự của các giải pháp. Sử dụng điều này, tôi đã giới hạn số lần thử và kiểm tra xem một giải pháp có khả thi hay không:
attempts = 1000
correct = 0
possibleBoards = []
for i in range(1, attempts+1):
board = np.zeros((9, 9), dtype=int)
score = 0
shapes = boxes
np.random.shuffle(shapes)
for block in shapes:
new_board = board
new_1d = board.reshape(81)
all_sols = solutions[len(block)-2]
np.random.shuffle(all_sols)
for sols in all_sols:
#print(len(sols))
new_1d[block] = sols
new_board = new_1d.reshape((9, 9))
if legal(new_board):
board = new_board
score+=1
break
confirm = board.reshape(81)
#solve(board) # Using my solve function, not important here
# Note that without it, correct would always be 0 as the middle of the puzzle has no boxes
confirm = board.reshape(81)
if (i%1000==0 or i==1):
print("Attempt",i)
if 0 not in confirm:
correct+=1
print(correct)
possibleBoards.append(board)
Trong đoạn mã trên, điểm số biến có nghĩa là có bao nhiêu khối thuật toán có thể tìm thấy trong một lần thử. Biến chính xác đề cập đến có bao nhiêu bảng sudoku được tạo có thể được hoàn thành. Nếu bạn quan tâm đến việc nó đã làm tốt như thế nào trong 700 lần thử, thì đây là một số thống kê (Đây là một lịch sử, trục x đại diện cho điểm số và trục y biểu thị số lượng mỗi điểm có trong 700 lần thử này).
Tôi cần giúp gì
Tôi đang vật lộn để tìm một cách khả thi để tìm ra giải pháp cho vấn đề này, đó thực sự có thể chạy trong một khoảng thời gian hữu hạn. Tôi sẽ đánh giá rất cao bất kỳ mẹo nào liên quan đến việc làm cho một số mã của tôi nhanh hơn hoặc tốt hơn, mọi ý tưởng về cách tiếp cận khác nhau cho vấn đề, mọi giải pháp cho vấn đề hoặc một số mẹo hữu ích về Python / Numpy liên quan đến vấn đề này.
isSquare(a): return math.sqrt(sum(a)) % 1.0 == 0
hoặc là. Khá chắc chắn rằng đó là cách nhanh hơn so với tính toán lại[i**2 for i in range(1,7)]
mỗi lần và thực hiện tìm kiếm tuyến tính trong đó. Ngoài ra,in
đã trả về một boolean. Không cần choif
.