Solving a Sudoku with Collections
If you want to write a program to solve sudokus, you can use almost any programming language. And PL/SQL too!

Solving a Sudoku can basically be done in two ways:
- applying rules
- brute force
See Oracle PL/SQL Sudoku Solver or How to solve sudoku puzzles for an explanation of some of the rules you can use for solving a Sudoku. But my I will describe my program. I use a nested table, so I started with the definition of some types.
create or replace type mog is object
( r number(1)
, c number(1)
, b number(1)
, v number(1)
, p sys.dbms_debug_vc2coll
)
/
create or replace type tab_mog is table of mog
/
In the type mog I can store the row, column and block of a cell in the sodoku, the solution value for the cell and the possible solutions for the cell in the nested table p. As input for my function I use a refcursor, which should contain a query with 9 records with 9 columns, for instance
select count(v) from table( sudoku( cursor( <br />select '','','','1','','','','','' from dual union all<br />select '','','','','','5','8','1','' from dual union all<br />select '','','5','','4','2','','','6' from dual union all<br />select '6','','4','5','','','','','' from dual union all<br />select '','2','','','9','','','8','' from dual union all<br />select '','','','','','6','2','','9' from dual union all<br />select '9','','','8','1','','7','','' from dual union all<br />select '','1','6','3','','','','','' from dual union all<br />select '','','','','','9','','','' from dual<br />) ) );
This refcursor is converted to a nested table of the type tab_mog
fetch rc bulk collect into z;<br />--<br /> x := tab_mog();<br /> x.extend(81);<br /> for i in z.first .. z.last<br /> loop<br /> for j in 1 .. 9<br /> loop<br /> x( ( i - 1 ) * 9 + j ) := mog( i<br /> , j<br /> , trunc( ( i - 1 ) / 3 ) * 3 + ( trunc( ( j - 1 ) / 3 ) + 1 )<br /> , case j<br /> when 1 then z( i ).c1<br /> when 2 then z( i ).c2<br /> when 3 then z( i ).c3<br /> when 4 then z( i ).c4<br /> when 5 then z( i ).c5<br /> when 6 then z( i ).c6<br /> when 7 then z( i ).c7<br /> when 8 then z( i ).c8<br /> when 9 then z( i ).c9<br /> end<br /> , null<br /> );<br /> end loop;<br /> end loop;<br />
On this nested table I can apply 3 other functions, a simple rule based version, a more advanced rule based version and a brute force solver.
The simple rule based version determines possible values for every cell, and uses this information to solve the sudoku.
select mog( r, c, b, v<br /> , nvl2( v<br /> , sys.dbms_debug_vc2coll( v )<br /> , nvl( p<br /> , sys.dbms_debug_vc2coll( '1','2','3','4','5','6','7','8','9' )<br /> multiset except ( select cast( collect( to_char( v ) ) as sys.dbms_debug_vc2coll )<br /> from table( x ) yy<br /> where yy.r = xx.r<br /> or yy.c = xx.c<br /> or yy.b = xx.b<br /> )<br /> )<br /> )<br /> )<br /> bulk collect into y<br /> from table( x ) xx;<br />--<br /> select mog( r, c, b<br /> , case<br /> when xx.v is not null then xx.v<br /> when cardinality( xx.p ) = 1 then ( select to_number( column_value ) from table( xx.p ) )<br /> else ( select to_number( z.column_value )<br /> from table( select ( xx.p multiset except distinct<br /> cast( collect( case when yy.r = xx.r<br /> and yy.c <> xx.c<br /> then cast( pyy.column_value as varchar2(1) )<br /> end<br /> ) as sys.dbms_debug_vc2coll<br /> )<br /> ) multiset union<br /> ( xx.p multiset except distinct<br /> cast( collect( case when yy.c = xx.c<br /> and yy.r <> xx.r<br /> then cast( pyy.column_value as varchar2(1) )<br /> &nb sp; end<br /> ) as sys.dbms_debug_vc2coll<br /> )<br /> ) multiset union<br /> ( xx.p multiset except distinct<br /> cast( collect( case when yy.b = xx.b<br /> and ( yy.r <> xx.r or yy.c <> xx.c )<br /> then cast( pyy.column_value as varchar2(1) )<br /> end<br /> ) as sys.dbms_debug_vc2coll<br /> )<br /> ) multiset except distinct sys.dbms_debug_vc2coll( null )<br /> from table( y ) yy<br /> , table( yy.p ) pyy<br /> ) z<br /> )<br /> end<br /> , null<br /> )<br /> bulk collect into x<br /> from table( y ) xx;<br />
With this simple rule most sudokus can be solved (more than I can do by hand), the version wich uses the more advanced rules can solve even more, but even that version can’t solve the following sudoku
Su do ku So lv er by Am is<br />-- -- -- - -- -- -- - -- -- --<br /> 3 8 2<br /> 1 4 2 5<br /> 2 4 6 1 9 8 1 9 2<br /> 3 9 8<br /> 4 2 1 8 9 8<br /> 9 3 8<br /> 1 4 9 211 rows selected.
The brute force version is a very simple recursive function. It solves every soduku, the more diffucult the sudoku the faster! For above sudoku it needs 2 seconds, but for some it may take more than 2 minutes.
select m<br /> bulk collect into y<br /> from ( select mog( r, c, b, v<br /> , sys.dbms_debug_vc2coll( '1','2','3','4','5','6','7','8','9' )<br /> multiset except ( select cast( collect( to_char( v ) ) as sys.dbms_debug_vc2coll )<br /> from table( x ) yy<br /> where yy.r = xx.r<br /> or yy.c = xx.c<br /> or yy.b = xx.b<br /> )<br /> ) m<br /> from table( x ) xx<br /> where v is null<br /> )<br /> order by cardinality( m.p );<br />--<br /> if y.count = 0<br /> then<br /> return 81;<br /> end if;<br />--<br /> if y( 1 ).p.count = 0<br /> then<br /> return 0;<br /> end if;<br />--<br /> for j in y( 1 ).p.first .. y( 1 ).p.last<br /> loop <br /> x( ( y( 1 ).r - 1 ) * 9 + y( 1 ).c ).v := y( 1 ).p( j );<br /> if brute_force( x ) > 0<br /> then<br /> return 81;<br /> end if; <br /> x( ( y( 1 ).r - 1 ) * 9 + y( 1 ).c ).v := null;<br /> end loop; <br /> return 0; <br />
So to solve all sudokus the best way is to start of with the simple rule based version, and than continue with the brute force version.
The code runs only on a Oracle 10.2 database.
Anton
(I have added the zip with the sourcecode)
Is there also such a thing as an obfuscated PL/SQL contest?
After reading your post, I couldn’t resist to do the same thing, in Java of course! Click here for my post containing my solution.