基于我修改的改用2个元素而不是9个元素保存行掩码德哥SQL,
sql
WITH RECURSIVE
initial AS (
SELECT id,
puzzle board,
(SELECT array_agg(m) FROM ( -- 用2个元素分别保存前5行和后4行
SELECT SUM(case when val>0 then 1::bigint << (case when r<=4 then r else r-5 end *9+val - 1) else 0 end)::bigint as m
FROM generate_series(0, 8) r
INNER JOIN LATERAL (SELECT substring(puzzle, r*9 + i, 1) as ch FROM generate_series(1, 9) i) s ON ch <> '.'
CROSS JOIN LATERAL (SELECT (ch::text)::int as val) v
GROUP BY case when r<=4 then 1 else 2 end ORDER BY case when r<=4 then 1 else 2 end
) s) as rows1,
(SELECT array_agg(m) FROM (
SELECT SUM(case when val>0 then 1 << (val - 1)else 0 end)::int as m
FROM generate_series(1, 9) c
INNER JOIN LATERAL (SELECT substring(puzzle, (i-1)*9 + c, 1) as ch FROM generate_series(1, 9) i) s ON ch <> '.'
CROSS JOIN LATERAL (SELECT (ch::text)::int as val) v
GROUP BY c ORDER BY c
) s) as cols,
(SELECT array_agg(m) FROM (
SELECT SUM(case when val>0 then 1 << (val - 1)else 0 end)::int as m
FROM generate_series(0, 8) b
INNER JOIN LATERAL (SELECT substring(puzzle, (b/3)*27 + (b%3)*3 + ((i-1)/3)*9 + ((i-1)%3) + 1, 1) as ch FROM generate_series(1, 9) i) s ON ch <> '.'
CROSS JOIN LATERAL (SELECT (ch::text)::int as val) v
GROUP BY b ORDER BY b
) s) as boxes
,(SELECT array_agg(pos::int) FROM generate_series(1, 81) p(pos) WHERE substring(puzzle, p.pos, 1) = '0' )as positions
, length(replace(replace(puzzle,'0',''),chr(10),'')) known
FROM (select id,replace(replace(puzzle, '?', '0'),chr(10), '') puzzle from sudoku9_9 where length(replace(replace(puzzle,'?',''),chr(10),''))<300 /*and id<=15 and id<>4*/ ) sudoku9_9
),
solve AS (
SELECT 0 as iteration,id, board::text board, rows1, cols, boxes, false as solved, positions, known::bigint FROM initial
UNION ALL
(
WITH current_level AS (
SELECT * FROM solve WHERE NOT solved
),
-- 找出所有唯一候选数(候选数数量为1)
unique_candidates AS (
SELECT iteration,
cl.id,
cl.board,
cl.rows1,
cl.cols,
cl.boxes,
cl.positions,
cl.known,
pos,
available_mask,
-- 提取唯一数字
floor(log(2, available_mask & -available_mask))::int + 1 as val
-- 正确的数字提取方法
--10-position(b'1' in (available_mask & -available_mask)::bit(9)) as val
FROM (select *, unnest(positions) as pos from current_level) cl
CROSS JOIN LATERAL (
SELECT (( ((cl.rows1[(pos-1)/45 + 1]>>9*((pos-1)/9%5) & 511)::int |
cl.cols[(pos-1)%9 + 1]::int |
cl.boxes[((pos-1)/27*3 + (pos-1)%9/3) + 1]::int) # 511 ) & 511 )::int as available_mask
) mask
WHERE mask.available_mask > 0
-- 检查是否为唯一候选数:位掩码中只有一个1
AND (mask.available_mask & (mask.available_mask - 1)) = 0
),
-- 分组统计每个id的唯一候选数
uc_grouped AS (
SELECT iteration,
id,
board,
rows1,
cols,
boxes,
positions,
known,
-- 收集所有唯一候选数的位置和数字
array_agg(pos ORDER BY pos) as fill_positions,
array_agg(val ORDER BY pos) as fill_values,
COUNT(*) as fill_count
FROM unique_candidates
GROUP BY iteration,id, board, rows1, cols, boxes, positions, known
),
-- 如果没有唯一候选数,直接标记为完成
no_unique AS (
SELECT cl, iteration,cl.id, cl.board, cl.rows1, cl.cols, cl.boxes, cl.positions, cl.known, true as no_unique_found
FROM current_level cl
WHERE NOT EXISTS (SELECT 1 FROM unique_candidates uc WHERE uc.id = cl.id)
),
-- 填充所有唯一候选数(使用正确的聚合更新掩码)
filled AS (
SELECT ug.iteration,
ug.id,
ug.board,
ug.rows1,
ug.cols,
ug.boxes,
ug.positions,
ug.known,
ug.fill_positions,
ug.fill_values,
ug.fill_count,
-- 构建新棋盘(填充所有唯一候选数)
(SELECT string_agg(
CASE
-- 检查当前位置是否需要填充
WHEN EXISTS (SELECT 1 FROM unnest(ug.fill_positions, ug.fill_values) as f(pos, val) WHERE f.pos = p.pos)
THEN (SELECT f.val::text FROM unnest(ug.fill_positions, ug.fill_values) as f(pos, val) WHERE f.pos = p.pos)
ELSE substring(ug.board, p.pos, 1)
END, '' ORDER BY p.pos)
FROM generate_series(1, 81) p(pos)
) as new_board,
-- 更新行掩码(正确的方式:按rows1_idx分组聚合)
array[
ug.rows1[1] | COALESCE((
SELECT bit_or(1::bigint << (val-1 + ((pos-1)/9%5)*9))
FROM unnest(ug.fill_positions, ug.fill_values) as f(pos, val)
WHERE (pos-1)/45 = 0
), 0::bigint),
ug.rows1[2] | COALESCE((
SELECT bit_or(1::bigint << (val-1 + ((pos-1)/9%5)*9))
FROM unnest(ug.fill_positions, ug.fill_values) as f(pos, val)
WHERE (pos-1)/45 = 1
), 0::bigint)
] as new_rows,
-- 更新列掩码(正确的方式:按列索引分组聚合)
(SELECT array_agg(
ug.cols[cn] | COALESCE(
(SELECT bit_or(1 << (val-1))::int
FROM unnest(ug.fill_positions, ug.fill_values) as f(pos, val)
WHERE (f.pos-1)%9 + 1 = cn),
0
)
ORDER BY cn
)
FROM generate_series(1, 9) cn
) as new_cols,
-- 更新宫掩码(正确的方式:按宫索引分组聚合)
(SELECT array_agg(
ug.boxes[bn] | COALESCE(
(SELECT bit_or(1 << (val-1))::int
FROM unnest(ug.fill_positions, ug.fill_values) as f(pos, val)
WHERE ((f.pos-1)/27*3 + (f.pos-1)%9/3) + 1 = bn),
0
)
ORDER BY bn
)
FROM generate_series(1, 9) bn
) as new_boxes,
-- 更新位置数组(移除已填充的位置)
(SELECT array_agg(p ORDER BY p)
FROM unnest(ug.positions) as p
WHERE p NOT IN (SELECT unnest(ug.fill_positions))
) as new_positions
FROM uc_grouped ug
),
-- 合并结果:有唯一候选数的填充,没有唯一候选数的保持原样
combined AS (
-- 填充了唯一候选数的
SELECT f.iteration,
f.id,
f.new_board as board,
f.new_rows as rows1,
f.new_cols as cols,
f.new_boxes as boxes,
false as solved, -- 继续递归
f.new_positions as positions,
f.known + f.fill_count as new_known
FROM filled f
UNION ALL
-- 没有唯一候选数的
SELECT nu.iteration,
nu.id,
nu.board,
nu.rows1,
nu.cols,
nu.boxes,
true as solved, -- 无法继续填充,标记为完成
nu.positions,
nu.known as new_known
FROM no_unique nu
)
SELECT iteration+1,
c.id,
c.board,
c.rows1,
c.cols,
c.boxes,
-- 如果棋盘已满,标记为已解决;否则继续
position('0' IN c.board) = 0 as solved,
c.positions,
c.new_known as known
FROM combined c
WHERE c.new_known <= 81 -- 还没填满才继续递归
and iteration<10
)
)
SELECT i.id,
regexp_replace(i.board,'(.{9})', '\1' || chr(10),'g') AS puzzle,
regexp_replace(v.board,'(.{9})', '\1' || chr(10),'g') AS result,
v.known - i.known as filled_count,
i.known as initial_known,
v.known as final_known,
v.iteration --_count
FROM initial i
LEFT JOIN (
SELECT iteration,
id,
board,
known,
--COUNT(*) OVER (PARTITION BY id) as iteration_count,
row_number() OVER (PARTITION BY id ORDER BY known DESC) as rn
FROM solve
WHERE solved OR known = (SELECT MAX(known) FROM solve s2 WHERE s2.id = solve.id)
) v ON i.id = v.id AND v.rn = 1
ORDER BY i.id;
我修改了几个错误:
- log()函数参数去掉::float强制转换
- solve初始known 列添加::bigint强制转换
- WHERE c.known < 81 改为 WHERE c.new_known <= 81
- 添加迭代轮数指示列iteration
对1000行执行用时约是我优化的字符串版本的5倍,代码行数是2.2倍。
将
sql
AND NOT EXISTS (
SELECT 1
FROM current_chars cc2
WHERE ((cc.pos-1)/9) = ((cc2.pos-1)/9)
AND cc2.ch = n.n::text
UNION ALL
SELECT 1
FROM current_chars cc2
WHERE ((cc.pos-1)%9) = ((cc2.pos-1)%9)
AND cc2.ch = n.n::text
UNION ALL
SELECT 1
FROM current_chars cc2
WHERE
((cc.pos-1)/27) = ((cc2.pos-1)/27)
AND (((cc.pos-1)%9)/3) = (((cc2.pos-1)%9)/3)
AND cc2.ch = n.n::text
)
改为
sql
AND NOT EXISTS (
SELECT 1
FROM current_chars cc2
WHERE ((cc.pos-1)/9) = ((cc2.pos-1)/9)
AND cc2.ch = n.n::text)
AND NOT EXISTS (
SELECT 1
FROM current_chars cc2
WHERE ((cc.pos-1)%9) = ((cc2.pos-1)%9)
AND cc2.ch = n.n::text)
AND NOT EXISTS (
SELECT 1
FROM current_chars cc2
WHERE
((cc.pos-1)/27) = ((cc2.pos-1)/27)
AND (((cc.pos-1)%9)/3) = (((cc2.pos-1)%9)/3)
AND cc2.ch = n.n::text
)
结果和字符串版本一样
postgres=# \i 1230/candstr4.sql
Time: 4477.547 ms (00:04.478)
postgres=# \i 1230/candstr4a.sql
SELECT 1000
Time: 673.315 ms
postgres=# \i 1230/dsdege3.txt
SELECT 1000
Time: 3521.175 ms (00:03.521)
postgres=# select count(*) from uni_cand where final_known=81;
count
-------
613
(1 row)
postgres=# select count(*) from unipos where unknown=0;
count
-------
613
(1 row)