-- Weekend practice : 2012-09-30
--
-- having recently discovered the Common Table Expressions, I wanted to see
if the SQL powerfull (threaded) motors
-- could run sophisticated algorithms (not "brute force") more quickly than
classic langages.
-- 
-- result : "could" = yes, "more quickly" = no, but I'm  a postgresql
beginner level and it is a vintage hardware
--
-- Does anyone has ideas to make the "dancing link" algorithm performant
than the "brute" force ?
--
-- Do you have more favorable results for "dancing links" on your bigger
hardware, compared to "brute" force  ?
--
-- ======================================================================
-- implementing the dancing link algorithm in pure sql to solve a sudoku
-- unfortunately : "dancing links" analyze 2067 possibilities only, but in
55 seconds
-- brute force is quicker : 18885 possibilities in 8 seconds
-- in python on same machine (centrino first generation, year 2003) , it's
3 seconds
-- ======================================================================

CREATE OR REPLACE FUNCTION whites(f text)
-- gives the table with the empty columns numbers
RETURNS SETOF integer
LANGUAGE SQL
AS $$
select gs FROM generate_series(1,length($1))  gs  where substr( $1, gs,  1
)=' ' ;
$$;

CREATE OR REPLACE function possibles(s text, ind integer)
-- gives the table of possible pieces to put in given "ind" position of the
current "s" sudoku problem

RETURNS SETOF text
LANGUAGE SQL
AS $$

select z from   (SELECT gs::text AS z FROM generate_series(1,9) gs) z
  WHERE   NOT EXISTS ( SELECT NULL
                   FROM generate_series(1,9) lp
                   WHERE  z = substr( $1, ( ($2 - 1 ) / 9 ) * 9 + lp, 1 )
                   OR     z = substr( $1, mod( $2 - 1, 9 ) - 8 + lp * 9, 1 )
                   OR     z = substr( $1, mod( ( ( $2 - 1 ) / 3 ), 3 ) * 3
                                      + ( ( $2 - 1 ) / 27 ) * 27 + lp
                                      + ( ( lp - 1 ) / 3 ) * 6
                                   , 1 )
                 )
$$;


 CREATE OR REPLACE function recommands2(s text , holes integer)
-- gives the least possible positions to evaluate from initial "s" sudoku
-- if the initial "s" sudoku is discovered non-solvable, there is no output
RETURNS SETOF text
LANGUAGE SQL
AS $$

with

-- search the smallest group of solutions to try from initial "s" sudoku
moves(posit, pion, nb, nblg , nbco, nbsq, line, colo, square) as(
select  dd, pp, least(score, nblg , nbco, nbsq) as nb, nblg ,
nbco, nbsq, line , colo, square from (
  select dd, pp , sum(100000  + dd ) over (partition by dd)  as score,
                  sum(110000+100*line+dd) over (partition by line, dd) as
nblg ,
                  sum(120000+100*colo+dd) over (partition by colo, dd) as
nbco,
                  sum(130000+100*square+dd) over (partition by square, dd)
as nbsq ,
     line, colo, square from (select  d as dd , possibles($1, d)  as pp,
(d-1)/9 as line,
      mod(d-1 , 9) as colo ,
      mod( ( ( d - 1 ) / 3 ), 3 )  * 3 +  ( ( d - 1 ) / 27 ) as square from
(
      select  whites($1)    as d ) as ee
   ) tyty) dsgf) ,

-- we must be able to play all empty position of the initial "s" sudoku
given
  unfounds(nb_nosol, nbmin) as (
    select $2 - count(*) , min(nb) from (
    select distinct  posit , nb from moves) as meme ) ,

-- we must be able to play as much different pieces in any given column,
line, sub-square
-- as there are empty holes in these sub-parts of the of the initial "s"
sudoku given
-- (as you can't play "more", a global check is sufficient, I suppose)
  unfound_lines(issues) as (
    select 3*$2 - sum(1)  as issues from  (select distinct  pion   r, line
t from moves
                union all  select distinct  pion   r, colo  t from moves
                union all  select distinct  pion   r, square  t from moves
) dfdf )
-- gives minimal group of solutions to try, is current "s" sudoku is still
a possible solution
select substr( $1, 1, a.posit - 1 ) || a.pion || substr( $1, a.posit + 1 )
from
 moves as a, unfounds, unfound_lines where a.nb=nbmin and nb_nosol=0 and
issues=0
$$;




WITH RECURSIVE
--'53  7    6  195    98    6 8   6   34  8 3  17   2   6 6    28    419
5    8  79' 2sec
-- 
-- 184 sec with dancing link vs 9 without
--'1    7 9  3  2   8  96  5    53  9   1  8   26    4   3      1  4
7  7   3  '
--'111111111222222222333333333444444444555555555666666666777777777888888888999999999'
sudoku(initial) as (select  (
'1    7 9  3  2   8  96  5    53  9   1  8   26    4   3      1  4      7
7   3  '::text)),

x( s, ind ) AS
( SELECT initial, length(initial) -length(replace(initial, ' ', '') )
  FROM   sudoku
  UNION ALL
  (with
  reco(s, ind, posi) as (select x.s , x.ind, recommands(x.s) from x where
ind>0)
  SELECT substr( s, 1, posi - 1 ) || possibles(s, posi) || substr( s, posi
+ 1 )
       , ind-1
  FROM   reco

  )
),




q( s, ind ) AS
( SELECT initial, length(initial) -length(replace(initial, ' ', '') )
  FROM   sudoku
  UNION ALL
   SELECT recommands2(q.s, q.ind) , q.ind-1   FROM   q where ind>0

  )

SELECT * FROM q WHERE ind >= 0  order by ind -- 0= sudoku, >=0 to show all
possibilities evaluated

-- "brute force" algorithm is  :
http://archives.postgresql.org/pgsql-general/2009-11/msg00150.php

Reply via email to