PostgreSQL生成任意基數數獨-3
標籤
PostgreSQL , 數獨
背景
使用隨機填充的方法,很難生成一個有解的數獨。
本文使用了《程式設計之美》中提到的另一種生成隨機數獨的方法,模板+對映法。
首先要生成一個模板,然後根據這個模板,我們可以旋轉、正反換面,一共生成8個模板。
模板生成好之後,把1-9的數字對映進去,即可得到有效的數獨。
例子
1、生成有解數獨模板
create or replace function gen_sudoku_template()
returns setof text[] as $$
declare
res text[]; -- 結果
begin
-- 初始種子模板
create TEMP table if not exists tmp_sudoku(id int, c1 "char", c2 "char", c3 "char", c4 "char", c5 "char", c6 "char", c7 "char", c8 "char", c9 "char");
truncate tmp_sudoku;
insert into tmp_sudoku values
(1, `i`,`g`,`h`,`c`,`a`,`b`,`f`,`d`,`e`),
(2, `c`,`a`,`b`,`f`,`d`,`e`,`i`,`g`,`h`),
(3, `f`,`d`,`e`,`i`,`g`,`h`,`c`,`a`,`b`),
(4, `g`,`h`,`i`,`a`,`b`,`c`,`d`,`e`,`f`),
(5, `a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`,`i`),
(6, `d`,`e`,`f`,`g`,`h`,`i`,`a`,`b`,`c`),
(7, `h`,`i`,`g`,`b`,`c`,`a`,`e`,`f`,`d`),
(8, `b`,`c`,`a`,`e`,`f`,`d`,`h`,`i`,`g`),
(9, `e`,`f`,`d`,`h`,`i`,`g`,`b`,`c`,`a`);
-- 返回初始模板
select array_agg(array[c1,c2,c3,c4,c5,c6,c7,c8,c9] order by id) into res from tmp_sudoku ;
return next res;
-- 調轉順序,得到反面
select array_agg(array[c9,c8,c7,c6,c5,c4,c3,c2,c1] order by id) into res from tmp_sudoku ;
return next res;
-- 旋轉3次
-- 旋轉90度,得到另一個模板。
select array[
array_agg(c1 order by id desc),
array_agg(c2 order by id desc),
array_agg(c3 order by id desc),
array_agg(c4 order by id desc),
array_agg(c5 order by id desc),
array_agg(c6 order by id desc),
array_agg(c7 order by id desc),
array_agg(c8 order by id desc),
array_agg(c9 order by id desc)
] into res from tmp_sudoku;
return next res;
-- 調轉順序,得到反面
select array[
array_agg(c1 order by id),
array_agg(c2 order by id),
array_agg(c3 order by id),
array_agg(c4 order by id),
array_agg(c5 order by id),
array_agg(c6 order by id),
array_agg(c7 order by id),
array_agg(c8 order by id),
array_agg(c9 order by id)
] into res from tmp_sudoku;
return next res;
-- 旋轉90度,得到另一個模板。
select array_agg(array[c9,c8,c7,c6,c5,c4,c3,c2,c1] order by id desc) into res from tmp_sudoku ;
return next res;
-- 調轉順序,得到反面
select array_agg(array[c1,c2,c3,c4,c5,c6,c7,c8,c9] order by id desc) into res from tmp_sudoku ;
return next res;
-- 旋轉90度,得到另一個模板。
select array[
array_agg(c9 order by id desc),
array_agg(c8 order by id desc),
array_agg(c7 order by id desc),
array_agg(c6 order by id desc),
array_agg(c5 order by id desc),
array_agg(c4 order by id desc),
array_agg(c3 order by id desc),
array_agg(c2 order by id desc),
array_agg(c1 order by id desc)
] into res from tmp_sudoku;
return next res;
-- 調轉順序,得到反面
select array[
array_agg(c9 order by id),
array_agg(c8 order by id),
array_agg(c7 order by id),
array_agg(c6 order by id),
array_agg(c5 order by id),
array_agg(c4 order by id),
array_agg(c3 order by id),
array_agg(c2 order by id),
array_agg(c1 order by id)
] into res from tmp_sudoku;
return next res;
-- 總共輸出8個模板。
return;
end;
$$ language plpgsql strict;
通過以上函式,得到模板如下
gen_sudoku_template
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{{i,g,h,c,a,b,f,d,e},{c,a,b,f,d,e,i,g,h},{f,d,e,i,g,h,c,a,b},{g,h,i,a,b,c,d,e,f},{a,b,c,d,e,f,g,h,i},{d,e,f,g,h,i,a,b,c},{h,i,g,b,c,a,e,f,d},{b,c,a,e,f,d,h,i,g},{e,f,d,h,i,g,b,c,a}}
{{e,d,f,b,a,c,h,g,i},{h,g,i,e,d,f,b,a,c},{b,a,c,h,g,i,e,d,f},{f,e,d,c,b,a,i,h,g},{i,h,g,f,e,d,c,b,a},{c,b,a,i,h,g,f,e,d},{d,f,e,a,c,b,g,i,h},{g,i,h,d,f,e,a,c,b},{a,c,b,g,i,h,d,f,e}}
{{e,b,h,d,a,g,f,c,i},{f,c,i,e,b,h,d,a,g},{d,a,g,f,c,i,e,b,h},{h,e,b,g,d,a,i,f,c},{i,f,c,h,e,b,g,d,a},{g,d,a,i,f,c,h,e,b},{b,h,e,a,g,d,c,i,f},{c,i,f,b,h,e,a,g,d},{a,g,d,c,i,f,b,h,e}}
{{i,c,f,g,a,d,h,b,e},{g,a,d,h,b,e,i,c,f},{h,b,e,i,c,f,g,a,d},{c,f,i,a,d,g,b,e,h},{a,d,g,b,e,h,c,f,i},{b,e,h,c,f,i,a,d,g},{f,i,c,d,g,a,e,h,b},{d,g,a,e,h,b,f,i,c},{e,h,b,f,i,c,d,g,a}}
{{a,c,b,g,i,h,d,f,e},{g,i,h,d,f,e,a,c,b},{d,f,e,a,c,b,g,i,h},{c,b,a,i,h,g,f,e,d},{i,h,g,f,e,d,c,b,a},{f,e,d,c,b,a,i,h,g},{b,a,c,h,g,i,e,d,f},{h,g,i,e,d,f,b,a,c},{e,d,f,b,a,c,h,g,i}}
{{e,f,d,h,i,g,b,c,a},{b,c,a,e,f,d,h,i,g},{h,i,g,b,c,a,e,f,d},{d,e,f,g,h,i,a,b,c},{a,b,c,d,e,f,g,h,i},{g,h,i,a,b,c,d,e,f},{f,d,e,i,g,h,c,a,b},{c,a,b,f,d,e,i,g,h},{i,g,h,c,a,b,f,d,e}}
{{a,g,d,c,i,f,b,h,e},{c,i,f,b,h,e,a,g,d},{b,h,e,a,g,d,c,i,f},{g,d,a,i,f,c,h,e,b},{i,f,c,h,e,b,g,d,a},{h,e,b,g,d,a,i,f,c},{d,a,g,f,c,i,e,b,h},{f,c,i,e,b,h,d,a,g},{e,b,h,d,a,g,f,c,i}}
{{e,h,b,f,i,c,d,g,a},{d,g,a,e,h,b,f,i,c},{f,i,c,d,g,a,e,h,b},{b,e,h,c,f,i,a,d,g},{a,d,g,b,e,h,c,f,i},{c,f,i,a,d,g,b,e,h},{h,b,e,i,c,f,g,a,d},{g,a,d,h,b,e,i,c,f},{i,c,f,g,a,d,h,b,e}}
(8 rows)
2、隨機對映,生成有解數獨
create or replace function gen_rand_sudoku() returns int[] as $$
declare
tmp text[];
res int[];
num int;
ch text;
begin
-- 從以上的8個模板中,隨機採用一個模板
select arr into tmp from
(select arr from gen_sudoku_template() arr order by random() limit 1) t;
-- 隨機對映
for num,ch in
select row_number() over(order by random()), val from (values (`a`),(`b`),(`c`),(`d`),(`e`),(`f`),(`g`),(`h`),(`i`)) as t(val)
loop
tmp := ( replace(tmp::text, ch::text, num::text) )::text[];
end loop;
res := tmp::int[];
return res;
end;
$$ language plpgsql strict;
例子,生成隨機的數獨如下。
postgres=# select * from gen_rand_sudoku();
NOTICE: relation "tmp_sudoku" already exists, skipping
gen_rand_sudoku
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{{4,2,7,1,6,8,5,3,9},{1,6,8,5,3,9,4,2,7},{5,3,9,4,2,7,1,6,8},{2,7,4,6,8,1,3,9,5},{6,8,1,3,9,5,2,7,4},{3,9,5,2,7,4,6,8,1},{7,4,2,8,1,6,9,5,3},{8,1,6,9,5,3,7,4,2},{9,5,3,7,4,2,8,1,6}}
(1 row)
postgres=# select * from gen_rand_sudoku();
NOTICE: relation "tmp_sudoku" already exists, skipping
gen_rand_sudoku
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{{7,3,5,8,2,6,1,4,9},{1,4,9,7,3,5,8,2,6},{8,2,6,1,4,9,7,3,5},{5,7,3,6,8,2,9,1,4},{9,1,4,5,7,3,6,8,2},{6,8,2,9,1,4,5,7,3},{3,5,7,2,6,8,4,9,1},{4,9,1,3,5,7,2,6,8},{2,6,8,4,9,1,3,5,7}}
(1 row)
3、驗證一個完整的數獨是否解答正確
返回有序陣列
create or replace function sort_arr(anyarray) returns anyarray as $$
select array(select unnest($1) order by unnest);
$$ language sql strict;
create or replace function check_sudoku(int[]) returns boolean as $$
declare
dim int := sqrt(array_length($1,1)); -- 輸入數獨的基數
dims int := dim^2; -- 輸入數獨的邊長(即可填值的範圍)
sorted_val int[]; -- 排好序的陣列,用於驗證
res int[] := $1; -- 輸入值
chk_val int[]; -- 臨時儲存每一邊的陣列
begin
-- 有序陣列,用於校驗
select sort_arr(array(select generate_series(1,dims))) into sorted_val;
raise notice `%`, sorted_val;
-- 驗證每一個元素所在XYB維度是否滿足數獨的規則,嚴格來說,應該驗證兩個陣列是否相等。
for x in 1..dims loop
for y in 1..dims loop
-- 橫向驗證
perform 1 where sort_arr(array(select res[x][generate_series(1,dims)]))::int[] = sorted_val;
if not found then
raise notice `%, %`, sorted_val, sort_arr(array(select res[x][generate_series(1,dims)]))::int[];
return false;
end if;
-- 縱向驗證
perform 1 where sort_arr(array(select res[generate_series(1,dims)][y]))::int[] = sorted_val;
if not found then
raise notice `%, %`, sorted_val, sort_arr(array(select res[generate_series(1,dims)][y]))::int[];
return false;
end if;
-- BOX驗證
perform 1 where
sort_arr(array(
select res[xx][yy] from
(select generate_series(((((x-1)/dim)::int)*dim)+1, ((((x-1)/dim)::int)*dim)+dim) xx) t1,
(select generate_series(((((y-1)/dim)::int)*dim)+1, ((((y-1)/dim)::int)*dim)+dim) yy) t2
))::int[] = sorted_val;
if not found then
raise notice `%, %`, sorted_val, sort_arr(array(
select res[xx][yy] from
(select generate_series(((((x-1)/dim)::int)*dim)+1, ((((x-1)/dim)::int)*dim)+dim) xx) t1,
(select generate_series(((((y-1)/dim)::int)*dim)+1, ((((y-1)/dim)::int)*dim)+dim) yy) t2
))::int[];
return false;
end if;
end loop;
end loop;
return true;
end;
$$ language plpgsql strict;
例子
postgres=# select check_sudoku(sudoku), sudoku from gen_rand_sudoku() sudoku;
NOTICE: relation "tmp_sudoku" already exists, skipping
NOTICE: {1,2,3,4,5,6,7,8,9}
check_sudoku | sudoku
--------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
t | {{7,5,1,8,2,4,3,9,6},{8,2,4,3,9,6,7,5,1},{3,9,6,7,5,1,8,2,4},{5,1,7,2,4,8,9,6,3},{2,4,8,9,6,3,5,1,7},{9,6,3,5,1,7,2,4,8},{1,7,5,4,8,2,6,3,9},{4,8,2,6,3,9,1,7,5},{6,3,9,1,7,5,4,8,2}}
(1 row)
postgres=# select check_sudoku(sudoku), sudoku from gen_rand_sudoku() sudoku;
NOTICE: relation "tmp_sudoku" already exists, skipping
NOTICE: {1,2,3,4,5,6,7,8,9}
check_sudoku | sudoku
--------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
t | {{4,5,3,8,7,6,2,9,1},{2,9,1,4,5,3,8,7,6},{8,7,6,2,9,1,4,5,3},{3,4,5,6,8,7,1,2,9},{1,2,9,3,4,5,6,8,7},{6,8,7,1,2,9,3,4,5},{5,3,4,7,6,8,9,1,2},{9,1,2,5,3,4,7,6,8},{7,6,8,9,1,2,5,3,4}}
(1 row)
postgres=# select check_sudoku(`{{8,2,1,6,9,3,7,5,4},{6,9,3,7,5,4,8,2,1},{7,5,4,8,2,1,6,9,3},{2,1,8,9,3,6,5,4,7},{9,3,6,5,4,7,2,1,8},{5,4,7,2,1,8,9,3,6},{1,8,2,3,6,9,4,7,5},{3,6,9,4,7,5,1,8,2},{4,7,5,1,8,2,3,6,0}}`::int[]);
NOTICE: {1,2,3,4,5,6,7,8,9}
NOTICE: {1,2,3,4,5,6,7,8,9}, {0,1,2,3,4,5,6,7,8}
check_sudoku
--------------
f
(1 row)
4、生成隨機可解數獨,抹去N個值。
create or replace function gen_sudoku_question(int) returns setof int[] as $$
declare
sudo int[] := gen_rand_sudoku(); -- 生成一個隨機有解數獨
tmp int[];
dims int := array_length(sudo, 1); -- 數獨任意一邊的長度
rand int; -- 隨機位置
n int; -- 替換N個數值計數
begin
return next sudo;
loop
-- 隨機開始位置
rand := 1 + (random()*(dims^2 - 1))::int;
select array_agg(arr) from
(
select
array_agg(case when g=rand then 0 else o end order by g) arr
from
(select row_number() over() g, unnest as o from unnest(sudo)) t
group by (g-1)/dims
) t
into tmp;
sudo := tmp;
select count(*) into n from unnest(sudo) t where t=0;
if n >= $1 then
return next sudo;
return;
end if;
n := n+1;
end loop;
end;
$$ language plpgsql strict;
例子
postgres=# select gen_sudoku_question(20);
NOTICE: relation "tmp_sudoku" already exists, skipping
gen_sudoku_question
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{{6,4,9,1,2,8,5,3,7},{5,3,7,6,4,9,1,2,8},{1,2,8,5,3,7,6,4,9},{9,6,4,8,1,2,7,5,3},{7,5,3,9,6,4,8,1,2},{8,1,2,7,5,3,9,6,4},{4,9,6,2,8,1,3,7,5},{3,7,5,4,9,6,2,8,1},{2,8,1,3,7,5,4,9,6}}
{{6,4,9,1,2,8,5,3,0},{5,0,7,6,4,0,1,2,8},{0,2,0,5,3,7,6,4,0},{9,6,4,8,1,0,0,5,0},{0,5,3,9,0,4,8,1,2},{8,0,0,7,5,3,0,6,4},{4,9,6,2,8,1,0,7,5},{3,7,5,0,0,0,2,8,1},{0,8,1,3,7,0,4,9,6}}
(2 rows)
postgres=# select gen_sudoku_question(50);
NOTICE: relation "tmp_sudoku" already exists, skipping
gen_sudoku_question
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{{6,9,1,3,7,8,4,2,5},{4,2,5,6,9,1,3,7,8},{3,7,8,4,2,5,6,9,1},{1,6,9,8,3,7,5,4,2},{5,4,2,1,6,9,8,3,7},{8,3,7,5,4,2,1,6,9},{9,1,6,7,8,3,2,5,4},{2,5,4,9,1,6,7,8,3},{7,8,3,2,5,4,9,1,6}}
{{0,0,0,3,7,8,4,0,5},{4,0,5,0,0,0,0,7,8},{0,7,0,4,0,5,0,0,1},{0,0,9,8,0,7,5,4,0},{0,0,0,0,0,0,0,0,7},{8,3,0,0,0,2,0,0,9},{0,0,0,0,0,0,2,0,0},{0,5,4,0,1,6,0,0,0},{7,0,3,2,0,0,0,0,0}}
(2 rows)
格式化
postgres=# select (row_number() over ()-1)/9 as group, * from (select regexp_split_to_table(rtrim(ltrim(gen_sudoku_question(20)::text, `{{`),`}}`),`},{`) ) t;
NOTICE: relation "tmp_sudoku" already exists, skipping
group | regexp_split_to_table
-------+-----------------------
0 | 3,2,1,4,8,7,9,6,5
0 | 4,8,7,9,6,5,3,2,1
0 | 9,6,5,3,2,1,4,8,7
0 | 2,1,3,8,7,4,6,5,9
0 | 8,7,4,6,5,9,2,1,3
0 | 6,5,9,2,1,3,8,7,4
0 | 1,3,2,7,4,8,5,9,6
0 | 7,4,8,5,9,6,1,3,2
0 | 5,9,6,1,3,2,7,4,8
1 | 3,2,1,0,8,7,9,6,5
1 | 4,8,7,9,6,5,3,2,1
1 | 9,0,0,3,2,1,4,8,7
1 | 2,1,3,0,0,4,6,0,9
1 | 8,7,4,6,5,0,2,1,0
1 | 6,0,9,0,1,0,8,0,4
1 | 1,3,2,7,4,0,0,9,6
1 | 7,4,0,0,0,6,0,3,2
1 | 5,9,6,1,3,2,7,0,0
(18 rows)
輸入引數1-81,數值越大,難度越高。
小結
使用模板+對映的方法,生成數獨效率大幅提高。
PostgreSQL的語法、plpgsql功能非常豐富,處理起來還是不錯的。
對接plpython, pljava, plR, plv8, ….等,可以在資料庫內部實現更加複雜的邏輯。
https://www.postgresql.org/docs/devel/static/external-pl.html
https://www.postgresql.org/docs/devel/static/external-projects.html
如果要生成16*16的數獨,方法類似。
參考
相關文章
- PostgreSQL生成任意基數數獨-2SQL
- 3、基於Python建立任意層數的深度神經網路Python神經網路
- PostgreSQL任意列組合條件行數估算實踐-取樣估算SQL
- uniapp js 數獨小遊戲 寫死的簡單數獨 數獨 3.0APPJS遊戲
- vue 數獨Vue
- 數獨遊戲遊戲
- java解數獨Java
- PostgreSQL DBA(145) - pgAdmin(AUTOVACUUM:引數解析#3)SQL
- PostgreSQL 生成隨機數字、字串、日期、驗證碼以及 UUIDSQL隨機字串UI
- 數獨遊戲技巧(轉)遊戲
- PostgreSQL:引數簡介SQL
- JavaScript數字開任意次方運算JavaScript
- 【機器學習】數值分析02——任意方程求根機器學習
- Go基礎系列:惰性數值生成器Go
- 數獨遊戲九宮格遊戲
- 生成隨機數隨機
- PostgreSQL AutoVacuum 相關引數SQL
- 前端--任意數求和(有名函式實現)前端函式
- boblog任意變數覆蓋漏洞(二)變數
- 一次數獨生成及解題演算法的剖析(Java實現)演算法Java
- 給定數字生成中文數字(MySQL)MySql
- CSS3-陰影引數基礎CSSS3
- 個人專案實戰(數獨)
- 拉丁方的衍生遊戲“數獨”遊戲
- 一個解數獨的程式
- leetcode:有效的數獨LeetCode
- sudoku 數獨 XY-ChainsAI
- 洛谷P1784.數獨
- JavaScript 生成隨機數JavaScript隨機
- mysql生成隨機數MySql隨機
- [數學] 生成函式函式
- Codeforces 876B Divisiblity of Differences:數學【任意兩數之差為k的倍數】
- postgresql配置引數最佳化SQL
- iOS生成最大最小數之間隨機數iOS隨機
- 基於聲網互動白板實現一個多人數獨遊戲遊戲
- PostgreSQL10.0preview安全增強-任意wal副本數,金融級高可用與可靠性並存需求SQLView
- LeetCode 36——有效的數獨LeetCode
- 日行一算(數獨遊戲)遊戲