Search code examples
prologclpfd

Understanding constraints in Prolog


Im trying to get familiar with constraints in Prolog and I'm having trouble understanding the program below:

sudoku(Rows) :-
        length(Rows, 9), maplist(same_length(Rows), Rows),
        append(Rows, Vs), Vs ins 1..9,
        maplist(all_distinct, Rows),
        transpose(Rows, Columns),
        maplist(all_distinct, Columns),
        Rows = [As,Bs,Cs,Ds,Es,Fs,Gs,Hs,Is],
        blocks(As, Bs, Cs),
        blocks(Ds, Es, Fs),
        blocks(Gs, Hs, Is).

blocks([], [], []).
blocks([N1,N2,N3|Ns1], [N4,N5,N6|Ns2], [N7,N8,N9|Ns3]) :-
        all_distinct([N1,N2,N3,N4,N5,N6,N7,N8,N9]),
        blocks(Ns1, Ns2, Ns3).

problem(1, [[_,_,_,_,_,_,_,_,_],
            [_,_,_,_,_,3,_,8,5],
            [_,_,1,_,2,_,_,_,_],
            [_,_,_,5,_,7,_,_,_],
            [_,_,4,_,_,_,1,_,_],
            [_,9,_,_,_,_,_,_,_],
            [5,_,_,_,_,_,_,7,3],
            [_,_,2,_,1,_,_,_,_],
            [_,_,_,_,4,_,_,_,9]]).

If anyone can help break down this code for me to better understand how it's solved using constraints I would greatly appreciate it.


Solution

  • What happens is:

    • sudoku/1 imposes constraints on a 9x9 matrix of elements
      • with the help of blocks/2
    • problem/2 lists a particular problem

    And we can then put both together:

    ?- problem(1,Matrix), 
       % Matrix is now a 9x9 matrix of partially bound elements.
       % Impose constraints; these suffice to find a unique solution
       sudoku(Matrix).    
    
    Matrix = [[9,8,7,6,5,4,3,2,1],
              [2,4,6,1,7,3,9,8,5],
              [3,5,1,9,2,8,7,4,6],
              [1,2,8,5,3,7,6,9,4],
              [6,3,4,8,9,2,1,5,7],
              [7,9,5,4,6,1,8,3,2],
              [5,1,9,2,8,6,4,7,3],
              [4,7,2,3,1,9,5,6,8],
              [8,6,3,7,4,5,2,1,9]].
    

    The constraint solver starts to work immediately (apparently it detects that there is enough information to proceed; is that always the case?). There is no need to call label/1.

    Let's see how the constraints between the matrix elements are set up:

    sudoku(Rows) :-
    
       % If "Rows" is unbound, bind it to a list of 9 unbound variables.
       % If "Rows" is bound (as in our call above), this just verifies that there
       % are indeed 9 elements in Rows. Same as:
       %
       % Rows = [R1,
       %         R2,
       %         R3,
       %         R4,
       %         R5,
       %         R6,
       %         R7,
       %         R8,
       %         R9].
    
       length(Rows, 9),
    
       % For each element in "Rows", bind the element to a list of the same
       % length as "Rows" (i.e. 9); we now have a 9 x 9 matrix of possibly
       % unbound variables. A bit too clever maybe.
       %
       % Same as:
       %
       % Rows = [[E11,E12,E13,E14,E15,E16,E17,E18,E19],
       %         [E21,E22,E23,E24,E25,E26,E27,E28,E29],
       %         [E31,E32,E33,E34,E35,E36,E37,E38,E39],
       %         [E41,E42,E43,E44,E45,E46,E47,E48,E49],
       %         [E51,E52,E53,E54,E55,E56,E57,E58,E59],
       %         [E61,E62,E63,E64,E65,E66,E67,E68,E69],
       %         [E71,E72,E73,E74,E75,E76,E77,E78,E79],
       %         [E81,E82,E83,E84,E85,E86,E87,E88,E89],
       %         [E91,E92,E93,E94,E95,E96,E97,E98,E99]].
    
       maplist(same_length(Rows), Rows),     
    
       % Concatenate all the variables in all the rows into a new list Vs (that
       % predicate is badly named, it concatenates lists form a list into a list)
    
       append(Rows, Vs),
    
       % A first constraint: every element Exx in Vs must be in domain [1..9]
       % If we passed in a matrix Rows with concrete values for Exx that are not
       % in domain [1..9], this will fail.
    
       Vs ins 1..9,
    
       % Another constraint: For every row (list of 9 elements) in Rows: 
       % all the elements of the row must be distinct
    
       maplist(all_distinct, Rows),
    
       % We now want to impose the constraint that for every column, 
       % all the elements of the column must be distinct
    
       % Transpose the 9x9 matrix Rows into the 9x9 matrix Columns. 
       % (the new rows of Columns are the columns of Rows).
       %
       % Columns = [[E11,E21,E31,E41,E51,E61,E71,E81,E91],
       %            [E12,E22,E32,E42,E52,E62,E72,E82,E92]
       %            [E13,E23,E33,E43,E53,E63,E73,E83,E93]
       %            [E14,E24,E34,E44,E54,E64,E74,E84,E94]
       %            [E15,E25,E35,E45,E55,E65,E75,E85,E95]
       %            [E16,E26,E36,E46,E56,E66,E76,E86,E96]
       %            [E17,E27,E37,E47,E57,E67,E77,E87,E97]
       %            [E18,E28,E38,E48,E58,E68,E78,E88,E98]
       %            [E19,E29,E39,E49,E59,E69,E79,E89,E99]].
    
       transpose(Rows, Columns),  
    
       % Another constraint: For every "column of Rows"
       % all the elements of the column must be distinct
    
       maplist(all_distinct, Columns), 
    
       % Now we need to impose the "all-distinct" constraint
       % on the 3x3 sub-matrices
    
       % Give the rows distinct names
    
       Rows = [As,Bs,Cs,Ds,Es,Fs,Gs,Hs,Is],  
    
       % Impose constraint on "3-element-wide" groups of the
       % "3-row-high" group of As, Bs, Cs: namely, "all distinct" 
    
       blocks(As, Bs, Cs),
                       
       % Same of the next "3-high" group
       
       blocks(Ds, Es, Fs),
    
       % Same of the next "3-high" group
    
       blocks(Gs, Hs, Is).