DeepSeek辅助编写的利用位掩码填充唯一候选数方法求解数独SQL

基于我修改的改用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;

我修改了几个错误:

  1. log()函数参数去掉::float强制转换
  2. solve初始known 列添加::bigint强制转换
  3. WHERE c.known < 81 改为 WHERE c.new_known <= 81
  4. 添加迭代轮数指示列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)
相关推荐
Z1Jxxx9 小时前
反序数反序数
数据结构·c++·算法
副露のmagic9 小时前
更弱智的算法学习 day25
python·学习·算法
求梦8209 小时前
【力扣hot100题】移动零(1)
算法·leetcode·职场和发展
NAGNIP9 小时前
机器学习中的数据预处理方法大全!
算法·面试
墨月白9 小时前
[QT] QT中的折线图和散点图
数据库·qt
涛涛北京9 小时前
Soft-Actor-Critic算法-连续环境
算法
vyuvyucd9 小时前
C++排序算法全解析
java·数据结构·算法
龙潜月七9 小时前
做一个背单词的脚本
数据库·windows·c#·aigc·程序那些事
胡萝卜不甜9 小时前
算法宗门---迪杰斯特拉Dijkstra(最短路径算法)
算法