Sudoku solver is slow, needs considering constrain

2019-07-08 16:56发布

问题:

I have an NxN sudoku solver written in prolog. The code below works fine for 4*4 solving. (I have some Domain infos) But it is slow for 9x9. I've tried many way to improve the create row function, that it is already considering that every value in a row should be unique (and must be contained in its domain), but they did not work. How could I improve this without any librarys?

all_distinct(X) :-
    sort(X, Sorted),
    length(X, OriginalLength),
    length(Sorted, SortedLength),
    OriginalLength == SortedLength.

 good_by_coulmns(Solution) :- length(Solution, Length), 
                              forall((between(1, Length, X), get_column(Solution, X, Y)),
                              all_distinct(Y)).
get_area(Solution, X, Y, Z) :- length(Solution, Length), 
                               SQRootF is sqrt(Length),
                               SQRoot is round(SQRootF),
                               MinCol is SQRoot * (X-1) + 1,
                               MinRow is SQRoot * (Y-1) + 1,
                               matrix_block(MinRow, MinCol, SQRoot, SQRoot, Solution, A), flatten2(A,Z).

good_by_areas(Solution) :- length(Solution, Length),
                           SQRootF is sqrt(Length), SQRoot is round(SQRootF),
                           forall((between(1, SQRoot, X), between(1, SQRoot, Y), get_area(Solution, X, Y, Z)),
                           all_distinct(Z)).

createRow(Solution, Domain, Row) :- maplist(member, Row, Domain), 
                                    all_distinct(Row), 
                                    good_by_coulmns(Solution).%, write(Solution), nl.

tryToSolve(Domains, Solution) :- length(Solution, L), length(Domains, L), !, 
                                 maplist(createRow(Solution), Domains, Solution),
                                 good_by_coulmns(Solution), 
                                 good_by_areas(Solution).

I can give the missing rules if needed, but this is working fine sofar, so we can use these as building blocks.

回答1:

The question is more about A.I then programming.

Well, you need some constraints for your sudoku in order to let it work faster. There is a constraint,however it's much work so i will explain and write it in mathematics form and it depends how you implemented your code and you can easily adopt it:

Idea: The basic idea is that when you are given some sudoku then you can immediately fill few places so that at many branches program will not go into depth and then fail and then backtrack , instead it's better if it fails at some higher node. Look below at the picture, If you place this constraint then it's possible to block the way for prolog at 6 places and allow it only at 3 places because every row has 9 elements. In many cases it will not explore the nodes where i have marked blue.Without constraint it will go down till the end and you see how many extra branches it explores.

How? We exploit the properties of sudoku:

Rules: Every row , every colum and every grid has unique elements.

Algorithm: When you are given some (sudoku-table) then you have to check first three lines(0,1,2) if they have any element which is same in any of two rows. For example for every element from first row you have to check if that element is in 2nd or third row if not then go to second row and try to find an element which is in second and third row. Here are two possibilities:

You have no match: If first three lines don't have any same element then go to the next three line (3,4,5) and find there and then line (6,7,8).

Note: You have to check only those lines who form a grid(3x3 matrix) together.For example rows (0,1,2) are together then (3,4,5) are together and then (6,7,8) are together.

You have a match: Suppose you found a match in rows (0,2). Since each row can have a unique element then we know that the this element should be in row 1 but how do we know that the resulting row is 1 ,well , we need a formula. see next:

Mathematics form: You can get always resulting row by subtraction. We had a match at row 0 and row 2. Sine these three rows are together so we take sum of their row number (0 + 1 + 2 = 3). Since we work with variables and you match at (i = 0 and j = 2) and your total sum = 3

and sum- i - j = 1 so the new element should be in row 1 . This formula is always valid: think that you have match at row numbers (i = 4 and j = 5) then sum of these three row numbers

is 4 + 5 + 6 = 15 and subtract 15 - 4 -5 = 5. However there are alternative ways but we work with variable so we need to know in which row we have to place the element from which we have a match in other two rows.

Determining grid: Once you have determined in which row your new element will lie then the next step is to check in which grid it will lie because every row is a part of three grids(3x3 matrices). For example rows 0 has elements (0,1,2) in first grid then (3,4,5) in second grid and then (6,7,8) in third grid.

In total you have (9) subgrids and starting from (0 to 8).

Relation between subgrids and rows: We have only infromation about rows so we need to find relation between rows and grid numbers. It's a NxN matrix so height of a grid is equal to the width of the grid(3x3) that means that every three rows together form 3 grids because the length of the rows is 9 and length of the grid in 3 so we get 3 grids of height 3 and length 3.Example: rows (0,1,2) form three girds starting at 0 ,1 ,2 and then rows (4,5,6) from another3 grids: gridnumber(4,5,6) .

We can get their grid number by doing this : (suppose at row 0 we had a match at colum 4 with a row number 2 and colum number 2 then we should get grid 0 and grid 1(respt) because first grid is grid 0(colum: 0 1 2).

We use this formula to get a grid number in one dimensional list.

(mod(x,9)/3) + 3*(x/27)

We know now that grid 1 and 2 can't have this element because in every grid we need a unique elemen from 1 to 9. SO it should be grid 0 but how do get resulting grid, well , we can get that again by taking sum of the three row numbers (because number of rows are equal to number of grid) which is = 3 and subtract the grid number of other two grids which was(0+1) then we get grid 2.

We are not done yet , we need convert this thing to one index because we work mostly with one dimensional list. So this formula give exact place:

formula: x = (newRow)*9 + (newGrid)*3 and x is our place. You can verify this formula by pluging in values.Now we get exact three slots where out our element can be placed. At this point you say that either list[x] = list[i] or list[x+1] = list[i] or list[x+2] = list[i].