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