|
改良后的递归法,推理过程简单多了,但最后的校验还是有点啰嗦。
WITH cells AS ( ------- 构造所有单元格及其坐标
SELECT LEVEL cell_id -------- 从左上角开始,从左到右从上到下从1开始编号
,CEIL(LEVEL/:v_width) r -------- 行号,1~v_height
,MOD(LEVEL-1,:v_width)+1 c -------- 列号,1~v_width
,SUBSTR(:v_str,LEVEL,1) cnt -------- 该单元格周围的地雷数目
FROM DUAL CONNECT BY LEVEL<=:v_width*:v_height
)
,cells2 AS (
SELECT cell_id,r,c,DECODE(cnt,' ',0,TO_NUMBER(cnt)) AS cnt
,CASE WHEN r>1 AND c>1 THEN cell_id-:v_width-1 ---- 左上方邻居的id
WHEN r>1 THEN cell_id-:v_width ---- 正上方邻居的id
WHEN c>1 THEN cell_id-1 ---- 左边邻居的id
END AS n_id ----- 参照邻居的id
,CASE WHEN r>1 THEN r-1 ELSE r END AS n_r ----参照邻居的行数
,CASE WHEN c>1 THEN c-1 ELSE c END AS n_c ----参照邻居的列数
from cells
)
,empty_cells AS (
SELECT --------- 找出所有空位及其邻居位置, 空位是未标数字的单元格,可能有地雷也可能没有
cc.*
,ROWNUM empty_id ---------- 按顺序为空位取编号,递归的时候每层编号递增,按序猜测每个空位
------ 如果某个方位的邻居不存在,比如边界上,把该邻居的id设为大数999999, 这样SUBSTR不报错只是返回NULL
,CASE WHEN n_r>1 AND n_c>1 THEN n_id-:v_width-1 ELSE 999999 END AS n1 ----- 参照邻居的左上角
,CASE WHEN n_r>1 THEN n_id-:v_width ELSE 999999 END AS n2 ----- 参照邻居的正上方
,CASE WHEN n_r>1 AND n_c<:v_width THEN n_id-:v_width+1 ELSE 999999 END AS n3 ----- 参照邻居的右上角
,CASE WHEN n_c>1 THEN n_id-1 ELSE 999999 END AS n4 ----- 参照邻居的左侧
,CASE WHEN n_r<:v_height AND n_c>1 THEN n_id+:v_width-1 ELSE 999999 END AS n6 ----- 参照邻居的左下角
,NVL(TO_NUMBER(TRIM(SUBSTR(:v_str,n_id,1))),0) n_cnt
FROM (SELECT * FROM cells2
WHERE cnt=0 ------- 空位
ORDER BY CASE WHEN cell_id<=2*:v_width THEN c ELSE 9999 END -------------- 霹雳火思路
,CASE WHEN cell_id<=2*:v_width THEN r ELSE 9999 END
,cell_id
) cc
)
,guess AS (SELECT 0 AS mine FROM DUAL UNION ALL SELECT 1 FROM DUAL)
,t(m_cnt ---- 到目前为止猜测为地雷的总数, 达到:V_CNT时就结束
,empty_id ---- 目前要猜测哪个空位
,res ---- 最后输出的结果。每猜出一个雷就拼一个 * 进去
) AS (
SELECT 0 ------- 未猜测之前总雷数为0
,1 ------- 从第一个空位开始遍历
,CAST(:v_str AS VARCHAR2(1000)) -------- 输出初始为原分布,在递归过程中嵌入地雷
FROM DUAL
UNION ALL
SELECT m_cnt + guess.mine ---- 总雷数递增或不变,取决于当前猜测
,t.empty_id+1 ------- 空单元格编号推进
,SUBSTR(res,1,e.cell_id-1)||CASE WHEN guess.mine=1 THEN '*' ELSE ' ' END||SUBSTR(t.res,e.cell_id+1) ---在输出结果中嵌入地雷
FROM t
,empty_cells e
,guess ---和两种猜测做笛卡尔积:0:没有地雷,1:有地雷
WHERE t.empty_id<=(SELECT MAX(empty_id) FROM empty_cells) ------- 空位编号推进直至最大
AND t.empty_id=e.empty_id ------连接到empty_cells表,取当前猜测空位的坐标、邻居等信息
AND t.m_cnt<:v_cnt ---- 总雷数不能溢出
AND (e.cell_id=1 OR
CASE WHEN e.n_cnt=0 THEN
DECODE(SUBSTR(t.res,e.n_id,1),'*',1,0)
WHEN DECODE(SUBSTR(t.res,e.n1,1),'*',1,0)
+DECODE(SUBSTR(t.res,e.n2,1),'*',1,0)
+DECODE(SUBSTR(t.res,e.n3,1),'*',1,0)
+DECODE(SUBSTR(t.res,e.n4,1),'*',1,0)
+DECODE(SUBSTR(t.res,e.n6,1),'*',1,0)
<e.n_cnt THEN 1
ELSE 0
END = guess.mine
)
)
,t2 AS (
SELECT ROWNUM id,res, TRANSLATE(res,'12345678',' ') as S
FROM t
WHERE m_cnt=:v_cnt ---------- 总雷数必须符合
)
SELECT res FROM (
SELECT res
FROM ( SELECT t2.id
,cells.cell_id
,t2.res
,CASE WHEN SUBSTR(t2.res,cell_id,1) = '*' THEN ' '
ELSE TO_CHAR(CASE WHEN r>1 THEN ---- 不是第一行
----也不是第一列,那么可以取左上邻居
CASE WHEN c>1 THEN DECODE(SUBSTR(s,cell_id-:v_width-1,1),'*',1,0) ELSE 0 END
----再取正上邻居
+DECODE(SUBSTR(s,cell_id-:v_width,1),'*',1,0)
----如果非最右一列,还可以取右上邻居
+CASE WHEN c<:v_width THEN DECODE(SUBSTR(s,cell_id-:v_width+1,1),'*',1,0) ELSE 0 END
ELSE 0 ---- 如果是第一行,那么上述三个邻居都不存在
END
+CASE WHEN c>1 THEN DECODE(SUBSTR(s,cell_id-1,1),'*',1,0) ELSE 0 END --- 如果非第一列则取左方邻居
+CASE WHEN c<:v_width THEN DECODE(SUBSTR(s,cell_id+1,1),'*',1,0) ELSE 0 END --- 如果非最后一列则取右方邻居
+CASE WHEN r<:v_height THEN ---- 如果不是最后一行
----也不是第一列,那么可以取左下邻居
CASE WHEN c>1 THEN DECODE(SUBSTR(s,cell_id+:v_width-1,1),'*',1,0) ELSE 0 END
----再取正下邻居
+DECODE(SUBSTR(s,cell_id+:v_width,1),'*',1,0)
----如果非最右一列,还可以取右下邻居
+CASE WHEN c<:v_width THEN DECODE(SUBSTR(s,cell_id+:v_width+1,1),'*',1,0) ELSE 0 END
ELSE 0 ---- 如果是最后一行,那么上述三个邻居都不存在
END
)
END str
FROM t2, cells
)
GROUP BY id,res
HAVING REPLACE(LISTAGG(str) WITHIN GROUP (ORDER BY cell_id) ,'0',' ')=:v_str
)
;
|
|